介绍
在上一课中,我们学习了 Go 语言中的字符使用 UTF-8 编码,并以 byte
或 rune
类型存储。现在,让我们讨论字符串,它是字符的集合。让我们一起探讨这个话题。
知识点:
- 什么是字符串
- 创建字符串
- 声明字符串
- 常见的字符串函数
- 访问字符串元素
在上一课中,我们学习了 Go 语言中的字符使用 UTF-8 编码,并以 byte
或 rune
类型存储。现在,让我们讨论字符串,它是字符的集合。让我们一起探讨这个话题。
知识点:
在我们学习的第一个 Go 程序中,我们打印了字符串 "hello, world"
。
字符串是 Go 语言中的一种基本数据类型,也称为字符串字面量。它可以理解为一个字符的集合,并占据一块连续的内存空间。这块内存可以存储任何类型的数据,例如字母、文本、表情符号等。
然而,与其他语言不同,Go 中的字符串是不可变的,无法被修改。这意味着一旦字符串被创建,你就无法更改其中的单个字符。如果你需要一个修改后的字符串版本,必须创建一个新的字符串。
字符串可以通过多种方式声明。让我们来看第一种方法。创建一个名为 string.go
的新文件:
touch ~/project/string.go
编写以下代码:
package main
import "fmt"
func main() {
// 使用 var 关键字创建字符串变量 a
var a string = "labex"
a = "labex" // 将 "labex" 赋值给变量 a
// 声明变量 b 并赋值
var b string = "labs"
// 可以省略类型声明
var c = "Monday"
// 使用 := 快速声明并赋值
d := "Sunday"
fmt.Println(a, b, c, d)
}
以上代码展示了如何使用 var
关键字和 :=
操作符创建字符串。如果你在使用 var
创建变量时赋值,可以省略类型声明,如变量 c
的创建所示。:=
操作符是 Go 中声明并初始化变量的简写形式。它会根据赋值的值自动推断变量的类型。使用它可以让你的代码更加简洁。
go run string.go
预期输出如下:
labex labs Monday Sunday
在大多数情况下,我们使用双引号 ""
来声明字符串。双引号的优点是可以用作转义序列。例如,在下面的程序中,我们使用 \n
转义序列来创建新行:
package main
import "fmt"
func main() {
x := "linux\nlabex"
fmt.Println(x)
}
go run string.go
预期输出如下:
linux
labex
以下是一些常见的转义序列:
符号 | 描述 |
---|---|
\n |
换行 |
\r |
回车 |
\t |
制表符 |
\b |
退格 |
\\ |
反斜杠 |
\' |
单引号 |
\" |
双引号 |
如果你想保留文本的原始格式或需要使用多行文本,可以使用反引号来表示:
package main
import "fmt"
func main() {
// 输出 "labex" 的 ASCII 艺术
ascii := `
## ##### ######### ########## ## ## ## ## ## ## ## ## ## ## ## ## ## ######### ######### ### ## ########## ## ## ## ### ## ## ## ## ## ## ## ## ########## ## ## ######### ########## ## #`
fmt.Println(ascii)
}
反引号通常用于提示信息、HTML 模板以及其他需要保留输出原始格式的场景。反引号内的文本被视为原始字符串字面量,这意味着转义序列不会被解释。这使得包含多行文本和特殊字符变得非常方便,而无需对它们进行转义。
在上一课中,我们学习了英文字符和一般标点符号占用一个字节。
因此,在 Go 中,我们可以使用 len()
函数来获取字符串的字节长度。如果字符串中没有占用多个字节的字符,len()
函数可以准确地测量字符串的长度。
如果字符串中包含占用多个字节的字符,你可以使用 utf8.RuneCountInString
函数来获取字符串的实际字符数。
让我们看一个例子。将以下代码写入 string.go
文件:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// 使用 var 和 := 声明两个空字符串
var a string
b := ""
c := "labex"
d := "abc"
// 输出字节长度
fmt.Printf("The value of a is %s, the byte length of a is: %d\n", a, len(a))
fmt.Printf("The value of b is %s, the byte length of b is: %d\n", b, len(b))
fmt.Printf("The value of c is %s, the byte length of c is: %d\n", c, len(c))
fmt.Printf("The value of d is %s, the byte length of d is: %d\n", d, len(d))
// 输出字符串长度
fmt.Printf("The length of c is: %d\n", utf8.RuneCountInString(c))
fmt.Printf("The length of d is: %d\n", utf8.RuneCountInString(d))
}
go run string.go
预期输出如下:
The value of a is , the byte length of a is: 0
The value of b is , the byte length of b is: 0
The value of c is labex, the byte length of c is: 5
The value of d is abc, the byte length of d is: 3
The length of c is: 5
The length of d is: 3
在程序中,我们首先声明了两个空字符串以及字符串 labex
和 abc
。你可以看到它们的字节长度和实际长度是相同的,因为它们只包含单字节字符。
由于字符串本质上是字节序列,我们可以使用索引访问字符串中的单个字节或字符。在 Go 中,字符串的索引从 0 开始,类似于数组。
然而,需要注意的是,使用索引访问字符串元素返回的是 byte
,而不是字符(rune
)。如果字符串包含多字节字符,使用索引时需要小心,因为你可能无法获取完整的字符。为了安全地获取单个字符(rune
),你可以使用 for...range
循环遍历字符串,它会正确处理 UTF-8 编码。
让我们将以下代码添加到 string.go
文件中:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Hello, world!"
// 通过索引访问字节
fmt.Printf("Index 0 处的字节: %c\n", str[0]) // 输出: H
fmt.Printf("Index 7 处的字节: %c\n", str[7]) // 输出: w
// 使用 for...range 遍历以安全获取 rune
fmt.Println("遍历 rune:")
for index, char := range str {
fmt.Printf("Index: %d, Char: %c\n", index, char)
}
// 获取 rune 的数量
fmt.Printf("字符串中的 rune(字符)数量: %d\n", utf8.RuneCountInString(str))
}
现在运行程序:
go run string.go
输出如下:
Index 0 处的字节: H
Index 7 处的字节: w
遍历 rune:
Index: 0, Char: H
Index: 1, Char: e
Index: 2, Char: l
Index: 3, Char: l
Index: 4, Char: o
Index: 5, Char: ,
Index: 6, Char:
Index: 7, Char: w
Index: 8, Char: o
Index: 9, Char: r
Index: 10, Char: l
Index: 11, Char: d
Index: 12, Char: !
字符串中的 rune(字符)数量: 13
在这个输出中,你可以看到:
for...range
循环可以正确地遍历 rune。这个示例突出了字节和 rune 之间的关键区别,以及在处理 Go 字符串中的字符时使用适当方法的重要性。
我们可以使用 strconv
包中的函数来实现字符串和整数之间的转换:
package main
import (
"fmt"
"strconv"
)
func main() {
// 声明一个字符串 a 和一个整数 b
a, b := "233", 223
// 使用 Atoi 将字符串转换为整数
c, _ := strconv.Atoi(a)
// 分别使用 Sprintf 和 Itoa 函数
// 将整数转换为字符串
d1 := fmt.Sprintf("%d", b)
d2 := strconv.Itoa(b)
fmt.Printf("a 的类型: %T\n", a) // string
fmt.Printf("b 的类型: %T\n", b) // int
fmt.Printf("c 的类型: %T\n", c) // int
fmt.Printf("d1 的类型: %T\n", d1) // string
fmt.Printf("d2 的类型: %T\n", d2) // string
}
go run string.go
预期输出如下:
a 的类型: string
b 的类型: int
c 的类型: int
d1 的类型: string
d2 的类型: string
在程序中,我们使用了 fmt
包中的 Sprintf()
函数,其格式如下:
func Sprintf(format string, a ...interface{}) string
format
是一个包含转义序列的字符串,a
是为转义序列提供值的常量或变量,...
表示可以有多个与 a
类型相同的变量。函数后的字符串表示 Sprintf
返回一个字符串。以下是使用该函数的示例:
a = fmt.Sprintf("%d+%d=%d", 1, 2, 3)
fmt.Println(a) // 1+2=3
在这段代码中,format
传递了三个整数变量 1、2 和 3。format
中的 %d
整数转义字符被整数值替换,Sprintf
函数返回替换后的结果 1+2=3
。
此外,需要注意的是,当使用 strconv.Atoi()
将字符串转换为整数时,该函数会返回两个值:转换后的整数 val
和错误代码 err
。因为在 Go 中,如果你声明了一个变量,就必须使用它,所以我们可以使用下划线 _
来忽略 err
变量。
当 strconv.Atoi()
正确转换时,err
返回 nil
。当转换过程中发生错误时,err
返回错误信息,而 val
的值将为 0。你可以更改字符串 a
的值,并将下划线替换为普通变量来亲自尝试。这是错误处理的一个良好实践,而错误处理是 Go 编程的关键部分。
拼接两个或多个字符串的最简单方法是使用 +
操作符。我们也可以使用 fmt.Sprintf()
函数来拼接字符串。让我们看一个例子:
package main
import (
"fmt"
)
func main() {
a, b := "lab", "ex"
// 使用最简单的方法 + 进行拼接
c1 := a + b
// 使用 Sprintf 函数进行拼接
c2 := fmt.Sprintf("%s%s", a, b)
fmt.Println(a, b, c1, c2) // lab ex labex labex
}
go run string.go
预期输出如下:
lab ex labex labex
在程序中,我们还使用了 fmt
包中的 Sprintf()
函数来拼接字符串并打印结果。这两种方法都是常见的字符串拼接方式,选择哪种方法通常取决于可读性和个人偏好。
我们可以使用 strings.TrimSpace
函数来去除字符串的首尾空格。该函数接受一个字符串作为输入,并返回去除首尾空格后的字符串。其格式如下:
func TrimSpace(s string) string
以下是一个示例:
package main
import (
"fmt"
"strings"
)
func main() {
a := " \t \n labex \n \t labs"
fmt.Println(strings.TrimSpace(a))
}
go run string.go
预期输出如下:
labex
labs
注意,strings.TrimSpace()
只会去除字符串开头和结尾的空格,字符串内部的空格保持不变。
总结一下我们在这节课中学到的内容:
for...range
(rune 访问)访问字符串元素。len()
(字节长度)和 utf8.RuneCountInString
(字符/rune 长度)获取字符串的长度。strconv.Atoi()
和 strconv.Itoa()
进行字符串与整数的转换。+
操作符和 fmt.Sprintf()
拼接字符串。strings.TrimSpace()
去除字符串的首尾空格。在这节课中,我们解释了日常生活中使用的字符串。我们学习了字符串与字符之间的关系,掌握了字符串的创建和声明,并了解了一些常见的字符串函数。我们还学习了如何安全地访问字符串中的单个字符,尤其是在处理多字节字符时,并掌握了一些关键的字符串操作方法。这为你处理 Go 中的字符串数据打下了坚实的基础。