侧边栏壁纸
博主头像
想起个优雅名字博主等级

没啥可以写的,有什么思路就写什么。

  • 累计撰写 56 篇文章
  • 累计创建 39 个标签
  • 累计收到 7 条评论

go 字符串及底层字符类型

想起个优雅名字
2021-07-24 / 1 评论 / 0 点赞 / 383 阅读 / 3,722 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2021-07-24,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

字符串

基本使用

在 Go 语言中,字符串是一种基本类型,默认是通过 UTF-8 编码的字符序列,当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节,比如中文编码通常需要 3 个字节。

声明和初始化

字符串的声明和初始化非常简单,举例如下:

var str1 string
str1 = "Hello World!"
str2 := "你好,中国"

格式化输出

还可以通过 Go 语言内置的 len() 函数获取指定字符串的长度,以及通过 fmt 包提供的 Printf 进行字符串格式化输出:

func TestStr(t *testing.T) {
	var str1 string
	str1 = "Hello World!"
	str2 := "你好,中国"

	fmt.Printf("The length of \"%s\" is %d \n", str1, len(str1))
	fmt.Printf("The length of \"%s\" is %d \n", str2, len(str2))
	fmt.Printf("The first character of \"%s\" is %c. \n", str2, str2[0])
}

转义字符

Go 语言的字符串不支持单引号,只能通过双引号定义字符串字面值,如果要对特定字符进行转义,可以通过 \ 实现,就像我们上面在字符串中转义双引号和换行符那样,常见的需要转义的字符如下所示:

  • \n :换行符
  • \r :回车符
  • \t :tab 键
  • \u 或 \U :Unicode 字符
  • \\ :反斜杠自身

多行字符串

对于多行字符串,也可以通过 ` 构建:

func TestMulStr(t *testing.T) {
	var str string
	str = `
			测试多行文本
			多行文本的符号无法互相包含
		`
	fmt.Println(str)
}

多行文本除了使用 ` 之外,还可以使用 + 进行拼接,缺点只是不太优雅。

func TestMulStr(t *testing.T) {
	str2 := "测试多行文本\n" +
		"多行文本的符号无法互相包含"
	fmt.Println(str2)
}

不可变值类型

虽然可以通过数组下标方式访问字符串中的字符:

func TestStr2(t *testing.T) {
	var str string
	str = "abcdefghijklmnopqrstuvwxyz"
	fmt.Printf("%c", str[0])
	fmt.Printf("%c", str[14])
	fmt.Printf("%c", str[15])
}

但是和数组不同,在 Go 语言中,字符串是一种不可变值类型,一旦初始化之后,它的内容不能被修改,比如看下面这个例子:

str := "Hello world"

str[0] = 'X' // 编译错误

编译器会报类似如下的错误:

cannot assign to str[0]

字符编码

Go 语言中字符串默认是 UTF-8 编码的 Unicode 字符序列,所以可以包含非 ANSI 字符,比如「你好,中国」可以出现在 Go 代码中。

但需要注意的是,如果你的 Go 代码需要包含非 ANSI 字符,保存源文件时请注意编码格式必须选择 UTF-8。特别是在 Windows 下一般编辑器都默认保存为本地编码,比如中国地区可能是 GBK 编码而不是 UTF-8,如果没注意到这点在编译和运行时就会出现一些意料之外的情况。

字符串的编码转换是处理文本文档(比如 TXT、XML、HTML 等)时非常常见的需求,不过 Go 语言默认仅支持 UTF-8 和 Unicode 编码,对于其他编码,Go 语言标准库并没有内置的编码转换支持。所幸的是我们可以很容易基于 iconv 库包装一个,这里有一个开源项目可供参考:https://github.com/qiniu/iconv。

字符串操作

字符串连接

Go 内置提供了丰富的字符串函数,常见的操作包含连接、获取长度和指定字符,获取长度和指定字符前面已经介绍过,字符串连接只需要通过 + 连接符即可:

str = str + ", 中国"

str += ", 中国"  // 上述语句也可以简写为这样,效果完全一样

另外,还有一点需要注意的是如果字符串长度较长,需要换行,则 + 连接符必须出现在上一行的末尾,否则会报错:

str = str +
        ", 中国"

字符串切片

在 Go 语言中,可以通过字符串切片实现获取子串的功能:

str := "hello, world"
str1 := str[:5]  // 获取索引5(不含)之前的子串
str2 := str[7:]  // 获取索引7(含)之后的子串
str3 := str[0:5]  // 获取从索引0(含)到索引5(不含)之间的子串

fmt.Println("str1:", str1)
fmt.Println("str2:", str2)
fmt.Println("str3:", str3)

Go 切片区间可以对比数学中的区间概念来理解,它是一个左闭右开的区间,比如上述 str[0:5] 对应到字符串元素的区间是 [0,5)str[:5] 对应的区间是 [0,5)(数组索引从 0 开始),str[7:] 对应的区间是 [7:len(str)](这是闭区间,是个例外,因为没有指定区间结尾)。

此外 Go 字符串也支持字符串比较、是否包含指定字符/子串、获取指定子串索引位置、字符串替换、大小写转换、trim 等操作,更多操作 API,请参考标准库 strings 包,这里就不一一展示了。

字符串遍历

Go 语言支持两种方式遍历字符串。

一种是以字节数组的方式遍历:

func TestIter(t *testing.T) {
	var str string
	str = "你好,中国"
	len := len(str)
	for i := 0; i < len; i++ {
		ch := str[i]
		fmt.Println(i, ch)
	}
}

这种方式是以字节便利,len 函数获取到的是字节长度,而非字符个数长度,中文是 3 字节。

另一种是以 Unicode 字符遍历:

func TestIterChar(t *testing.T) {
   var str string
   str = "你好,中国"
   for i, v := range str {
      fmt.Printf("%d %c\n", i, v)
   }
}

Unicode 字符方式遍历时,每个字符的类型是 rune,而不是 byte

底层字符类型

Go 语言对字符串中的单个字符进行了单独的类型支持,在 Go 语言中支持两种字符类型:

  • 一种是 byte,代表 UTF-8 编码中单个字节的值(它也是 uint8 类型的别名,两者是等价的,因为正好占据 1 个字节的内存空间);
  • 另一种是 rune,代表单个 Unicode 字符(它也是 uint32 类型的别名,因为正好占据 4 个字节的内存空间。关于 rune 相关的操作,可查阅 Go 标准库的 unicode 包)。

UTF-8 和 Unicode 的区别

Unicode 是一种字符集,囊括了目前世界上所有语言的所有字符,与之类似的术语还有 ASCII 字符集(仅包含 256 个字符)、ISO 8859-1 字符集等(包含所有西方拉丁字母),广义的 Unicode 既包含了字符集,也包含了编码规则,比如 UTF-8、UTF-16、UTF8MB4、GBK 等。

因此 UTF-8 是 Unicode 字符集的实现方式之一,它会将 Unicode 字符以某种方式进行编码。

在具体实现时,UTF-8 是一种变长的编码规则,从 1~4 个字节不等,比如英文字符是 1 个字节,中文字符是 3 个字节。通过 UTF-8 编码的 Unicode 字符以最大长度 4 个字节作为单个字符固定占据的内存空间,在 Go 语言中可以通过 unicode/utf8 包进行 UTF-8 和 Unicode 之间的转换。

所以如果从 Unicode 字符集的视角看,字符串的每个字符都是一个字符的独立单元,但如果从 UTF-8 编码的视角看,一个字符可能是由多个字节编码而来的。

我们通过 len 函数获取到的是字符串的字节长度,再据此通过字符数组的方式遍历字符串时,是以 UTF-8 编码的角度切入的;而当我们通过 range 关键字遍历字符串时,又是从 Unicode 字符集的角度切入的,如此一来就得到了不同的结果。

出于简化语言的考虑,Go 语言的多数 API 都假设字符串为 UTF-8 编码。

将 Unicode 编码转化为可打印字符

如果你想要将 Unicode 字符编码转化为对应的字符,可以使用 string 函数进行转化:

func TestIterChar(t *testing.T) {
   var str string
   str = "你好,中国"
   for i, v := range str {
      fmt.Println(i, string(v))
   }
}

UTF-8 编码不能这样转化,英文字符没问题,因为一个英文字符就是一个字节,中文字符则会乱码,因为一个中文字符编码需要三个字节,转化单个字节会出现乱码。

0

评论