string徒爹、byte荚醒、rune
在go中字符串有很多令人困惑的地方,所以查了些資料隆嗅,搞懂了些界阁,寫下此文方便查閱。
1. 從一個困惑開始
package main
import "fmt"
func main() {
s := "hello 中國"
fmt.Println(len(s))
for i := 0; i < len(s); i++ {
fmt.Printf("%c ", s[i])
}
}
// 12
// h e l l o ? ? - ? %
為什么長度是12呢胖喳,不應(yīng)該是8個字符么? 為什么按照序號取出來的數(shù)最后亂碼了泡躯?
有很多困惑,其實是由于我們對字符理解不夠?qū)е碌睦龊福瑒e著急较剃,下面我們一步步來,一一說明技健。
2. 字符與字符串
在golang中字符和字符串有很大區(qū)別,
表示形式:
-
字符
是通過單引號表示的 - 字符串是雙引好表示的
a := "a" // 這是字符串
b := 'a' // 這是字符
類型區(qū)別:
- 字符串類型為
string
- 字符類型則有兩種:
- byte = uint8(一個字節(jié)8位)
- rune = int32(這是萬國碼Unicode可以包含所有國家的字符)
PS: 默認(rèn)情況下定義一個字符它的類型為rune(更通用)
字符在底層都是整數(shù),之所以有
byte
和rune
;他們的作用相當(dāng)于別名,相比int8和int32更好區(qū)分写穴;rune代表的是通用字符。
我們來驗證一下:
package main
import "fmt"
func main() {
a := 'c' // 默認(rèn)字符 類型為rune int32
var b byte = 'c' // byte字符 byte int8
c := "c" // 雙引號 字符串
fmt.Println(a) // 發(fā)現(xiàn)了么 這里打印的是數(shù)字 因為他說整數(shù)類型啊
fmt.Printf("%T\n", a)
fmt.Println(b)
fmt.Printf("%T\n", b)
fmt.Println(c) // 只有這里會打印出c 字符串
fmt.Printf("%T\n", c)
}
// 99
// int32
// 99
// uint8
// c
// string
3. 快速了解字符編碼相關(guān)知識
要對rune有很好的了解雌贱,需要了解一些字符編碼相關(guān)知識啊送。
字符編碼指的是什么呢偿短?
我們知道在計算機(jī)底層使用的是二進(jìn)制代表數(shù)據(jù),一個字符要被計算機(jī)識別,需要一個映射:
字符 -> 對應(yīng)的二進(jìn)制數(shù)
,我們把這個過程稱為字符編碼馋没。
有哪些字符編碼呢昔逗?
ASCII編碼:
最早是美國定義的,它主要對于英文符號,英文符號相對比較少,用一個字節(jié)(8位)表示,實際使用的是 0 - 127編號(最高位都沒用上)
非ASCII編碼:
籠統(tǒng)的說篷朵,除了ASCII編碼的其它編碼就是非ASCII編碼纤子。
Unicode編碼:
俗稱萬國碼,它囊括了世界上所有符號,任何一個國際的語言都包含進(jìn)去了款票,這也是萬國的來源控硼。由于符號很多,所以它使用4個字節(jié)(32位)表示艾少。
編碼方式UTF-8:
記住UTF-8它和Unicode不同卡乾,它是Unicode的實現(xiàn)方式;
為什么已經(jīng)有了Unicode編碼還要多一個UTF-8呢?
因為Unicode使用4個字節(jié)(32)位表示缚够,如果所有字符都完整的按照Unicode去映射二進(jìn)制,比如一個ASCII字符幔妨,本身一個8位就夠啦,非要用32去表示,非常浪費谍椅,文本文件也會很大误堡。
所以UTF-8采用可變長的方式表示字符,按需使用長度,比如ASCII碼字符仍然按照8位雏吭,而其它字符則按實際Unicode值,做二進(jìn)制映射锁施。它是采用了一種二進(jìn)制高位1個數(shù)來判斷字符多少個字節(jié)的方式實現(xiàn),具體細(xì)節(jié)不做討論杖们。
PS: 在程序世界中默認(rèn)都采用UTF-8編碼方式悉抵。
一個中文字符占用幾個字節(jié)呢?
答案是3-4個字節(jié)摘完。
4. 字符串len代表的是姥饰?
代表的是字符串的字節(jié)數(shù),現(xiàn)在可以解釋一開始困惑中的問題了?
s := "hello 中國"
fmt.Println(len(s))
上面的代碼打印12是因為"hello "后面有一個空格,一個英文字符占一個字節(jié),這里占6個;后面"中國"一個字符占用3個字節(jié),所以總共12個字節(jié)孝治。
5. 如何讓字符串按照(人眼的字符打印呢)
- 使用
range
package main
import "fmt"
func main() {
s := "hello 中國"
// 第一個是索引 第一個才是字符
for _, c := range s {
fmt.Printf("%c ", c)
}
}
// h e l l o 中 國
- 先轉(zhuǎn)換為rune切片,再循環(huán)
package main
import "fmt"
func main() {
s := "hello 中國"
// 第一個是索引 第一個才是字符
ss := []rune(s)
for i := 0; i < len(ss); i++ {
fmt.Printf("%c ", ss[i])
}
}
// h e l l o 中 國
6.如何真實(按照字符)的打印字符串長度呢
用utf8.RuneCountInString
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "hello 中國"
fmt.Println(utf8.RuneCountInString(s))
}
// 8
7. 字符串修改
在go中字符串,不能直接修改列粪,不信你看
package main
import "fmt"
func main() {
s := "hello 中國"
fmt.Printf("%T\n", s[0])
s[0] = 'a' // 不能直接修改 報錯
// cannot assign to s[0] (value of type byte)
}
那如何做到修改呢?
- 改成byte/rune切片谈飒,改切片岂座,然后再轉(zhuǎn)換回字符串
package main
import "fmt"
func main() {
s := "hello 中國"
ss := []rune(s)
ss[0] = 'a' // 改h為a
s = string(ss) // 重新轉(zhuǎn)換為字符串
fmt.Println(s)
}
// aello 中國