作者:@apocelipes
本文為作者原創,轉載請注明出處:https://www.cnblogs.com/apocelipes/p/9798413.html
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的長度,首先要知道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無法輸出類型別名,但我們還是可以粗略判斷出它的類型名稱。