1. 程式人生 > >GO學習筆記——字元和字串(14)

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切片來遍歷,那麼得到的下標是按順序的,不會出現直接遍歷字串這樣跳下標的問題。它直接把表示中文的三個位元組當成一個字元,它是按字元來遍歷的。

所以這兩種遍歷方式,需要具體具體分析,到底用哪個是區分於不同的使用場景的,如果只是想使用字元,而不使用前面的下標,那就無所謂了,只要把前面的變數用下劃線_代替就可以了。