GO學習筆記——字元和字串(14)
GO中用string表示字串,它是一個內建型別,而C++中的string是一個標準類,這是一個區別。因為字串操作非常多,另外GO中還引入了rune來支援國際化的字元(中文字元等),因此這裡單獨開一篇文章來將字元和字串。
先來簡單地看一箇中英文都有的字串
func main() {
str := "我叫lyb"
fmt.Println(len(str))
}
輸出結果
9
輸出9的原因是因為,一箇中文在utf-8編碼的情況下佔了3個字元(當然也有可能是2個字元的),加上lyb所以輸出了9。
GO中的字串其實是一個位元組切片(很好理解,C++中是string其實也是一個數組,只不過陣列中存的都是字元罷了)
rune和遍歷字串
因為字串本質是一個切片,所以也可以用range來遍歷字串
列印一些每個位元組的值看看。
func main() {
str := "我叫lyb"
for _,b := range []byte(str){ //這裡把字串看為一個位元組陣列
fmt.Printf("%X ",b) //以十六進位制的方式列印
}
}
輸出結果
E6 88 91 E5 8F AB 6C 79 62
可以看到總共是9個位元組,其中(E6 88 91)表示中文“我”,(E5 8F AB)表示中文“叫”,後面三個位元組分別表示l,y,b。
再來遍歷一下原字串
func main() { str := "我叫lyb" for i,ch := range str{ fmt.Printf("(%d,%x) ",i,ch) } for i,ch := range str{ fmt.Printf("(%d,%c) ",i,ch) //%c表示以字元形式列印 } }
輸出結果
(0,6211) (3,53eb) (6,6c) (7,79) (8,62)
(0,我) (3,叫) (6,l) (7,y) (8,b)
在這裡,這個ch其實就是一個rune型別,在存的時候,“我”佔了3個位元組,它的值是6211;“叫”佔了3個位元組,它的值是53eb。
但是在之前的輸出結果中,這個“我”明明是(E6 88 91),這裡確是6211。因為在按位元組編碼的時候,GO預設使用的是utf-8編碼,而(E6 88 91)就是utf-8編碼的“我”,而6211是Unicode編碼的。
所以編譯器做的事情是這樣的,“我”以uft-8的形式儲存在每一個位元組中,編譯器對其進行解碼之後,又將其轉成了Unicode編碼,轉完了之後又放在了rune型別中,這是一個int32型別。所以字串中的每個字元都是rune型別,rune就是GO中的char型別。
字串的長度
之前我們列印了“我叫lyb”的長度,得到的是9,因為len函式求的只是所佔位元組長度。
但是按常理我們應該得到的是5,中文也應該算是一個字元。
在utf8包中有一個函式可以幫助我們得到總共有多少字元
func main() {
str := "我叫lyb"
for i,ch := range str{
fmt.Printf("(%d,%c) ",i,ch)
}
fmt.Println()
fmt.Println("總共有多少個位元組: ",len(str))
fmt.Println("總共有多少個rune: ",utf8.RuneCountInString(str))
bytes := []byte(str)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ",ch)
}
fmt.Println()
//如果直接對每個位元組按%c輸出,會出現亂碼
for i,ch := range []byte(str){
fmt.Printf("(%d,%c) ",i,ch)
}
}
輸出結果
(0,我) (3,叫) (6,l) (7,y) (8,b)
總共有多少個位元組: 9
總共有多少個rune: 5
我 叫 l y b
(0,æ) (1,) (2,) (3,å) (4,) (5,«) (6,l) (7,y) (8,b) //這就亂碼了
字串是不可變的
GO中的字串是不可變的,一個字串一旦被定義就不可以改變,如果想要改變只能通過rune切片。
func main() {
str := "我叫lyb"
str[0] = 'a'
fmt.Println(str)
}
上述程式碼會報錯
.\main.go:7:9: cannot assign to str[0]
可以通過rune切片來改
func main() {
str := "我叫lyb"
fmt.Println(str)
//將str轉為一個rune切片,這個切片是重新開闢空間分配的,不是在原地址上直接改變的
runes := []rune(str)
runes[3] = 'a'
str = string(runes) //將rune切片再轉為string型別賦值回去
fmt.Println(str)
}
輸出結果
我叫lyb
我叫lab
最後再來看一下下面兩種遍歷方式的區別
func main() {
str := "我叫lyb"
for i,ch := range []rune(str){ //將字串轉為rune切片遍歷
fmt.Printf("(%d,%c) ",i,ch)
}
fmt.Println()
for i,ch := range str{ //直接遍歷字串
fmt.Printf("(%d,%c) ",i,ch)
}
}
輸出結果
(0,我) (1,叫) (2,l) (3,y) (4,b)
(0,我) (3,叫) (6,l) (7,y) (8,b)
可見,如果當成rune切片來遍歷,那麼得到的下標是按順序的,不會出現直接遍歷字串這樣跳下標的問題。它直接把表示中文的三個位元組當成一個字元,它是按字元來遍歷的。
所以這兩種遍歷方式,需要具體具體分析,到底用哪個是區分於不同的使用場景的,如果只是想使用字元,而不使用前面的下標,那就無所謂了,只要把前面的變數用下劃線_代替就可以了。