全面瞭解golang string
string我們每天都在使用,可是對於string的細節問題你真的瞭解嗎?
今天我們先以一個問題開篇。
你能猜到下面程式碼的輸出嗎?
package main import ( "fmt" ) func main() { s := "測試" fmt.Println(s) fmt.Println(len(s)) fmt.Println(s[0]) for _, v := range s { fmt.Println(v) } }
謎底揭曉:
是不是覺得很奇怪?明明是2個漢字,為啥長度是6?為啥s[0]是個數字,又為啥長度是6卻只迴圈了兩次,而且輸出的也是數字?
別急,我們一個個地說明。
長度
要知道string的長度,首先要知道string裡到底存了什麼,我們看下官方的文件:
type string string string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.
是的,沒看錯,在string裡儲存的是字元按照utf8編碼後的“8-bit bytes”二進位制資料,再說得明確點,就是我們熟悉的byte型別:
type byte = uint8 byte is an alias for uint8 and is equivalent to uint8 in all ways. It is used, by convention, to distinguish byte values from 8-bit unsigned integer values.
我們都知道,utf8在表示中文時需要2個位元組以上的空間,這裡我們一個漢字是3位元組,所以總長度就是我們直接用len得到的6。
從string中索引到的值
從string裡使用索引值得到的資料也是byte型別的,所以才會輸出數字,最好的證據在於此(最後還會有證明程式碼),還記得byte的文件嗎:
type byte = uint8
如果看不懂,沒關係,這是golang的type alias語法,相當於給某個型別起了個別名,而不是建立了新型別,所以byte就是uint8。
所以,輸出uint8型別的資料,那麼自然會看到數字。
range string時得到的值
那麼range的情況呢,長度是,為什麼只迴圈兩次?
首先我們可以排除byte了,uint8怎麼可能會有20000的值。
首先我們來看一下官方文件,其中有這麼一段:
For strings, the range does more work for you, breaking out individual Unicode code points by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD. (The name (with associated builtin type) rune is Go terminology for a single Unicode code point. See the language specification for details.) The loop
有點長,大致意思就是range會把string裡的byte重新轉換成utf8字元,對於錯誤的編碼就用一位元組的佔位符替代,這下清楚了,range實際上和如下程式碼基本等價:
for _, v := range []rune(s)
我們是字串正好是2個utf8字元,所以迴圈輸出兩次。我們再看看看看rune的文件:
type rune = int32 rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.
rune是int32的別名,它的值是Unicode碼點,所以當我們println時就看到了數字。
程式碼驗證
雖然沒什麼必要,但我們還是可以通過程式碼不算太嚴謹地驗證一下我們得到的結論,想獲取變數的型別,使用reflect.TypeOf即可(無法獲取別名,所以“不嚴謹”):
package main import ( "fmt" "reflect" ) func main() { s := "測試" fmt.Println("s type:", reflect.TypeOf(s)) fmt.Println("s[index] type:", reflect.TypeOf(s[0])) for _, v := range s { fmt.Println("range value type:", reflect.TypeOf(v)) } }
與我們預想的一樣,uint8是byte,int32是rune,雖然TypeOf無法輸出類型別名,但我們還是可以粗略判斷出它的型別名稱。
通過這篇文章,我們已經對string型別有了全面的認知。
如有錯誤歡迎指正!