字符串的定义与底层结构

字符串(string)是 Go 语言中的基础数据类型,可以看成是由一个个字符组成的数组,但其实 string 底层的数据是使用字节数组存储的。

可以使用双引号 "" 或者反引号 `` (键盘中Tab上面的那个按键)来声明字符串。

 var s string = "DawnSiro"
 var ss string = `Dawn
 Siro`

其中,双引号声明的字符只能声明单行,且如果字符串的内容中包含了双引号的话,需要使用 \ 来进行转义。

 json := "{"id":1}"

而反引号声明的字符串就可以声明多行,而且包含双引号的情况下无需转移,在需要手写JSON字符串的时候还是比较好用的。

 json := `{
         "id" : 1
     }`
"hello"
reflect.StringHeader
 type StringHeader struct {
     Data uintptr
     Len  int
 }

其中,Data 为指向底层实际存储 string 数据的只读字符数组,Len为数组的长度。

常见操作

获取字符串长度

len()
 s := "DawnSiro"
 fmt.Printf("len(s): %v\n", len(s)) // len(s): 8

注意这里的 Len 并不是指字符的长度,而是数组的长度。

在 Go 语言中使用的是 UTF8 对字符串进行编码。

在 UTF-8 编码中,中文字符通常占用 3 个字节。这是因为中文字符使用了 Unicode 的 3 字节编码范围,即 U+0800 到 U+FFFF 之间的编码。

另外,UTF-8 编码中的每个字符可能会占用不同数量的字节。例如,英文字符通常只需要占用 1 个字节,而某些特殊字符可能需要占用多达 4 个字节。

 ss := "晓希洛"
 fmt.Printf("len(s): %v\n", len(ss)) // len(s): 9

这个例子中的字符串只有三个中文字符,而该字符串的 Len 却是 9 。

访问字符串中的字符

Go语言中的字符串是一个只读的byte类型的切片。

所以可以直接使用类似数组下标访问的方法进行访问

 s := "DawnSiro"
 fmt.Printf("s[0]: %v\n", s[1]) // s[1]: 97

s 的 1 号索引位置的字符是 a,使用 byte 表示为 97 。

当然因为字符串底层是只读数组的缘故,是无法对字符串中的字符进行修改的。

 s[1] = 'A'// cannot assign to s[1] (value of type byte)

另外需要说明的是,字符串的只读仅限于底层数组的数据和长度不能改变,string 类型的指向还是可以改变的。

 s := "Dawn"
 fmt.Printf("s: %v\n", s) // s: Dawn
 s = "Siro" // 改变了 s 的指向
 fmt.Printf("s: %v\n", s) // s: Siro

遍历字符串

字符串可以通过下标直接访问对应位置的字符,我们很自然的可以想到使用 循环 + 遍历索引下标的方式。

 s := "DawnSiro"
 for i := 0; i < len(s); i++ {
     fmt.Printf("%c", s[i])
 }
 // DawnSiro

但是前面也说过,string 中的长度和字符数可能不是对应的,因为有的字符串可能需要多个字节来表示。

 s := "晓希洛"
 for i := 0; i < len(s); i++ {
     fmt.Printf("%c", s[i])
 }

上面的代码就只会打印出乱码了。

那么该怎么遍历带有需要多个字节的字符的字符串呢?

最简洁的方式是使用 for range 循环。

 for i, v := range s {
     fmt.Printf("%v ", i)
     fmt.Printf("%c\n", v)
 }
 /*
 0 晓
 3 希
 6 洛
 */

第一个参数i是该字符的启示下标,第二个参数是表示该字符的符文数 rune。

那么什么是符文数呢?我们看看官方给出的解释。

type rune = int32

rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.

我们简单翻译下第一句话:符文数是int32的别名,在所有方面都与int32等效。

所以符文数其实就是使用一个 int32 来表示字符。

为什么可以这样表示呢?

这是因为UTF-8编码最多占四个字节,使用同为四个字节的 int32 完全能够表示了。

字符串与字节数组的转换

直接使用强转的写法就能进行转换了。

 str := "hello"
 charArr := []byte(str)
 str = string(charArr)

字符串拼接

Go 语言中可以直接使用加号 + 进行字符串之间的拼接。

 s := "a" + "b"
 fmt.Printf("s: %v\n", s)

但需要注意的是,字符串和其他数据类型之间并不能使用 + 直接进行拼接。

 ss := "a" + 1 // invalid operation: "a" + 1 (mismatched types untyped string and untyped int)
fmt.Sprintf()
 s := "a" + "b"
 fmt.Printf("s: %v\n", s)
 s1 := fmt.Sprintf("%s %d", s, 1)
 fmt.Printf("s1: %v\n", s1)

比较和搜索

Go 语言中的字符串可以直接使用 == 进行比较。

另外使用双引号和反引号声明的字符串被认为是同一内容的字符串。

 if "ss" == `ss` {
     fmt.Println("对的")
 } // 对的

参考与鸣谢