字符串、字符、字节

Strings, bytes, runes and characters in Go - The Go Programming Language

结论:

  • go 源码文件总是以 utf-8 编码存储,对于出现在字符串中不能编码的字符,则会以添加逃逸字符的形式存储起来

  • 字符串就是只读的[]byte。可以包含任意字节,不局限于可 utf-8 编码的字节。所以使用常规的下标获取法获取到的只是一个字节

  • unicode 中每一个字符都有其对应的 code point,但是有些字符也可以由多个code point序列来表示

  • 这些序列代表了 unicode 概念中的 code points,go 将其称作 rune,也即是字符

  • 单引号即表示 rune,另外一种唯一的情况就是使用 for range 循环获取 rune

  • 由字符串文字构造的字符串(比如下面代码中的 sample),如果不包含字节等级的逃逸符号(比如 \xbd),总是包含合法的 utf-8 序列

  • Go中不保证字符串中的字符是规范化的

总结:

  • Go source code is always UTF-8.
  • A string holds arbitrary bytes.
  • A string literal, absent byte-level escapes, always holds valid UTF-8 sequences.
  • Those sequences represent Unicode code points, called runes.
  • No guarantee is made in Go that characters in strings are normalized.

验证代码:

func TestGoString(t *testing.T) {
    t.Run("string is []byte", func(t *testing.T) {
        const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98中 Z"
        //sample := []byte("\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98中 Z") // 若把 sample 换成 []byte形式, 其与字符串形式的输出一模一样

        fmt.Println("Printf with % x:")
        fmt.Printf("% x\n", sample)
        // %q将转义字符串中任何不可打印的字节序列
        fmt.Println("Printf with % q:")
        fmt.Printf("%q\n", sample)
        /* go 源码中的存储形式。
        go 的源码就是以 utf8 格式存储的,所以字面字符串自然也是 utf8。
        除非它包含像 sample 中那样的UTF-8中断转义符,否则常规字符串文本也将始终包含有效的UTF-8。
        */
        fmt.Println("Printf with % +q:")
        fmt.Printf("%+q\n", sample)

        fmt.Println()
        fmt.Println("Byte loop:")
        for i := 0; i < len(sample); i++ {
            b := sample[i]
            fmt.Printf("%x ", b)
        }
        fmt.Println()
    })

    t.Run("raw string, 只包含字面意义文本", func(t *testing.T) {
        const placeOfInterest = `⌘ \xbd 走`

        fmt.Printf("plain string: ")
        fmt.Printf("%s", placeOfInterest)
        fmt.Printf("\n")

        fmt.Printf("quoted string: ")
        // go 源码中的存储形式
        fmt.Printf("%+q", placeOfInterest)
        fmt.Printf("\n")

        // 这些字节就是 unicode 字符十六进制 2318 的 UTF-8 编码。
        // those bytes are the UTF-8 encoding of the hexadecimal value 2318.
        fmt.Printf("hex bytes: ")
        for i := 0; i < len(placeOfInterest); i++ {
            fmt.Printf("%x ", placeOfInterest[i])
        }
        fmt.Printf("\n")
    })

    t.Run("rune is character in go with a integer value", func(t *testing.T) {
        char := 'a' // rune
        fmt.Printf("%c\n", char)

        const sample = "\xbd中国\x20Z"
        fmt.Printf("% x\n", sample)
        fmt.Println("Range loop:")
        for i, runeVal := range sample {
            fmt.Printf("%#U, index at:%d\n", runeVal, i)
        }
        // Output: 
        // a
        // bd e4 b8 ad e5 9b bd 20 5a
        // Range loop:
        // U+FFFD '�', index at:0
        // U+4E2D '中', index at:1
        // U+56FD '国', index at:4
        // U+0020 ' ', index at:7
        // U+005A 'Z', index at:8
    })
}