Go 中的字符串值得特别关注,因为与其他语言相比,Go 中的字符串实现方式是非常不同的。
GO 中字符串定义在双引号 "......" 之间,而不是单引号,与 JavaScript 不同。 Go 中的字符串是 ** UTF-8 ** 默认编码 这在 21 世纪的今天是非常有意义的。 由于 UTF-8 支持 ASCII字符集,因此在大多数情况下您无需担心编码问题。 但要深入了解 UTF-8 编码的工作原理,您一定要访问 这个维基百科页面。
先来写一个简单的程序。使用 string 关键字来定义 string 类型的空变量。关于如何声明变量 可以查看之前的教程。
https://play.golang.org/p/vMDoeaV3RCY
使用 len 函数来查看字符串的长度。len 函数在 Go 运行时是可用的,因此不需要引用。
len 是全局函数,不仅限于字符串类型,它可以查看任何数据类型的长度。在后面的教程中,我们会了解更多 Go 的内置函数。
https://play.golang.org/p/Kqj-TJMFyXP
这里会输出 11,因为 s 一共有 11 个字符,空格也算一个字符。在字符串 Hello World 中,所有的字符都是合法的 ASCII 字符,因此我们希望每个字符只占一个字节(因为 ASCII 字符在 UTF-8 编码中占 8 位 或 *1 个字节)。下面看下对字符串使用 for 循环。
https://play.golang.org/p/cE32NenaYmN
哇!我猜你肯定认为,i 是从 0 开始的对字符串中每个字符的 索引 ,那么 s[i] 应该输出的是字符串 s 中的每个字符。然而,这是什么?好吧,这些是 Hello World 字符串中每个 ASCII/UTF-8 字符的十进制值(对照表看这里 http://www.asciichart.com)。
在 Go 中,一个字符串实际上就是一个只读的字节切片。我们会在后面学习更多关于切片的知识。不过暂时先把 切片 理解为 数组。因此,在上面的例子中,我们所看到的字符串 s 的字节 (uint8) 的值,实际上是一个切片。因此,s[i] 输出的是 每个字符所占字节的十进制值。如果想看到每个字符,你可以在 Printf 语句中使用 %c。你还可以用 %v 查看字节的值,%T 查看值的数据类型。
https://play.golang.org/p/wwqhgHcTeIU
这样你就能够看出,每个字母输所对应的十进制值,这些值以 uint8 为数据类型时,在内存中占 8 位 或 1 个字节。
正如我们所知道的 (维基百科),UTF-8 编码的字符在内存中占 1 - 4 个字节 (与 ASCII 兼容) 。因此,在 Go 中,所有的字符都以 int32 (占 4 个字节) 为数据类型。编码单元 是编码用于单个单元的位数。所以,对于一个 编码单元,UTF-8 占用 8 个字节,UTF-16 占用 16 个字节,这意味着 UTF-8 编码至少需要 8 个位 or 1 个字节 来表示一个字符。
“编码点” 是定义字符的任何数值,根据编码的不同,由一个或多个编码单元表示。由于 UTF-8 与 ASCII 兼容,因此,所有 ASCII 字符都以单字节(8 位)来表示,因此 UTF-8 编码只需要 1 个编码单元。
但是,最大的问题是,如果在 UTF-8 编码中,所有的字符都是 int32 类型,为什么在上面的例子中我们得到的却是 uint8 类型。正如前面所说的,在 Go 中,一个字符串就是一个只读的字节切片。当我们对字符串使用 len 函数时,它会计算切片的长度。当我们使用 for 循环时,它会循环这个切片,一次返回一个字节或一个 编码单元 。因为到目前为止,我们所用到的字符都是在 ASCII 字符集中,因此,从 for 循环得到的都是合法的字符或者说 编码单元实际上是一个编码点。因此,在 Printf 语句中使用 %c 可以输出正确的字符。但是正如我们所知道的,UTF-8 编码的 编码点 或 字符值 可以使用一个或多个字节(最多 4 个字节)来表示,那么如果在 for 循环中使用非 ASCII 字符,会出现什么情况呢?
我们把 Hello 中的 o 换成 õ(拉丁小写字母 O, http://www.utf8-chartable.de)它在 unicode 编码中,用 U+00F5 表示,它由 2 个编码单元(2 个字节)c3 b5 组成(十六进制表示)。因此,这次我们应该会看到由 c3 b5 代表的字符 õ,而不是 6f 代表的字符 o 。
https://play.golang.org/p/rhueGpn4pDc
从上面的结果中,我们得到了 c3 b5 而不是 6f,但是 Hellõ World 的输出效果不好。而且 len(s) 返回的是 12,因为 len 计算的是字符串所占的字节数。当使用 for 循环遍历字符串时,遍历的是每个字节,而不是字符。因此,在 UTF-8 中 c3 (十进制值 195) 表示 Ã,b5(十进制值 181) 代表 µ (check here)。
为了避免上述问题,Go 引入了数据类型 rune(编码点 的同义词),它是 int32 的别名,正如我前面所说的(但尚未证明)Go 以数据类型 int32 表示一个字符(编码点)。
关于为什么 rune 是 int32 而不是 uint32 (因为字符 编码点 的值不能是负数,而 int32 类型的值包含正数和负数) 的答案在 这里。
因此,我们需要把字符串转换为 runes 类型的切片,而不是字节切片。
https://play.golang.org/p/ELgL-upVnz_r
可以通过 类型转换 来将字符串转换为 runes 类型的切片。在上面的输出中,我们看到了 f5 而不是 c3 b5,因为我们遍历的是 rune 类型,而且 õ 在 UTF-8 中的编码点是 f5(因此,unicode 编码点表示 *U+00F5*)或十进制值 245 (check here)。
而且,我们也得到了正确的字符串 s 的长度值 11,因为在切片中有 11 个 runes(或者说是 11 个编码点或 11 个字符)。而且,也证明了 Go 中的一个编码点或一个字符就是 int32 类型。
对 string 的 for 循环
如果你在 for 循环中使用 [**range**](https://gobyexample.com/range),range 会返回 rune 和 每个字符的 字节索引。
https://play.golang.org/p/Xet2cJbywLH
在上面的程序中,没有输出索引 5 ,因为它是 õ 字符的第二个 编码单元。如果你不需要输出索引值,可以使用 _ (空白标识符)。
rune 是什么
字符串其实是 bytes 的切片类型,就是那么简单。当我们使用 for 和 range 进行循环,我们得到的是 rune 类型,因为字符串中的每个字符代表 rune 数据类型。在 Go 中,位于单引号 之间的内容的字面量代表一个字符。因此,单引号(')中包裹的任何合法 UTF-8 字符内容是一个 rune 类型并且它就是 int32 类型。
https://play.golang.org/p/QNBsDunKTrJ
上面的程序将会打印 f5 245 int32 , 它是一个十六进制、十进制位的值,并且 代码段中的 õ 值的数据类型是在 UTF-8 字符集中的。
字符串是不可变的
从字符串的定义可以看出,字符串是 只读的字节分片。 于是当我们尝试替换切片中的字节时,会抛出一个错误。
https://play.golang.org/p/9Uu5LqNqVkb
上面的程序不能通过编译,抛出了一个错误, 由于字符串是只读的字节切片,所以 不能对 s[0] 赋值。
反引号之于字符串
除了双引号,我们还可以使用反引号( ` )来表示 Go 中的字符串。当使用双引号(“)时,换行符,制表符(tab) 等其他特殊字符都会被转义,但是在使用反引号时,不会被转义。如果在反引号字符串中放置换行符,则会直接输出字符 '\ n',详见 https://golang.org/ref/spec#String_literal...
字符串的实际的值就是反引号之间未经编译的( 默认UTF-8编码 )字符;尤其特别的是,在这里反斜杠是没有特殊含义的,字符串还可以包含换行符,但是,回车符( \ r )不会输出。- GoLang 文档
一起来看个小例子
https://play.golang.org/p/9Ir-0Lxx0u3
从图中可以看到,换行符 , 制表符 和 双引号 都原样输出了,但是回车符 \r 则没有输出。
字符之间进行比较
正如前面所讲的, 在 Go 中,字符的数据类型是 rune,而 rune 类型是可以比较大小的,因为它是 unicode 编码 ( int32 类型 )。因此,占用内存多的字符会大于占用内存少的字符。
一起来看个小例子。
https://play.golang.org/p/lxGiJzNeNWO
由于字符 b 的 int32 值大于字符 a,因此表达式 'b' > 'a' 的值为真。再来看个例子。
https://play.golang.org/p/aw8Sv8Vto-c
既然我们已经知道了,在语言底层,字符的数据类型就是 int32,那么我们就可以对它们做各种操作。例如,可以以两个字符的值为区间,进行 for 循环。
https://play.golang.org/p/kS4vxuSSmWg
以上,是对 Go 字符串类型的基本介绍,strings 包 还提供了很多很实用的功能,可以用来对 string 做各种操作,比如 join, replace, search 等等。 strings 包 是 Go 标准库的一部分。
————————————————
原文作者:Summer
转自链接:https://learnku.com/go/t/27741
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。