在 Go 语言中,字符串作为一个内置的类型,被广泛得使用,但是,虽然被用得多,你知道的未必多。在使用字符串的过程中,确实有很多不确定点可能会让代码的编写有一些被动的情况,例如在字符串和其他类型之间转换,如何传递字符串,如何比较字符串和如何切割字符串比较高效等,这些都是需要去考虑的问题。所以,我想一次性将这些问题都解决掉,因此在这篇文章中你都能看到这些问题的解答,以及更多的其他关于 Go 语言中字符串的知识。
string 的内部结构
在 Go 语言的官方说明中,强调了 Go 语言的 string 的定义是:a string in effect a read-only slice of bytes,也就是说从内部的实现来说,string 的定义应该是这样的:
也就是说其实 Go 的实现是封装了一种方式,让我们将一个字符串 literal 转换为 byte 数组,然后记录下字符串的长度,所以我们可以用一下的方式来简单验证一波:
这里介绍得比较简单,但是,我们对 string 的内部结构有了一个简单的了解,后面的东西可能就比较好接受了,所以继续;
关于 string 的一些总结
前面只是简单得分析了一下 string 的组成,下面接着说一些在 Go 里面,对于 string 的概念:
- 虽然 string 的结构是 slice ,但是 string 的类型在 Go 语言里面是常量,也就是一旦创建,不能修改里面的值(不代表不能修改一个 string 变量的值,关系类同常量指针);
- Go 支持两种类型的定义方式,分别是双引号(\"\")和 反引号(``),双引号就是普通的字符串,但是反引号会保留一定的格式;
- string 类型的默认值或者说零值是 "" 或者 ``;
- string 类型可以通过
+
和+=
操作连接; - string 类型可以通过 "==" 或者 "!=" 进行内容比较,但是后面会说到一个小坑;
- string 本身没有任何方法可以调用,一些通用的操作(例如字符串分割,高效的合并等)可以通过
strings
这个 package 中的辅助函数进行; - 可以通过数组的索引下标方式获取某个字符(str[0])或者一段子串(str[0:10]);
字符串和字符
前面说了,字符串是 byte 的组合,为啥是 byte 而不是字符呢?这就涉及到计算机里面关于字符的定义了,在通用意义上,所谓的字符是一个完整的可打印的表达,例如对于英文单词:"hi",我们通常会认为这是由两个字符:'h' 和 'i' 组成;对于中文词组:"我们",通常也会认为是由两个字符:'我' 和 '们' 组成。但是,对于计算机来说,却不是这样的,英文单词因为历史的缘故(ASCII),大多数表示法中都和我们的通用认识是一致的,"hi" 是由两个字符组成,但是,对于 "我们" 这个词组,各种表示法有不同的看法,不过有一点是基本一致的,那就是 "我们" 不能用 2 个 byte 表示出来,所以很可能我们通用意义上的两个字符的 "我们" 在计算机中是 4-6 个 byte,那么对于有中文也有英文的场景,我们要如何定义 '字符' 的概念呢?
这里 Go 给出了自己的答案,在 Go 中,不去关注你用什么表示法,我只管用 byte 来存储数据,至于你通用意义上的字符,Go 抽象出了一个 rune
的类型,这是一个 int32
的 alias,如果你需要区分通用概念上的字符时,可以使用它,下面我就给一个例子来表达一下 Go 的处理方式:
这里有几个地方是需要说的:
- Go 里面的单引号
''
的作用就是定义rune
的常量; - Line 7 是非法的,你通不过编译,因为前面说了字符串是
[]byte
,所以strB[0]
的类型是byte
不能和rune
对比; - Line 8 展示了如果你需要通用意义上的字符的操作,需要转换成
[]rune
; - Line 9 和 Line 10 对比可以发现,这个
'⌘'
占了 3 个 byte 的大小。
字符串和其他类型的转换
可能在实际使用过程中,用的比较多的字符串转换是和数字类型的转换,这里就展示两种不同类型的转换,其他类型应该都大同小异,这里只是抛个砖:
可以发现这里其实还是比较简单的,但是有个不爽的地方是可以发现在 Line 5 和 Line 7 都忽略了 error,所以对于一些场景,可以尝试自己封装一个 MustAtoi
,这样更舒服一些。