[奔跑的 Go] 教程六、Go 语言的字串数据类型 String

Golang

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 类型。

stringfor 循环

如果你在 for 循环中使用 [**range**](https://gobyexample.com/range)range 会返回 rune 和 每个字符的 字节索引

https://play.golang.org/p/Xet2cJbywLH

在上面的程序中,没有输出索引 5 ,因为它是 õ 字符的第二个 编码单元。如果你不需要输出索引值,可以使用 _ (空白标识符)。

rune 是什么

字符串其实是 bytes 的切片类型,就是那么简单。当我们使用 forrange 进行循环,我们得到的是 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

由于字符 bint32 值大于字符 a,因此表达式 'b' > 'a' 的值为真。再来看个例子。

https://play.golang.org/p/aw8Sv8Vto-c

既然我们已经知道了,在语言底层,字符的数据类型就是 int32,那么我们就可以对它们做各种操作。例如,可以以两个字符的值为区间,进行 for 循环。

https://play.golang.org/p/kS4vxuSSmWg

以上,是对 Go 字符串类型的基本介绍,strings 还提供了很多很实用的功能,可以用来对 string 做各种操作,比如 joinreplacesearch 等等。 strings 包 是 Go 标准库的一部分。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/rungo/string-data-typ...

译文地址:https://learnku.com/go/t/27741

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 1

跟其他语言都不一样啊

file

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!