3.1.1 字符串
字符串一般用来存放可读字符,因为Go源码要求为UTF-8编码,所以一般将字符串按照UTF-8的码点(rune)序列来理解。实际上,字符串对应的是字节序列,存储byte类型的0值也是可以的。此外,还可以在字节内存储GBK编码,当然是多个字节对应一个编码,此处将对应字符串理解为字节序列更为准确。
字符串本质上是不可变的字节序列,for range并不支持非UTF-8编码的遍历,因为程序“不知道”几个字节对应一个字符。再者,len()函数在Go语言中返回的是字符串字节数而非字符个数。
对于字符串,Go语言在reflect.StringHeader的结构体内有如下定义:
type StringHeader struct{ Data uintptr Len int }
结构体的具体内容在3.4节会进行介绍。其中,类型uintptr就是指针,用来存放字符串的地址。
下面几个代码示例,可对字符串的相关操作进行说明:
book/ch03/3.1/strings/main.go
1. package main
2.
3. import "fmt"
4.
5. func main() {
6. s := "hello,world"
7. fmt.Println(len(s))
8. fmt.Println(s[0],s[10])
9. fmt.Println(s[:5])
10. fmt.Println(s[6:])
11. fmt.Println(s[:])
12. }
13.
14. //以下是程序打印输出
15. 11
16. 104 100
17. hello
18. world
19. hello,world
第8行,打印出来的104和100就是“h”和“d”两个字符。
第9行至第11行,是生成一个新串,如果不写开始的下标就默认是0,不写结束的下标就默认是到最后一个字符。如果是s[i:j],则表示左闭右开地取得字符。对应的结果可以看第17行至第19行。
不过,后面形成的新串和原来的字符串是公用内存的,新串只是记录地址和长度,可以参考图3-1加以理解。
s字符串从h开始到d结束,即字符串“hello,world”。而s[:5]则是从h开始到o字母,即字符串“hello”,s[6:]是从w开始取5个字节,即字符串“world”。
这三个字符串在内存的底层是共享的,每个字符串只是记录了地址和字节长度。
介绍完字符串的这些特点,再来介绍UTF-8、Unicode和字节的关系,这些概念是处理字符串的理论基础。
对于最初的计算机字符集,理论上ASCII编码就够用,它有128个字符:英文的大小写字母、数字、各种标点和控制符。不过随着计算机行业的高速发展,全世界有各种语言糅杂在同一个互联网中,而且未来会越来越多。显然,如果有一种字符集能够支持所有的语言,那么在进行程序处理时会更高效,程序员编码也会更为方便,在这种情况下Unicode标准应运而生。
Unicode支持超过一百种的语言和十万字符的码点。
UTF-8是Unicode标准的一种具体实现,其特点是字符长度可变,长度为1到4个字节不等。因为具有这个特性,所以它可以无缝对接ASCII码,如果UTF-8编码的第一个bit位是0,那么长度为一个字节,即只使用第一字节剩下的7位存储字符,这正好能覆盖ASCII码字符集。如果UTF-8编码的前两个bit位是10,则表示长度为两个字节,第二个字节以0开头;对于三个字节的UTF-8码,这三个字节对应的bit位分别是110、10和0。这种方式可以压缩字符存储长度。
因为字符串与字节之间存在这种不确定的长度关系,且有可能出现字节损坏或者非UTF-8编码,所以对于字符串的操作,建议使用Go语言提供的如下几个包。
▪strings:提供搜索、比较、切分与字符串连接等功能。
▪bytes:如果要对字符串的底层字节进行操作,可以使用[]bytes转换类型后进行处理。
▪strconv:主要是字符串与其他类型的转换,比如整数、布尔。
▪unicode:主要是对字符串中的单个字符做判断,比如IsLetter、IsDigit、IsUpper等。
注意
字符串可以强制转换为[]bytes和[]rune这两种类型进行处理。不管是哪种转换,系统都需要付出多分配一块内存的代价。可是如果需要对字符串进行新增字符等操作,转换到[]bytes后可以使用bytes.Buffer的writeRune方法;而转换到[]rune则会多做一些检查,会要求底层尽可能保持一致。