[轉]全面認識golang string


作者:@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無法輸出類型別名,但我們還是可以粗略判斷出它的類型名稱。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM