7·基础入门入门

字符串与 rune

stringrunebyteutf8

Go 并发编程(基础入门)第 7 课:字符串与 rune

1. 学习目标

完成本课学习后,你将能够:

  • 理解 Go 语言中 string 的底层结构与特性(UTF-8 编码的字节序列)。
  • 区分 byte(字节)与 rune(Unicode码点)的概念,并知道何时使用哪一个。
  • 掌握使用 for range 循环正确遍历包含多字节字符(如中文)的字符串。
  • 运用标准库 strings 包和切片操作对字符串进行基本处理。
  • 避免初学者在字符串操作中最常见的几个错误。

2. 核心概念

字符串 (string):不可变的字节序列

在 Go 中,字符串本质上是一个只读的字节切片 ([]byte)。它的内容使用 UTF-8 编码。这意味着一个英文字符通常占用1个字节,而一个中文字符通常占用3个字节。

var s string = "Hello, 世界"

字符串 s 在内存中是一串字节:72 101 108 108 111 44 32 228 184 150 231 149 140。它有两个关键特性:

  1. 不可变性:一旦创建,字符串的内容不能被修改。任何看起来像“修改”的操作(如 += 拼接)都会创建一个新的字符串。
  2. 长度len(s) 返回的是字符串的字节数,而不是字符数。

byterune:字符的两种视角

  • byte:是 uint8 的别名,代表一个字节。它是网络I/O、文件读写等底层操作的基本单位。
  • rune:是 int32 的别名,代表一个 Unicode码点(可以理解为一个抽象的“字符”)。它是处理文本、字符、字符串时更合适的单位。

我们通过 []byte(s)[]rune(s) 可以将字符串分别转换为字节切片和码点切片。

s := "Hi, 世"
byteSlice := []byte(s) // [72 105 44 32 228 184 150] 7个字节
runeSlice := []rune(s) // [72 105 44 32 19990]      5个码点

for range 循环:遍历字符串的正确方式

直接用 for i := 0; i < len(s); i++ 遍历字符串,得到的是字节,对中文等多字节字符会产生乱码。for range 循环会自动按UTF-8编码将字符串解析为一个个 rune,是处理文本的首选。

s := "世界, Hello"
for index, r := range s {
    fmt.Printf("索引: %d, 码点: %d, 字符: %c\n", index, r, r)
}
// 输出:
// 索引: 0, 码点: 19990, 字符: 世
// 索引: 3, 索引: 30028, 字符: 界
// ...

注意:index 是该 rune 起始字节的索引。

3. 代码示例

示例1:理解长度与遍历

package main

import "fmt"

func main() {
    str := "Go语言,你好!"

    // 1. 长度的区别
    fmt.Printf("字符串: %s\n", str)
    fmt.Printf("字节长度 (len): %d\n", len(str))          // 13个字节 (英文1字节,中文3字节)
    fmt.Printf("字符长度 (rune): %d\n", len([]rune(str))) // 7个字符

    // 2. 使用 for range 遍历字符
    fmt.Println("\n遍历 rune (字符):")
    for i, char := range str {
        // i 是该字符起始字节的索引,char 是对应的 rune
        fmt.Printf("索引: %2d, 字符: %c\n", i, char)
    }

    // 3. 对比:使用索引遍历字节(错误方式)
    fmt.Println("\n按字节索引遍历 (可能出错):")
    for i := 0; i < len(str); i++ {
        fmt.Printf("%c ", str[i]) // 对中文字符会输出乱码(字节的一部分)
    }
    fmt.Println()
}

示例2:字符串与rune切片操作

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, 世界!"
    runeSlice := []rune(s)

    // 1. 从 rune 切片中提取子串
    sub := string(runeSlice[7:9]) // 从第8个字符开始,取2个字符
    fmt.Println("提取的子串:", sub) // 输出: 世界

    // 2. 反转字符串(中文也能正确处理)
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    reversed := string(runes)
    fmt.Println("反转后的字符串:", reversed) // 输出: !界世 ,olleH

    // 3. 使用 strings 包进行查找和替换
    fmt.Println("包含“世界”吗?", strings.Contains(s, "世界"))
    newStr := strings.ReplaceAll(s, "世界", "World")
    fmt.Println("替换后:", newStr) // 输出: Hello, World!
}

4. 实践练习

练习1:统计字符类型

编写一个函数 CountChars(s string) (letters, digits, others int),统计输入字符串 s 中英文字母、数字和其他字符的数量。

  • 要求:使用 for range 遍历,并正确判断 Unicode 字符类型(提示:使用 unicode 包的 IsLetter, IsDigit 函数)。
  • 输入"你好,Go 2024!"
  • 预期输出Letters: 2, Digits: 4, Others: 4(“你好”是2个字母, “Go”是2个字母,但统计单位是rune,所以是2个中文+2个英文字母=4?这里需要明确:unicode.IsLetter 对中文字符返回 true)。

练习2:提取域名

给定一个邮箱地址字符串 email := "[email protected]",编写代码提取出域名部分(example.com)。

  • 要求:使用 strings.Index 找到 @ 的位置,然后截取后面的子串。
  • 输入"[email protected]"
  • 预期输出my-domain.net

练习3:验证回文

编写一个函数 IsPalindrome(s string) bool,判断一个字符串是否为回文(正读反读都一样)。要求忽略所有非字母数字字符,并且不区分大小写。

  • 输入示例"A man, a plan, a canal: Panama"
  • 预期输出true
  • 提示:先清理字符串,只保留字母数字并转为小写,然后使用 rune 切片进行反转比较。

5. 常见错误

  1. 误用 len 获取字符数

    • 错误:fmt.Println("长度:", len("世界")) // 输出 6,而不是 2
    • 正确:fmt.Println("长度:", len([]rune("世界"))) // 输出 2
  2. 用索引遍历中文字符串

    • 错误:for i:=0; i<len(str); i++ { fmt.Printf("%c", str[i]) } // 中文变成乱码。
    • 正确:for _, r := range str { fmt.Printf("%c", r) }
  3. 认为 string 是字符数组

    • 错误:str[0] = 'H' // 编译错误,字符串不可变。
    • 正确:创建新的字符串,或转换为 []byte[]rune 操作后再转回 string
  4. 混淆 byterune 的使用场景

    • 处理文本内容(显示、比较、查找字符)时,使用 rune
    • 处理二进制协议、文件I/O等底层数据流时,使用 byte

6. 小结

本课我们学习了Go语言处理文本的核心概念:

  • 字符串 (string) 是不可变的UTF-8编码字节序列。len()返回字节数。
  • byte (uint8) 代表一个字节,用于底层数据操作。rune (int32) 代表一个Unicode码点,用于文本处理。
  • 使用 for range 循环可以正确、安全地遍历字符串中的每个字符(rune)。
  • 将字符串转换为 []rune 切片是进行复杂文本操作(如反转、按字符索引)的基础。
  • 使用 strings 标准库可以高效完成常见的字符串查找、分割、替换等操作。
  • 始终牢记:处理的是字节 (byte) 还是字符 (rune),决定了你的操作是否正确。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「结构体」 以巩固所学知识。