|
|
@ -1,6 +1,6 @@
|
|
|
|
# 字符串
|
|
|
|
# 字符串
|
|
|
|
|
|
|
|
|
|
|
|
在其他语言,字符串往往是送分题,因为实在是太简单了,例如 `"hello, world"` 就是字符串章节的几乎全部内容了,对吧?如果你带着这样的想法来学 Rust,我保证,绝对会栽跟头,**因此这一章大家一定要重视,仔细阅读,这里有很多其它 Rust 书籍中没有的内容**。
|
|
|
|
在其他语言,字符串往往是送分题,因为实在是太简单了,例如 `"hello, world"` 就是字符串章节的几乎全部内容了,但是如果你带着同样的想法来学 Rust,我保证,绝对会栽跟头,**因此这一章大家一定要重视,仔细阅读,这里有很多其它 Rust 书籍中没有的内容**。
|
|
|
|
|
|
|
|
|
|
|
|
首先来看段很简单的代码:
|
|
|
|
首先来看段很简单的代码:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
@ -103,6 +103,9 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
println!("the first word is: {}", word);
|
|
|
|
println!("the first word is: {}", word);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn first_word(s: &String) -> &str {
|
|
|
|
|
|
|
|
&s[..1]
|
|
|
|
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
编译器报错如下:
|
|
|
|
编译器报错如下:
|
|
|
|
```console
|
|
|
|
```console
|
|
|
@ -152,7 +155,7 @@ let s: &str = "Hello, world!";
|
|
|
|
|
|
|
|
|
|
|
|
## 什么是字符串?
|
|
|
|
## 什么是字符串?
|
|
|
|
|
|
|
|
|
|
|
|
顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF8 编码,也就是字符所占的字节数是变化的(1 - 4)**,这样有助于大幅降低字符串所占用的内存空间。
|
|
|
|
顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)**,这样有助于大幅降低字符串所占用的内存空间。
|
|
|
|
|
|
|
|
|
|
|
|
Rust 在语言级别,只有一种字符串类型: `str`,它通常是以引用类型出现 `&str`,也就是上文提到的字符串切片。虽然语言级别只有上述的 `str` 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 `String` 类型。
|
|
|
|
Rust 在语言级别,只有一种字符串类型: `str`,它通常是以引用类型出现 `&str`,也就是上文提到的字符串切片。虽然语言级别只有上述的 `str` 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 `String` 类型。
|
|
|
|
|
|
|
|
|
|
|
@ -282,7 +285,7 @@ let hello = String::from("中国人");
|
|
|
|
|
|
|
|
|
|
|
|
所以,可以看出来 Rust 提供了不同的字符串展现方式,这样程序可以挑选自己想要的方式去使用,而无需去管字符串从人类语言角度看长什么样。
|
|
|
|
所以,可以看出来 Rust 提供了不同的字符串展现方式,这样程序可以挑选自己想要的方式去使用,而无需去管字符串从人类语言角度看长什么样。
|
|
|
|
|
|
|
|
|
|
|
|
还有一个原因导致了 Rust 不允许去索引字符:因为索引操作,我们总是期望它的性能表现是 O(1),然而对于 `String` 类型来说,无法保证这一点,因为 Rust 可能需要从 0 开始去遍历字符串来定位合法的字符。
|
|
|
|
还有一个原因导致了 Rust 不允许去索引字符串:因为索引操作,我们总是期望它的性能表现是 O(1),然而对于 `String` 类型来说,无法保证这一点,因为 Rust 可能需要从 0 开始去遍历字符串来定位合法的字符。
|
|
|
|
|
|
|
|
|
|
|
|
## 字符串切片
|
|
|
|
## 字符串切片
|
|
|
|
前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 UTF8 编码,因此你无法保证索引的字节刚好落在字符的边界上,例如:
|
|
|
|
前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 UTF8 编码,因此你无法保证索引的字节刚好落在字符的边界上,例如:
|
|
|
@ -349,13 +352,13 @@ for b in "中国人".bytes() {
|
|
|
|
## 字符串深度剖析
|
|
|
|
## 字符串深度剖析
|
|
|
|
那么问题来了,为啥 `String` 可变,而字符串字面值 `str` 却不可以?
|
|
|
|
那么问题来了,为啥 `String` 可变,而字符串字面值 `str` 却不可以?
|
|
|
|
|
|
|
|
|
|
|
|
就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行得过程中动态生成的。
|
|
|
|
就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串字面值的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行得过程中动态生成的。
|
|
|
|
|
|
|
|
|
|
|
|
对于 `String` 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的:
|
|
|
|
对于 `String` 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的:
|
|
|
|
- 首先向操作系统请求内存来存放 `String` 对象
|
|
|
|
- 首先向操作系统请求内存来存放 `String` 对象
|
|
|
|
- 在使用完成后,将内存释放,归还给操作系统
|
|
|
|
- 在使用完成后,将内存释放,归还给操作系统
|
|
|
|
|
|
|
|
|
|
|
|
其中第一个由 `String::from` 完成,它创建了一个全新的 `String`。
|
|
|
|
其中第一部分由 `String::from` 完成,它创建了一个全新的 `String`。
|
|
|
|
|
|
|
|
|
|
|
|
重点来了,到了第二部分,就是百家齐放的环节,在有**垃圾回收 GC** 的语言中,GC 来负责标记并清除这些不再使用的内存对象,这个过程都是自动完成,无需开发者关心,非常简单好用;但是在无 GC 的语言中,需要开发者手动去释放这些内存对象,就像创建对象需要通过编写代码来完成一样,未能正确释放对象造成的后果简直不可估量。
|
|
|
|
重点来了,到了第二部分,就是百家齐放的环节,在有**垃圾回收 GC** 的语言中,GC 来负责标记并清除这些不再使用的内存对象,这个过程都是自动完成,无需开发者关心,非常简单好用;但是在无 GC 的语言中,需要开发者手动去释放这些内存对象,就像创建对象需要通过编写代码来完成一样,未能正确释放对象造成的后果简直不可估量。
|
|
|
|
|
|
|
|
|
|
|
|