跳转到主要内容
模块 4 · 第 4 课(共 5)课程中的19/50~12 min
模块课程(4/5)

字符串、字节和符文

在 Go 中, string 是一个不可变的字节序列。字符串文字 以 UTF-8 编码。 rune 是 int32 的别名,并且 代表单个 Unicode 代码点。

了解字节和符文之间的区别对于 当字符串包含重音符号、表情符号、表意文字或任何内容时避免出现错误 非 ASCII 字符。

string = 字节,而不是字符

Go
s := "ciaò"
fmt.Println(len(s))   // 5 — not 4!
fmt.Println(s[0])     // 99 ('c' as a byte)
fmt.Println(s[3])     // 195 (first byte of 'ò' in UTF-8)

len(s) 返回字节的数量,不可见字符。 索引 s[i] 返回第 i 个字节 (uint8),而不是字符。

for range 迭代符文

for i, r := range s 动态解码 UTF-8: i 是字节偏移量 符文开头的 r 是符文 (int32):

Go
s := "ciaò"
for i, r := range s {
    fmt.Printf("%d %c (U+%04X)\n", i, r, r)
}
// 0 c (U+0063)
// 1 i (U+0069)
// 2 a (U+0061)
// 3 ò (U+00F2)   <- starts at byte 3, takes 2 bytes

[]rune(s):每个字符索引

显式转换:分配新的符文片段。成本高昂但是 有时需要:

Go
runes := []rune("ciaò")
fmt.Println(len(runes))    // 4
fmt.Println(string(runes[3])) // ò

string(runes) 是逆转换:采用 []rune 并生成 UTF-8 字符串。

字符串是不可变的

Go
s := "ciao"
// s[0] = 'C'   // ERROR: cannot assign to s[0]

要变异,转换为 []byte 或 []rune,操作,转换回来:

Go
b := []byte(s)
b[0] = 'C'
s = string(b)   // "Ciao"

级联

+ 运算符有效,但对于循环中的许多串联,请使用 strings.Builder (更高效,避免中间分配):

Go
var sb strings.Builder
for i := 0; i < 5; i++ {
    sb.WriteString("ab")
}
fmt.Println(sb.String())   // "ababababab"

有用的包

  • strings:Contains、HasPrefix、Split、Join、ToUpper、Replace...
  • strconv:Atoi、Itoa、ParseFloat、FormatInt...
  • unicode:IsLetter、IsDigit、ToUpper...
  • unicode/utf8:RuneCountInString(替代len([]rune(s)))。

尝试一下

锻炼#go.m4.l4.e1
尝试:0加载中...

使用 %d %c 迭代带有范围的字符串 s 并打印索引和符文。

正在加载编辑器...
显示提示

字符串范围返回字节偏移+符文。

3 次尝试后可用的解决方案

锻炼#go.m4.l4.e2
尝试:0加载中...

将 s 转换为 []rune 并以字符为单位打印其长度。

正在加载编辑器...
显示提示

显式转换:`[]符文`。

3 次尝试后可用的解决方案

Quiz#go.m4.l4.e3
准备好

什么是 len("ciaò")?

Go
s := "ciaò"
fmt.Println(len(s))
答案选项

回顾

  • string = 不可变的字节序列,UTF-8 编码。
  • len(s) = 字节; s[i] = 第 i 个字节 (uint8)。
  • for i, r := range s 解码符文; i 是字节偏移量。
  • []rune(s) 按字符索引(成本高昂,分配)。
  • 变异:通过 []byte 或 []rune。
  • 在循环中连接:strings.Builder。
  • 关键包:strings、strconv、unicode、unicode/utf8。