Strings、bytes and runes -- 就要學習 Go 語言

Seekload發表於2018-12-23

Go 中的字串值得特別關注,與其他語言相比,Go 中的字串實現方式有所不同。

字串

在Go中,使用雙引號 "" 宣告字串:

s := "Hello world"
fmt.Println("len(s):",len(s))
fmt.Println(s);       
複製程式碼

輸出:

len(s): 11
Hello world
複製程式碼

上面的程式碼宣告瞭字串 slen 函式返回字串 s 的位元組數(包括空格)。在 Go 中,字串其實是隻讀的位元組切片

s := "Hello world"
for i:=0;i<len(s);i++ {
	fmt.Print(s[i]," ")
}
複製程式碼

你覺得上面的程式碼會輸出什麼,是一個個字母嗎?其實不是:

72 101 108 108 111 32 119 111 114 108 100
複製程式碼

輸出的是每個字母在 ASCII 碼錶上對應的十進位制數字。 正如大家熟知的,Go 語言採用 UTF-8 編碼,這種編碼方式與 ASCII 編碼相容,只不過 ASCII 編碼只需 1 個位元組,而 UTF-8 需要 1-4 個位元組表示一個符號。

s := "Hello world"
for i:=0;i<len(s) ;i++  {
	fmt.Printf("%c ",s[i])
}
fmt.Println("")
for i:=0;i<len(s) ;i++  {
	fmt.Printf("%v ",s[i])      
}
fmt.Println("")
for i:=0;i<len(s) ;i++  {
	fmt.Printf("%x ",s[i])    
}
fmt.Println("")
for i:=0;i<len(s) ;i++  {
	fmt.Printf("%T ",s[i])
}
複製程式碼

輸出

H e l l o   w o r l d 
72 101 108 108 111 32 119 111 114 108 100 
48 65 6c 6c 6f 20 77 6f 72 6c 64 
uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 
複製程式碼

上面的程式碼,%v 格式化輸出位元組對應的十進位制值;%x 以十六進位制輸出;%T 格式化輸出值的型別。從結果可以看出,值的型別都是 uint8byte 型別,byteuint8 的別名,byte 型別在我的文章中有所介紹。 我們來看下:一個字串中包含非 ASCII 碼的字元 會是怎樣的情況。

s := "Hellõ World"
fmt.Println("len(s):", len(s))
for i := 0; i < len(s); i++ {
	fmt.Printf("%c ", s[i])
}
fmt.Println("")
for i := 0; i < len(s); i++ {
	fmt.Printf("%v ", s[i])
}
fmt.Println("")
for i := 0; i < len(s); i++ {
	fmt.Printf("%x ", s[i])
}
複製程式碼

輸出

len(s): 12
H e l l à µ   W o r l d 
72 101 108 108 195 181 32 87 111 114 108 100 
48 65 6c 6c c3 b5 20 57 6f 72 6c 64
複製程式碼

上面的例子中,將 o 替換成 õ 。從結果可以看出,字串的位元組長度是 12 ,說明 õ 佔用兩個位元組。然而 õ 的輸出變成了 Ã µõUnicode 碼點是 U+00F5 ,其 UTF-8 編碼 佔兩個位元組 c3b5for 迴圈按位元組讀取,c3 (十進位制 195 )對應字元 Ãb5 (十進位制 181 )對應字元 µ詳見這裡)。 熟悉 ASCIIUTF-8Unicode 更有利於理解這些知識,關於這些知識,不會在本文展開細講,有興趣的可以參考這裡UTF-8 編碼中,一個碼點佔用至少一位元組,如果我們還是以一個碼點佔用一個位元組去列印字元肯定會出問題,就像上面的例子一樣。那有沒有辦法解決這個問題,好在 Go 為我們提供了 rune

Rune

rune 是 Go 的內建資料型別,是 int32 的別名,表示 Go 中的 Unicode 程式碼點。用 rune 資料型別,開發人員就不必關心程式碼點佔用幾個位元組了。

s := "Hellõ World"
r := []rune(s)

fmt.Println("len(r):", len(r))
for i := 0; i < len(r); i++ {
	fmt.Printf("%c ", r[i])
}
fmt.Println("")
for i := 0; i < len(r); i++ {
	fmt.Printf("%v ", r[i])
}
fmt.Println("")
for i := 0; i < len(r); i++ {
	fmt.Printf("%x ", r[i])
}
fmt.Println("")
for i := 0; i < len(r); i++ {
	fmt.Printf("%T ", r[i])
}
複製程式碼

輸出

len(r): 11
H e l l õ   W o r l d 
72 101 108 108 245 32 87 111 114 108 100 
48 65 6c 6c f5 20 57 6f 72 6c 64 
int32 int32 int32 int32 int32 int32 int32 int32 int32 int32 int32 
複製程式碼

上面的程式碼,字串 s 通過型別轉化成了 rune 切片。 õUnicode 碼點就是 U+00F5 ,對應十進位制的 245 ,參考這。切片 r 的長度就是:11 ;輸出的 int32 ,印證了 runeint32 的別名。

for range 字串

上面的例子已經很好解決了之前遇到的問題,有種更好的方式 -- range string 。使用 range 迴圈一個字串,將返回 rune 型別的字元和位元組索引。

s := "HellõWorld"
for index, char := range s {
	fmt.Printf("%c starts at byte index %d \n", char,index)
}
複製程式碼

輸出

H starts at byte index 0 
e starts at byte index 1 
l starts at byte index 2 
l starts at byte index 3 
õ starts at byte index 4 
W starts at byte index 6 
o starts at byte index 7 
r starts at byte index 8 
l starts at byte index 9 
d starts at byte index 10
複製程式碼

從輸出結果可以看出,õ 佔用了兩個位元組:索引 4 和 5。

文章讀到這,可能會有個疑問,怎麼獲取字串的長度呢?

Length of the string

可以使用 RuneCountInString() 函式,原型是這樣的:

func RuneCountInString(s string) (n int)
複製程式碼

返回字串中 rune 字元的個數。

s := "Hellõ 中國"
length := utf8.RuneCountInString(s)
fmt.Println(length)
複製程式碼

輸出:8

字串是不可變的

前面我們已經說過,字串是隻讀的位元組切片,一旦建立,是不可更改的。如果強制修改,就會報錯:

s := "Hello World"
s[0] = "h"
複製程式碼

報錯:cannot assign to s[0]

這篇文章有幾點比較重要

  1. 字串是隻讀的位元組切片;
  2. rune 表示 Go 中的 Unicode 程式碼點;
  3. Go 採用 UTF-8 編碼,這種編碼方式是 Unicode 的實現方式之一;
  4. 熟悉 ASCIIUTF-8Unicode ,可以參考 ,更有利於理解這篇文章;

希望這篇文章能夠解決你對 Go string 的一些疑問,有不懂的可以留言討論!


(全文完)

原創文章,若需轉載請註明出處!
歡迎掃碼關注公眾號「Golang來啦」或者移步 seekload.net ,檢視更多精彩文章。

公眾號「Golang來啦」給你準備了一份神祕學習大禮包,後臺回覆【電子書】領取!

公眾號二維碼

相關文章