031-神奇的字符串

无论在哪种语言里,字符串都是一种相当重要的数据类型。很多语言都有字符串的概念,但是又有很大区别。比如 c 语言里,需要使用一个以 '\0' 结尾的字节数组来表示字符串;c++ 里使用 std::string 类型表示字符串;在 java 里是 String. 在这些语言里,字符串并不是一种基础数据类型,而是更加复杂的复合类型,或者『类』类型。

接下来即将讨论的 go 语言里的字符串,它就是一种基础数据类型,和上面那些语言有着相当大的区别。

1. 不可变的字符串

go 里的字符串,是不可变的字节序列(immutable sequence of bytes). 这句话有两层含义:

  • 不可变 (immutable)
  • 字节序列(sequence of bytes)

第二点在后面的第 2 节里说明。这里先看第一点。

在 go 语言里,字符串是基本类型,这一点和 python/nodejs 语言非常像。具体一点,我们说字符串是一个不可分割,不可修改的整体。举 2 个反例:

  • c 语言
char s[] = "abcde";
s[0] = 'f';
  • c++ 语言
std::string s = "abcde";
s[0] = 'f';

在上面的例子中,对字符串进行修改是合法的。然而,这种修改似乎破坏了字符串的整体性。一个字符串,它就像一个完整的人,它的局部不应该被随随便便更改才对。

在 go 语言里,下面的操作是不合法的:

var s string = "abcde"
s[0] = 'f'; // not ok

可能你会着急问,如果要修改怎么办?这个问题解决起来很简单,创建一个新的字符串就行了。在 go 里你可以这么处理:

var s string = "abcde"
var t string = "f" + s[1:] // 创建一个新字符串

2. 遍历字符

刚刚我们看到了,要想访问字符串的『字符』我们使用下标就可以做到。但是还有一点点小问题,稍后再说,先来看一个例子:

var s string = "abcde"

// len 函数是 go 的内置函数,可以计算字符串的长度。在第一章节我们也用过,还记得吗?
for i := 0; i < len(s); i++ {
    fmt.Printf("%c\n", s[i])
}

最后会输出:

a
b
c
d
e

下面来看第二个例子:

var s string = "你好,世界"

// len 函数是 go 的内置函数,可以计算字符串的长度。在第一章节我们也用过,还记得吗?
for i := 0; i < len(s); i++ {
    fmt.Printf("%c\n", s[i])
}

运行后输出:


这里写图片描述
图1 输出字符???

首先这个输出是什么鬼?难道不认识中文吗?还是中文太博大精深了?这里有两点需要注意:

  • 字符串的下标一次只索引一个字节数据
  • 字节 != 字符

在 ascii 码里,字节和字符是一致的,但是到了汉字世界,就不成立了。

先解释一下乱码的原因:在 go 里,源文件使用 utf-8 进行编码。也就是说你写在源文件里的 “你好,世界” 也是 utf-8 编码的。程序运行后,在 go 语言的内存里,这个字节序列保存的也是 utf-8 字节序列(注意这里我提到了字节序列)。而 go 的下标一次只索引一个字节的数据,因此你每次索引的只是 utf-8 字节序列的某一个部分,看到的当然是个乱码。

如何正确遍历字符这个问题放到后面的博客里解决,这里先暂放一放。继续下一个话题。

3. 字符串切片

说到字符串切片,大多数人就想到了 python. 没错,go 里的切片也很强大,强的接近 python……好吧,go 的字符串切片已经相当不错了,至少比 c++ 语言字符串要强大 10000 万倍了是不。

可能有同学还不知道切片是什么意思,其实它就是取子串,即得指定范围的 substring. 举例:

var s string = "Hello, world"

a := s[2:]  // "llo, world"
b := s[:5]  // "Hello"
c := s[2:5] // "llo"

go 的切片没有 Python 强,是因为 Python 还支持使用负数做索引(有点逆天是不)。不过我们的 go 是静态语言,能做到这样,也已经很牛逼了!

接下来,我们再回顾一下『不可变』的含义,它背后隐藏的目的是什么?

  • 可变性带来的缺点

不妨假设字符串 s 是可变的,我们执行 s[2] = 'x' ,会不会导致字符串 a 变成 "xlo world"? 很明显,我们不希望这件事情发生,因为字符串 s 和 a 本来就是不相干的。

如此一来,为了防止字符串 a 和 s 产生关联,我们又需要为 a 重新开辟一块内存空间,将 s[2:]的内容复制到新的空间去。

  • 不可变的优势

好了,『可变』的字符串为切片操作带来了困扰。那如果是『不可变』的呢?这意味着字符串 a 可以继续使用 s 的内存,而不必再开辟新空间!因为字符串 s 本来就是不可变的,所以共享它的内存是安全的。


这里写图片描述
图2 『不可变』字符串的好处是可以共享内存

这个特性会让字符串的复制和切片操作变得非常低廉,因为它没有任何开辟新内存和拷贝字节的代价!

4. 总结

  • 掌握字符串类型

猜你喜欢

转载自blog.csdn.net/q1007729991/article/details/79561554