Update ch08-02-strings.md

This commit is contained in:
Gary Wang 2018-07-01 19:50:02 +08:00
parent bee0a9135f
commit 100ebe5d85

View File

@ -1,24 +1,24 @@
## 字符串存储 UTF-8 编码的文本 ## 使用字符串存储 UTF-8 编码的文本
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md) > [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md)
> <br> > <br>
> commit c2fd7b2d39c4130dd17bb99c101ac94af83d1a44 > commit d0e83220e083ef87880e6a04f030b90c9af9385b
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。 第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。
字符串出现在集合章节的原因是,字符串是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引` String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。 在集合章节中讨论字符串的原因是,字符串是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引` String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。
### 什么是字符串? ### 什么是字符串?
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:`str`,字符串 slice它通常以被借用的形式出现`&str`。第四章讲到了 **字符串 slice**:它们是一些储存在别处的 UTF-8 编码字符串数据的引用。比如字符串字面值被储存在程序的二进制输出中,字符串 slice 也是如此。 在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:`str`,字符串 slice它通常以被借用的形式出现`&str`。第四章讲到了 **字符串 slice**:它们是一些储存在别处的 UTF-8 编码字符串数据的引用。比如字符串字面值被储存在程序的二进制输出中,字符串 slice 也是如此。
称作 `String` 的类型是由标准库提供的而没有写进核心语言部分它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 和字符串 slice `&str`类型,而不仅仅是其中之一。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slice 都是 UTF-8 编码的。 称作 `String` 的类型是由标准库提供的而没有写进核心语言部分它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 和字符串 slice `&str` 类型,而不仅仅是其中之一。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slice 都是 UTF-8 编码的。
Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。`*String`/`*Str` 的命名类似,它们通常也提供有所有权和可借用的变体,就比如说 `String`/`&str`。这些字符串类型在储存的编码或内存表现形式上可能有所不同。本章将不会讨论其他这些字符串类型;查看 API 文档来更多的了解如何使用它们以及各自适合的场景。 Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。看到这些由 `String` 或是 `Str` 结尾的名字了吗?这对应着它们提供的所有权和可借用的字符串变体,就像是你之前看到的 `String``str`。举例而言,这些字符串类型能够以不同的编码或内存表现形式上以不同的形式存储文本内容。本章将不会讨论其他这些字符串类型,查看 API 文档来更多的了解如何使用它们以及各自适合的场景。
### 新建字符串 ### 新建字符串
很多 `Vec` 可用的操作在 `String` 中同样可用,从以 `new` 函数创建字符串开始,如示例 8-11 所示 很多 `Vec` 可用的操作在 `String` 中同样可用,从以 `new` 函数创建字符串开始,如示例 8-11 所示
```rust ```rust
let mut s = String::new(); let mut s = String::new();
@ -28,7 +28,7 @@ let mut s = String::new();
这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。 这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。
通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,字符串字面值就可以。示例 8-12 展示了两个例子: 通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,字符串字面值也实现了它。示例 8-12 展示了两个例子。
```rust ```rust
let data = "initial contents"; let data = "initial contents";
@ -43,7 +43,7 @@ let s = "initial contents".to_string();
这些代码会创建包含 `initial contents` 的字符串。 这些代码会创建包含 `initial contents` 的字符串。
也可以使用 `String::from` 函数来从字符串字面值创建 `String`。示例 8-13 中的代码代码等同于使用 `to_string` 也可以使用 `String::from` 函数来从字符串字面值创建 `String`。示例 8-13 中的代码代码等同于使用 `to_string`
```rust ```rust
let s = String::from("initial contents"); let s = String::from("initial contents");
@ -53,7 +53,7 @@ let s = String::from("initial contents");
因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。它们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。 因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。它们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示 记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示
```rust ```rust
let hello = String::from("السلام عليكم"); let hello = String::from("السلام عليكم");
@ -79,7 +79,7 @@ let hello = String::from("Hola");
#### 使用 push 附加字符串 #### 使用 push 附加字符串
可以通过 `push_str` 方法来附加字符串 slice从而使 `String` 变长,如示例 8-15 所示 可以通过 `push_str` 方法来附加字符串 slice从而使 `String` 变长,如示例 8-15 所示
```rust ```rust
let mut s = String::from("foo"); let mut s = String::from("foo");
@ -88,12 +88,12 @@ s.push_str("bar");
<span class="caption">示例 8-15使用 `push_str` 方法向 `String` 附加字符串 slice</span> <span class="caption">示例 8-15使用 `push_str` 方法向 `String` 附加字符串 slice</span>
执行这两行代码之后 `s` 将会包含 `foobar`。`push_str` 方法获取字符串 slice因为我们并不需要获取参数的所有权。例如示例 8-16 展示了如果将 `s2` 的内容附加到 `s1` 中后自身不能被使用就糟糕了 执行这两行代码之后 `s` 将会包含 `foobar`。`push_str` 方法获取字符串 slice因为我们并不需要获取参数的所有权。例如示例 8-16 展示了如果将 `s2` 的内容附加到 `s1` 中后自身不能被使用就糟糕了
```rust ```rust
let mut s1 = String::from("foo"); let mut s1 = String::from("foo");
let s2 = "bar"; let s2 = "bar";
s1.push_str(&s2); s1.push_str(s2);
println!("s2 is {}", s2); println!("s2 is {}", s2);
``` ```
@ -101,7 +101,7 @@ println!("s2 is {}", s2);
如果 `push_str` 方法获取了 `s2` 的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作! 如果 `push_str` 方法获取了 `s2` 的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作!
`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 l 加入 `String` 的代码 `push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 l 加入 `String` 的代码
```rust ```rust
let mut s = String::from("lo"); let mut s = String::from("lo");
@ -114,12 +114,12 @@ s.push('l');
#### 使用 + 运算符或 `format!` 宏连接字符串 #### 使用 + 运算符或 `format!` 宏连接字符串
通常我们希望将两个已知的字符串合并在一起。一种办法是像这样使用 `+` 运算符,如示例 8-18 所示 通常你会希望将两个已知的字符串合并在一起。一种办法是像这样使用 `+` 运算符,如示例 8-18 所示
```rust ```rust
let s1 = String::from("Hello, "); let s1 = String::from("Hello, ");
let s2 = String::from("world!"); let s2 = String::from("world!");
let s3 = s1 + &s2; // Note that s1 has been moved here and can no longer be used let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
``` ```
<span class="caption">示例 8-18使用 `+` 运算符将两个 `String` 值合并到一个新的 `String` 值中</span> <span class="caption">示例 8-18使用 `+` 运算符将两个 `String` 值合并到一个新的 `String` 值中</span>
@ -162,7 +162,7 @@ let s = format!("{}-{}-{}", s1, s2, s3);
### 索引字符串 ### 索引字符串
在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果我们尝试使用索引语法访问 `String` 的一部分,会出现一个错误。考虑一下如示例 8-19 中所示的无效代码 在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果尝试使用索引语法访问 `String` 的一部分,会出现一个错误。考虑一下如示例 8-19 中所示的无效代码
```rust,ignore ```rust,ignore
let s1 = String::from("hello"); let s1 = String::from("hello");
@ -193,7 +193,7 @@ error[E0277]: the trait bound `std::string::String: std::ops::Index<{integer}>`
let len = String::from("Hola").len(); let len = String::from("Hola").len();
``` ```
在这里,`len` 的值是,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢? 在这里,`len` 的值是 4 ,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是阿拉伯数字 3
```rust ```rust
let len = String::from("Здравствуйте").len(); let len = String::from("Здравствуйте").len();
@ -206,13 +206,13 @@ let hello = "Здравствуйте";
let answer = &hello[0]; let answer = &hello[0];
``` ```
`answer` 的值应该是什么呢?它应该是第一个字符 `З` 吗?当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。返回字节值可能不是人们希望看到的,即便是只有拉丁字母时:`&"hello"[0]` 会返回 `104` 而不是 `h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的发生。 `answer` 的值应该是什么呢?它应该是第一个字符 `З` 吗?当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即便这个字符串只有拉丁字母: 即便 `&"hello"[0]` 是返回字节值的有效代码,它也应当返回 `104` 而不是 `h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的发生。
#### 字节、标量值和字形簇!天呐! #### 字节、标量值和字形簇!天呐!
这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节、标量值和字形簇(最接近人们眼中 **字母** 的概念)。 这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节、标量值和字形簇(最接近人们眼中 **字母** 的概念)。
比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 `Vec` 中的 `u8` 值看起来像这样: 比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 `u8` 值看起来像这样:
```text ```text
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
@ -237,7 +237,7 @@ Rust 提供了多种不同的方式来解释计算机储存的原始字符串数
### 字符串 slice ### 字符串 slice
索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此如果你真的希望使用索引创建字符串 slice 时 Rust 会要求你更明确一些。为了更明确索引并表你需要一个字符串 slice相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice 索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此如果你真的希望使用索引创建字符串 slice 时 Rust 会要求你更明确一些。为了更明确索引并表你需要一个字符串 slice相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice
```rust ```rust
let hello = "Здравствуйте"; let hello = "Здравствуйте";
@ -253,13 +253,13 @@ let s = &hello[0..4];
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/libcore/str/mod.rs:2188:4 thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/libcore/str/mod.rs:2188:4
``` ```
你应该小心谨慎的使用这个操作,因为可能会使你的程序崩溃。 你应该小心谨慎的使用这个操作,因为这么做可能会使你的程序崩溃。
### 遍历字符串的方法 ### 遍历字符串的方法
幸运的是,这里还有其他获取字符串元素的方式。 幸运的是,这里还有其他获取字符串元素的方式。
如果我们需要操作单独的 Unicode 标量值,最好的选择是使用 `chars` 方法。对 “नमस्ते” 调用 `chars` 方法会将其分开并返回六个 `char` 类型的值,接着就可以遍历其结果来访问每一个元素了: 如果需要操作单独的 Unicode 标量值,最好的选择是使用 `chars` 方法。对 “नमस्ते” 调用 `chars` 方法会将其分开并返回六个 `char` 类型的值,接着就可以遍历其结果来访问每一个元素了:
```rust ```rust
for c in "नमस्ते".chars() { for c in "नमस्ते".chars() {
@ -286,14 +286,16 @@ for b in "नमस्ते".bytes() {
} }
``` ```
这些代码会打印出组成 `String` 的 18 个字节,开头是这样的 这些代码会打印出组成 `String` 的 18 个字节:
```text ```text
224 224
164 164
168 168
224 224
// ... etc // --snip--
165
135
``` ```
不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。 不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。