|
|
|
@ -14,7 +14,7 @@
|
|
|
|
|
|
|
|
|
|
称作 `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文档。
|
|
|
|
|
|
|
|
|
|
### 新建字符串
|
|
|
|
|
|
|
|
|
@ -49,7 +49,7 @@ let s = String::from("initial contents");
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 8-13:使用 `String::from` 函数从字符串字面值创建 `String`</span>
|
|
|
|
|
|
|
|
|
|
因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。它们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
|
|
|
|
|
因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。其中一些可能看起来多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
|
|
|
|
|
|
|
|
|
|
记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示。
|
|
|
|
|
|
|
|
|
@ -73,7 +73,7 @@ let hello = String::from("Hola");
|
|
|
|
|
|
|
|
|
|
### 更新字符串
|
|
|
|
|
|
|
|
|
|
`String` 的大小可以增长其内容也可以改变,就像可以放入更多数据来改变 `Vec` 的内容一样。另外,可以方便的使用 `+` 运算符或 `format!` 宏来拼接 `String` 值。
|
|
|
|
|
`String` 的大小可以增加,其内容也可以改变,就像可以放入更多数据来改变 `Vec` 的内容一样。另外,可以方便的使用 `+` 运算符或 `format!` 宏来拼接 `String` 值。
|
|
|
|
|
|
|
|
|
|
#### 使用 `push_str` 和 `push` 附加字符串
|
|
|
|
|
|
|
|
|
@ -86,7 +86,7 @@ s.push_str("bar");
|
|
|
|
|
|
|
|
|
|
<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
|
|
|
|
|
let mut s1 = String::from("foo");
|
|
|
|
@ -122,7 +122,7 @@ let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 8-18:使用 `+` 运算符将两个 `String` 值合并到一个新的 `String` 值中</span>
|
|
|
|
|
|
|
|
|
|
执行完这些代码之后字符串 `s3` 将会包含 `Hello, world!`。`s1` 在相加后不再有效的原因,和使用 `s2` 的引用的原因与使用 `+` 运算符时调用的方法签名有关,这个函数签名看起来像这样:
|
|
|
|
|
执行完这些代码之后,字符串 `s3` 将会包含 `Hello, world!`。`s1` 在相加后不再有效的原因,和使用 `s2` 的引用的原因,与使用 `+` 运算符时调用的函数签名有关。`+` 运算符使用了 `add` 函数,这个函数签名看起来像这样:
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
|
fn add(self, s: &str) -> String {
|
|
|
|
@ -134,7 +134,7 @@ fn add(self, s: &str) -> String {
|
|
|
|
|
|
|
|
|
|
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **解引用强制多态**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论解引用强制多态。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。
|
|
|
|
|
|
|
|
|
|
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝不过实际上并没有:这个实现比拷贝要更高效。
|
|
|
|
|
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。
|
|
|
|
|
|
|
|
|
|
如果想要级联多个字符串,`+` 的行为就显得笨重了:
|
|
|
|
|
|
|
|
|
@ -204,7 +204,7 @@ let hello = "Здравствуйте";
|
|
|
|
|
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 根本不会编译这些代码,并在开发过程中及早杜绝了误会的发生。
|
|
|
|
|
|
|
|
|
|
#### 字节、标量值和字形簇!天呐!
|
|
|
|
|
|
|
|
|
@ -230,11 +230,11 @@ let answer = &hello[0];
|
|
|
|
|
|
|
|
|
|
Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据,这样程序就可以选择它需要的表现方式,而无所谓是何种人类语言。
|
|
|
|
|
|
|
|
|
|
最后一个 Rust 不允许使用索引获取 `String` 字符的原因是索引操作预期总是需要常数时间 (O(1))。但是对于 `String` 不可能保证这样的性能,因为 Rust 不得不检查从字符串的开头到索引位置的内容来确定这里有多少有效的字符。
|
|
|
|
|
最后一个 Rust 不允许使用索引获取 `String` 字符的原因是,索引操作预期总是需要常数时间 (O(1))。但是对于 `String` 不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符。
|
|
|
|
|
|
|
|
|
|
### 字符串 slice
|
|
|
|
|
|
|
|
|
|
索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时 Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice:
|
|
|
|
|
索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
let hello = "Здравствуйте";
|
|
|
|
|