这篇文章已经放到腾讯智能工作台的知识库啦,链接在这里:ima.copilot-Go 入门到入土。要是你有啥不懂的地方,就去知识库找 AI 聊一聊吧。
本篇将详细讲解 Go 语言中与字符串相关的操作。
1、rune 和 字符串长度
1、Go 函数语法约定
在开始之前,需要注意 Go 语言的一个语法规范。函数或流程控制语句的左花括号 {
必须与函数名或关键字在同一行,不能换行书写。
这种强制性规定统一了代码风格,避免了不同开发者因个人习惯造成代码格式的混乱。
2、len()
计算字符串长度
Go 语言提供了一个内置函数 len()
,可以方便地计算出字符串的长度。
在上述代码中,len()
函数直接返回了字符串 name
的长度。这是一个非常便捷和常见的操作。
3、len()
的本质:字节数
len()
函数虽然通用,但它返回的是字符串所占的字节数(the number of bytes),而不是字符数。
当字符串只包含英文字母、数字或标准 ASCII 符号时,一个字符恰好占用一个字节,此时字节数等于字符数。但当字符串中包含非 ASCII 字符(如中文、日文等)时,情况就有所不同。
在 UTF-8 编码(Go 语言默认的字符串编码)中:
-
一个英文字符占用 1 个字节。
-
一个中文字符通常占用 3 个字节。
直观上看,字符串 “叫我阿杰好l” 包含 6 个字符。但 len()
的计算结果为什么是 16 呢? 因为计算的是字节数:1 (l) + 5*3 (叫我阿杰好) = 16
个字节。
4、如何获取真实的字符数?
在很多业务场景中,我们关心的是字符串实际包含多少个字符,而不是它占用了多少字节。为了解决这个问题,我们需要将字符串转换为 rune
切片。
rune
类型是 Go 语言中用于表示单个 Unicode 字符的特殊类型。将字符串转换为 []rune
后,每个中文字符和英文字符都会被当作一个独立的元素。此时再使用 len()
函数,就能得到准确的字符数量。
5、总结
在 Go 语言中计算字符串长度时,必须注意以下关键点:
-
如果你的字符串只包含英文或 ASCII 字符,或者你关心的就是字节长度,可以直接使用
len(str)
。 -
如果你需要知道字符串中实际的字符数量(特别是处理包含中文等多字节字符的场景),必须先将字符串转换为
rune
切片[]rune(str)
,然后再对其使用len()
函数。
掌握这个细节对于正确处理多语言文本至关重要。
2、转义符详解
1、 为什么需要转义符?
假设我们需要定义一个字符串,其内容本身就包含特殊字符,例如双引号。
如果我们直接这样编写代码:
编译器会报错。因为在 Go 语言中,双引号用于界定字符串的开始和结束。上述代码会被编译器误解为:第一个字符串是 "Go "
,第二个字符串是 ""
,而中间的 体系课
则是不合法的语法。
为了在字符串内部正确地包含双引号等特殊字符,我们就需要使用转义符。
2、使用反斜杠 \
进行转义
在 Go 中,反斜杠 \
是核心的转义符。当它出现在一个特殊字符前时,它会“转义”该字符,使其失去原有的特殊含义,而被当作一个普通的字符来处理。
示例:
要在一个由双引号定义的字符串中包含双引号,我们可以在内部的双引号前加上 \
:
在这里,\"
被编译器视为一个整体,并被解释为单个双引号字符。
2、使用反引号 `
创建原生字符串
除了使用转义符,Go 语言还提供了一种更简洁的方式来处理包含大量特殊字符的字符串:原生字符串字面量 (Raw String Literal)。这种字符串使用反引号 `
(通常位于键盘 Tab 键的上方) 来定义。
在反引号包裹的字符串中,所有的字符都按其字面意义进行解释,无需任何转义。
这种写法的输出结果与使用转义符完全相同。反引号内的内容可以随意编写,包括换行符和双引号,这与 Python 中的三引号字符串有些类似,非常适合定义多行文本或包含复杂特殊字符的文本。
3、常见的转义序列
无论是否使用原生字符串,理解并掌握常见的转义序列都至关重要。转义符 \
可以与多个字符组合,形成具有特殊含义的序列。这些序列通常与 ASCII 控制码相对应。
以下是一些最常用的转义序列:
|序列|含义|描述|
|—|—|—|
|\n
|换行符|将光标移动到下一行的开头 (Line Feed)|
|\r
|回车符|将光标移动到当前行的开头 (Carriage Return)|
|\t
|水平制表符|相当于按下 Tab 键,用于对齐文本|
|\\
|反斜杠|在字符串中插入一个反斜杠字符 \
|
|\"
|双引号|在字符串中插入一个双引号字符 "
|
|\'
|单引号|在字符串中插入一个单引号字符 '
|
|\?
|问号|在字符串中插入一个问号字符 ?
|
其中,\r\n
组合(回车并换行)在 Windows 系统中常被用作标准的换行符,而 \n
在 Unix/Linux 和 macOS 中更为常见。
4、fmt.Print
与 fmt.Println
的区别
在 Go 中,标准库 fmt
提供了多种打印函数,其中 Print
和 Println
的一个关键区别在于是否自动换行。
-
fmt.Println
:在打印完所有参数后,会自动在末尾添加一个换行符。 -
fmt.Print
:仅打印传入的参数,不会在末尾添加任何内容。
如果我们想用 Print
实现与 Println
相同的换行效果,就需要手动添加转义符 \n
或 \r\n
。
掌握转义符是 Go 语言编程的基础。无论是为了在字符串中嵌入特殊字符,还是为了控制输出格式,理解 \
和 `
的用法都至关重要。在后续的开发中,我们会频繁地遇到这些概念,熟练运用它们将极大提升编码效率和代码的可读性。
3、格式化输出
在实际开发中,我们常常需要将不同类型的变量(如字符串、整数、浮点数等)组合成一个完整的字符串进行输出。
1、字符串拼接的挑战
使用简单的 +
号拼接字符串,在处理复杂或多类型数据时会变得非常繁琐且难以维护。
可以看到,这种方式不仅代码可读性差,还需要手动进行类型转换(如 strconv.Itoa
),非常不便。
2、使用 fmt.Printf
进行格式化输出
为了解决上述问题,Go 语言提供了 fmt.Printf
函数。它允许我们使用一个模板字符串和一系列占位符(也称格式化动词),将变量优雅地嵌入到字符串中。
Printf
的工作方式:
-
定义一个包含占位符的模板字符串。
-
在字符串后面,按顺序提供与占位符对应的变量。
-
函数会自动将变量替换到相应的位置,并输出到控制台。
示例:
这段代码不仅更易读、更易维护,而且 Go 会自动处理类型,我们无需再关心 int
到 string
的转换。注意 Printf
不会自动换行,因此我们在模板末尾手动添加了 \n
。
3、生成格式化字符串:fmt.Sprintf
有时我们需要的不是直接打印到控制台,而是将格式化后的结果保存为一个字符串变量。这时可以使用 fmt.Sprintf
。
它的用法与 Printf
完全相同,唯一的区别是它会返回一个格式化好的字符串,而不是将其打印出来。
这个函数在构建日志信息、生成 API 响应或任何需要先处理再输出的场景中非常有用。
4、性能说明
通常情况下,fmt.Printf
和 fmt.Sprintf
是构建字符串的首选方法,因为它们极大地提升了代码的可读性和可维护性。但在对性能有极端要求的场景中,手动的字符串拼接(如使用 strings.Builder
)可能会有更好的性能表现。对于绝大多数应用,可读性带来的好处远超微小的性能差异。
5、常用格式化占位符
Go 提供了丰富的占位符来控制输出格式。以下是一些最常用的占位符:
| 占位符 | 描述 | 常用类型 |
| ---------- | -------------------------------- | --------------- |
| 通用 | | |
| %v
| 默认格式的值。对于不同类型,会以最合适的方式展示。 | 任何类型 |
| %+v
| 在 %v
的基础上,打印结构体时会额外输出字段名。 | struct
|
| %#v
| Go 语法表示的值。输出结果是符合 Go 语法的字面量。 | 任何类型 |
| %T
| 值的类型。输出变量的类型信息。 | 任何类型 |
| 字符串 | | string
|
| %s
| 普通的字符串或 []byte
| string
|
| %q
| 为字符串加上双引号,并对特殊字符进行安全转义。 | string
|
| 整数 | | int
, uint
等 |
| %d
| 十进制表示 | int
|
| %b
| 二进制表示 | int
|
| %o
| 八进制表示 | int
|
| %x
, %X
| 十六进制表示 (分别为小写 a-f
和大写 A-F
) | int
|
| 浮点数 | | float
|
| %f
| 标准小数表示 (例如 123.456
) | float
|
| %e
, %E
| 科学计数法表示 (例如 1.23456e+02
) | float
|
| 布尔值 | | bool
|
| %t
| true
或 false
| bool
|
| 指针 | | 指针类型 |
| %p
| 指针的十六进制表示(以 0x
开头) | 指针 |
宽度与对齐示例:
你还可以控制输出的宽度和对齐方式。
掌握格式化输出是 Go 语言开发者的必备技能。fmt.Printf
和 fmt.Sprintf
提供了一种强大而灵活的方式来构建和展示字符串,使得代码更加简洁和专业。在日常开发中,应优先选择这些函数来处理字符串格式化任务。
4、高性能字符串拼接
当需要拼接大量字符串,尤其是在循环中,性能就成为一个重要的考量因素。此时,strings.Builder
是最佳选择。它的性能远高于 +
拼接和 fmt.Sprintf
。
strings.Builder
通过维护一个内部的字节缓冲区(buffer)来避免每次拼接都重新分配内存,从而实现高效的字符串构建。
使用步骤:
-
创建一个
strings.Builder
实例。 -
使用
WriteString()
或Write()
等方法向其内部缓冲区追加内容。 -
最后调用
String()
方法获取最终拼接完成的字符串。
虽然 strings.Builder
的代码看起来比 Sprintf
繁琐一些,但在性能敏感的场景下,它是构建字符串的不二之选。
如何选择拼接方式
掌握不同场景下最合适的字符串拼接方法,是编写高效、可维护 Go 代码的关键。
-
+
拼接:仅适用于少量、简单的字符串连接。 -
fmt.Sprintf
:日常开发首选。代码可读性最高,使用最方便,足以应对绝大多数场景。 -
strings.Builder
:性能要求高的场景首选。当程序中有大量或频繁的字符串拼接操作(例如在循环内部)时,应优先使用Builder
以获得最佳性能。
5、字符串的比较
在 Go 中,比较字符串是一项直接且常用的操作。
1、等于 (==
) 和不等于 (!=
)
你可以使用标准的比较运算符 ==
和 !=
来判断两个字符串是否完全相同。
2、大于 (>
) 和小于 (<
)
Go 语言同样支持使用 >
、<
、>=
和 <=
来比较字符串的大小。
比较规则: 字符串的比较是按字典顺序进行的。它会从左到右逐个比较两个字符串中对应位置字符的 ASCII 码值(或更准确地说是 Unicode 码点)。一旦发现差异,比较立即结束并返回结果。
6、字符串操作常用方法
Go 的标准库 strings
提供了大量实用、高效的字符串处理函数。要使用它们,首先需要导入该包:
import (
"strings"
)
下面我们介绍一些最常用的函数。
1、包含与计数
-
strings.Contains(s, substr)
: 判断字符串s
中是否包含子串substr
。 -
strings.Count(s, substr)
: 计算子串substr
在字符串s
中出现的次数。
2、分割与连接
strings.Split(s, sep)
: 使用分隔符sep
将字符串s
分割成一个字符串切片。
如果分隔符不存在,Split
会返回一个只包含原字符串的切片。
3、前缀与后缀
-
strings.HasPrefix(s, prefix)
: 判断字符串s
是否以prefix
开头。 -
strings.HasSuffix(s, suffix)
: 判断字符串s
是否以suffix
结尾。
4、查找位置
strings.Index(s, substr)
: 查找子串substr
在字符串s
中首次出现的位置(字节索引)。如果不存在,则返回 -1。
注意:Index
返回的是字节位置。对于包含多字节字符(如中文)的字符串,这个位置可能不等于字符的个数。
5、替换
-
strings.Replace(s, old, new, n)
: 将字符串s
中的old
子串替换为new
。参数n
控制替换次数: -
n < 0
(通常用-1
): 全部替换。 -
n > 0
: 最多替换n
次。
6、大小写转换
-
strings.ToLower(s)
: 将字符串转换为全小写。 -
strings.ToUpper(s)
: 将字符串转换为全大写。
7、修剪字符
-
strings.Trim(s, cutset)
: 从字符串s
的两端移除cutset
中包含的任意字符。 -
strings.TrimSpace(s)
: 移除字符串两端的空白字符(空格、制表符、换行符等),这是最常用的修剪函数。 -
strings.TrimLeft(s, cutset)
和strings.TrimRight(s, cutset)
: 分别只从左边或右边修剪。