1. 程式人生 > >全面瞭解golang string

全面瞭解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型別有了全面的認知。

如有錯誤歡迎指正!