简单谈谈Golang中的字符串与字节数组

2020-01-28 13:55:17王振洲

除了今天的主角字符串之外,另外的配角 byte 也还是需要简单介绍一下的,byte 其实非常好理解,每一个 byte 就是 8 个 bit,相信稍微对编程有所了解的人应该都对这个概念一清二楚,而字节数组也没什么值得介绍的,所以这里就直接跳过了。

字符串在 Go 语言中的接口其实非常简单,每一个字符串在运行时都会使用如下的 StringHeader 结构体去表示,在运行时包的内部其实有一个私有的结构 stringHeader ,它有着完全相同的结构只是用于存储数据的 Data 字段使用了 unsafe.Pointer 类型:


type StringHeader struct {
 Data uintptr
 Len int
}

为什么我们会说字符串其实是一个只读类型的切片 呢,我们可以看一下切片在 Go 语言中的运行时表示:


type SliceHeader struct {
 Data uintptr
 Len int
 Cap int
}

这个表示切片的结构 SliceHeader 和字符串的结构 StringHeader 非常类似,与切片的结构相比,字符串少了一个表示容量的 Cap 字段,这是因为字符串作为只读的类型,我们并不会直接向字符串直接追加元素改变其本身的内存空间,所有追加的操作都是通过拷贝来完成的。

字符串的解析一定是解析器在词法分析 时就完成的,词法分析阶段会对源文件中的字符串进行切片和分组,将原有无意义的字符流转换成 Token 序列,在 Go 语言中,有两种字面量的方式可以声明一个字符串,一种是使用双引号,另一种是使用反引号:


str1 := "this is a string"
str2 := `this is another 
string`

使用双引号声明的字符串其实和其他语言中的字符串没有太多的区别,它只能用于简单、单行的字符串并且如果字符串内部出现双引号时需要使用 符号避免编译器的解析错误,而反引号声明的字符串就可以摆脱单行的限制,因为双引号不再标记字符串的开始和结束,我们可以在字符串内部直接使用 " ,在遇到需要写 JSON 或者其他数据格式的场景下非常方便。

两种不同的声明方式其实也意味着 Go 语言的编译器需要在解析的阶段能够区分并且正确解析这两种不同的字符串格式,解析字符串使用的 scanner 扫描器,它的主要作用就是将输入的字符流转换成 Token 流, stdString 方法就是它用来解析使用双引号包裹的标准字符串:


func (s *scanner) stdString() {
 s.startLit()

 for {
 r := s.getr()
 if r == '"' {
 break
 }
 if r == '' {
 s.escape('"')
 continue
 }
 if r == 'n' {
 s.ungetr()
 s.error("newline in string")
 break
 }
 if r < 0 {
 s.errh(s.line, s.col, "string not terminated")
 break
 }
 }

 s.nlsemi = true
 s.lit = string(s.stopLit())
 s.kind = StringLit
 s.tok = _Literal
}