Go 语言的 string| 青训营笔记

124 阅读4分钟

Go 是一门高效编程语言,在 Go 中,字符串被视为不可变的字节序列,其中每个元素都代表一个 Unicode 编码点。本文将从字符串的本质、原理、常见函数和中文、emoji 的处理这几个角度介绍 Go 的字符串。

字符串的本质

Go 中的字符串是不可变的字节序列,也就是说,字符串一旦被创建,就不能被修改。字符串底层是一个字节数组,每个元素都表示一个 Unicode 编码点。由于 Go 使用 UTF-8 编码,因此每个 Unicode 编码点可能由多个字节组成。

下面是一个创建字符串的示例:

s := "hello world"

在上面的代码中,字符串 "hello world" 被赋值给了变量 s。在内存中,字符串 "hello world" 被表示为一个不可变的字节数组,可以使用下标访问其中的每个元素。

字符串的原理

Go 中的字符串是一个结构体,包含两个字段:指向底层字节数组的指针和字符串的长度。当创建一个新的字符串时,Go 会开辟一段内存存储字符串的字节数组,并将指向该字节数组的指针和字符串的长度存储在字符串结构体中。

下面是一个查看字符串结构体的示例:

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    s := "hello world"
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    fmt.Printf("Data: %v\nLen: %d\n", sh.Data, sh.Len)
}

在上面的代码中,使用 reflect.StringHeader 获取字符串的底层结构体,然后使用 unsafe.Pointer 将字符串转换为指针,最后打印出底层字节数组的指针和字符串的长度。

由于字符串是不可变的,因此当需要对字符串进行修改时,Go 会先将字符串转换成可变的字节数组,修改后再转换回字符串。这个过程需要重新分配内存,并将原始字符串的内容复制到新的内存空间中,因此在频繁修改字符串时需要注意性能问题。

字符串的常见函数

Go 中提供了一些常见的字符串函数,用于处理字符串。以下是一些常见的字符串函数:

字符串长度函数

len(s string) int:返回字符串的长度(即 Unicode 编码点的个数)。

s := "hello world"
length := len(s)
fmt.Println(length) // 输出 11

整数转字符串函数

strconv.Itoa(i int) string:将一个整数转换成字符串。

i := 42
s := strconv.Itoa(i)
fmt.Println(s) // 输出 "42"

字符串分割函数

strings.Split(s, sep string) []string:将字符串按照指定的分隔符分割成多个子串,返回一个字符串切片。

s := "apple,banana,orange"
a := strings.Split(s, ",")
fmt.Println(a) // 输出 ["apple" "banana" "orange"]

字符串连接函数

strings.Join(a []string, sep string) string:将字符串切片中的所有子串使用指定的分隔符连接成一个字符串。

a := []string{"apple", "banana", "orange"}
s := strings.Join(a, ",")
fmt.Println(s) // 输出 "apple,banana,orange"

子串查找函数

strings.Contains(s, substr string) bool:判断字符串是否包含指定的子串。

s := "hello world"
if strings.Contains(s, "world") {
    fmt.Println("contains world")
}

子串替换函数

strings.Replace(s, old, new string, n int) string:将字符串中的指定子串替换为新的字符串,可以指定替换的次数。

s := "hello world"
s = strings.Replace(s, "world", "gopher", 1)
fmt.Println(s) // 输出 "hello gopher"

字符串对于中文和 emoji 的处理

由于 Go 中的字符串是基于 UTF-8 编码的,因此对于中文和 emoji 的处理需要特别注意。

对于中文,由于中文字符通常占用多个字节,因此在进行字符串操作时,需要使用 Unicode 编码点的个数而不是字节长度来计算字符串的长度。例如,下面的代码可以正确计算包含中文字符的字符串长度:

s := "hello 世界"
length := len([]rune(s))
fmt.Println(length) // 输出 9

在上面的代码中,使用 []rune 将字符串转换成 Unicode 编码点的切片,然后使用 len 函数计算切片的长度。

对于 emoji,由于一些 emoji 表情可能由多个 Unicode 编码点组成,因此在处理包含 emoji 的字符串时,也需要注意使用 Unicode 编码点的个数来计算字符串长度。

另外,由于一些操作系统和终端可能不支持显示某些 emoji 表情,因此在处理包含 emoji 的字符串时,需要注意兼容性问题。在 Go 中,可以使用 golang.org/x/text/emoji 包来处理 emoji 表情。

下面是一个使用 golang.org/x/text/emoji 包处理 emoji 的示例:

import (
    "fmt"
    "golang.org/x/text/emoji"
)

func main() {
    s := "hello 😃"
    length := len([]rune(s))
    fmt.Println(length) // 输出 7

    s = emoji.Sprint(s)
    fmt.Println(s) // 输出 "hello 😃"
}

在上面的代码中,使用 golang.org/x/text/emoji 包将包含 emoji 的字符串转换成可以显示的字符串,并使用 len 函数计算字符串长度。