@@ -1,22 +1,22 @@
# 切片类型
**The Slice Type **
所谓 * 切片, slices * ,允许咱们引用集合中连续的元素序列。切片属于一种引用,因此他不拥有所有权。
这里有个小的编程问题:编写一个函数,该函数取由空格分隔单词的字符串,并返回其在该字符串中找到的第一个单词。若函数在字符串中未找到空格,则整个字符串必定是个单词,因此整个字符串应被返回。
* 切片, slices * 允许咱们引用某个集合中,连续的元素序列,而不是整个集合。切片属于一种引用,因此他不具有所有权 。
> **注意**:出于介绍切片的目的,我们在这一小节中仅假设 ASCII; 对 UTF-8 处理的更全面讨论,是在第 8 章的 [以字符串存储 UTF-8 编码的文本](../common_collections/strings.md) 小节处 。
这里有一个编程小问题:要编写一个,取个以空格分隔单词的字符串,并返回其在该字符串中,找到的第一个单词的函数。如果该函数在字符串中,未找到空格,那么整个字符串必定是一个单词,所以这整个字符串就应被返回。
我们来看看,在不使用切片的情况下,咱们要如何编写这个函数的签名,以了解切片将解决什么问题:
我们来看看,在不使用切片下咱们要怎样编写这个函数的签名,以了解切片将解决的问题:
``` rust
fn first_word ( s : & String ) -> ?
```
函数 `first_word` 有着一 个 `&String` 的参数。我们不需要所有权,所以这没有问题。 但我们应返回什么呢?我们确实没有描述字符串的 * 一部分 * 的方法。不过,我们可以返回该单词, 以空格表示的结束处 的索引。我们来试试看 ,如下清单 4-7 所示。
函数 `first_word` 有个 `&String` 类型 的参数。我们不需要所有权,所以这很好。(在惯用的 Rust 下,除非需要,否则函数不会取得他们参数的所有权,随着我们继续,其原因将变得清晰。) 但我们应返回什么呢?我们确实没有一种 描述字符串的 * 一部分 * 的方法。不过,我们可以返回以空格表示的单词结尾 的索引。我们来尝试一下 ,如下清单 4-7 中 所示。
<a name="listing_4-7"></a>
文件名:`src/main.rs`
``` rust
@@ -33,10 +33,9 @@ fn first_word(s: &String) -> usize {
}
```
* 清单 4-7: 返回 `& String` 参数中某个 字节索引值的 `first_word` 函数*
** 清单 4-7** :返回 `String` 参数中字节索引值的 `first_word` 函数
因为我们需要逐个元素地遍历这个 `String` ,并检查某个值是否为空格,所以我们将使用 `as_bytes` 方法,将咱们的 `String` 转换为一个字节数组。
因为我们需要逐个元素地遍历 `String` 并检查值是否为空格,所以我们将使用 `as_bytes` 方法将 `String` 转换为字节数组。
``` rust
@@ -44,7 +43,7 @@ fn first_word(s: &String) -> usize {
```
接下来,我们使用 `iter` 方法,在这个 字节数组上, 创建了 一个迭代器:
接下来,我们使用 `iter` 方法在 字节数组上创建一个迭代器:
``` rust
@@ -52,11 +51,11 @@ fn first_word(s: &String) -> usize {
```
我们将在 [第 13 章 ](../functional_features/iterator.md ) 详细讨论迭代器。现在,我们只需知道 `iter` 是个会 返回, 集合中每个元素的方法,而 `enumerate` 会 封装 `iter` 的结果,而 将每个元素作为元组的一部分返回。`enumerate` 返回元组的第一个元素是索引,第二个元素是指向集合 元素的引用。这比我们自己计算索引,要 方便一些 。
我们将在 [第 13 章 ](../functional_features/iterator.md ) 中更 详细地 讨论迭代器。现在只需了解 , `iter` 是个返回集合中每个元素的方法,而 `enumerate` 封装 `iter` 的结果并 将每个元素作为元组的一部分返回。`enumerate` 返回的 元组的第一个元素是索引,第二个元素是指向元素的引用。这比我们自己计算索引更 方便一点 。
因为 `enumerate` 方法返回了 个元组,所以我们可以使用模式, 来解构这个 元组。我们将在 [第 6 章 ](../enums_and_pattern_matching/match_control_flow.md#绑定到值的模式 ) 详细讨论模式。在这个 `for` 循环中,我们指定了个 其中 `i` 表示元组中的索引,`&item` 表示元组中单个字节的模式 。因为我们从 `.iter().enumerate()` 中,得到的是个指向集合 元素的引用,所以我们在模式中, 使用了 `&` 。
因为 `enumerate` 方法返回个元组,所以我们可以使用模式来解构该 元组。我们将在 [第 6 章 ](../enums_and_pattern_matching/match_control_flow.md#绑定到值的模式 ) 中更多地 详细讨论模式。在 `for` 循环中,我们指定了一种模式, 其中 `i` 表示元组中的索引,`&item` 表示元组中单个字节。因为我们得到一个对 `.iter().enumerate()` 中元素的引用,所以我们在模式中使用 `&` 。
在这个 `for` 循环中 ,我们通过使用字节字面值语法, 检索表示空格的字节。如果 我们找到了空格, 就返回其位置。否则,我们便 通过使用 `s.len()` , 返回该 字符串的长度。
在 `for` 循环内 ,我们通过使用字节字面值语法检索表示空格的字节。当 我们找到一个空格时,我们 就返回其位置。否则,我们通过使用 `s.len()` 返回字符串的长度。
``` rust
@@ -69,7 +68,7 @@ fn first_word(s: &String) -> usize {
```
我们现在有了已知 找出字符串中, 第一个单词末尾索引的方法,但有个问题。我们单独返回一个 `usize` ,但他只有在那个 `&String` 的上下文中, 才是个有意义的数字。换句话说,因为他是个独立于那个 `String` 的值,所以不能保证他在将来仍然有效。请看下面清单 4-8 中的那个 程序,他使用了 清单 4-7 中的 `first_word` 函数。
我们现在有一种办法, 找出字符串中第一个单词末尾索引的方法,但有个问题。我们单独返回一个 `usize` ,但他只在 `&String` 的上下文中才是个有意义的数字。换句话说,因为他是个独立于 `String` 的值,所以不能保证他在将来仍然有效。请看下面清单 4-8 中的程序,他使用清单 4-7 中的 `first_word` 函数。
文件名:`src/main.rs`
@@ -82,34 +81,30 @@ fn main() {
s . clear ( ) ; // 这会清空那个 String, 令其等于 ""
// 这里 word 仍有着值 5, 但已没 有咱们可将 值 5
// 有意义地运用的字符串了。 word 现在完全无效!
// word 在这里 仍有着值 5, 但 s 已不再 有咱们可对 值 5
// 有意义地运用的任何内容。因此 word 现在完全无效!
}
```
* 清单 4-8: 存储调用 `first_word` 函数的解构,并随 后修改那个 `String` 的内容 *
* 清单 4-8: 存储调用 `first_word` 函数的结果,然 后修改 `String` 的内容 *
此程序会不带 任何报错地 编译,即使 在我们在调用 `s.clear()` 之 后使用 `word` 也会如此。由于 `word` 完全未与 `s` 的状态联系起来 , `word` 仍然包含值 `5` 。我们本可以使用 值 `5` 于 变量 `s` , 提取出第一个单词,但这将是个错误 ,因为自从我们将 `5` 保存 在 `word` 中后 , `s` 的内容已经发生了变化。
这个程序可在没有 任何报错下 编译,并将 在我们在调用 `s.clear()` 后使用 `word` 如此行事。因为 `word` 完全没有连接 `s` 的状态,所以 `word` 仍然包含值 `5` 。我们本可将 值 `5` 与 变量 `s` 一起使用来 提取出第一个单词,但这将是个 bug ,因为自从我们在 `word` 中保存 `5` 以来 , `s` 的内容已经发生了变化。
要 担心 `word` 中的索引与 `s` 中的数据不同步,既繁琐 又容易出错!如果 我们要编写一个 `second_word` 函数,那么 管理这些索引, 就会变得更加棘手。其签名应该是这样的 :
不得不 担心 `word` 中的索引与 `s` 中的数据不同步,既乏味 又容易出错!若 我们还 要编写 `second_word` 函数,管理这些索引就会更加脆弱。其签名就必须如下 :
``` rust
fn second_word ( s : & String ) -> ( usize , usize ) {
```
现在,我们正在跟踪起始索引 * 和 * 结束索引,而且咱们还有更多的值是根据特定状态下的数据计算得出的,却完全与该状态无关。我们有三个需要保持同步的无关变量在游离。
现在,我们要跟踪起始 * 和 * 终止索引,而且咱们还有更多的值,是根据特定状态下的数据计算得出的,但这些值又与该状态完全无关。我们有三个不相关的变量,需要保持同步 。
幸运的是, Rust 有此问题的解决方法:字符串切片。
幸运的是, Rust 有一种针对这个问题的解决方法:字符串切片 。
## 字符串切片
**String Slices **
所谓 * 字符串切片 * ,是对字符串部分内容的引用,他看起来像这样:
所谓 * 字符串切片 * ,是对 `String` 中连续元素序列的引用,他看起来像这样:
``` rust
@@ -120,21 +115,21 @@ fn second_word(s: &String) -> (usize, usize) {
```
与对整个 `String` 的引用不同,`hello` 是对这个 `String` 中,由 额外的 `[0..5]` 代码,所指定的一部分的引用 。我们通过指明 `[starting_index..ending_index]` , 来 使用括号内的范围创建出 切片,其中 `starting_index` 是切片中的第一个位置,`ending_index` 比切片中最后一个位置多一。在内部,切片这种 数据结构, 存储了切片的起始位置和长度,即 `ending_index` 减去 `starting_index` 。因此,在 `let world = &s[6..11];` 的情况下,`world` 将是个包含着指向 `s` 的索引 `6` 处字节指针,长度值为 `5` 的切片 。
与对整个 `String` 的引用不同,`hello` 是对 `String` 中一部分的引用,在 额外的 `[0..5]` 部分中指定 。我们通过指定 `[starting_index..ending_index]` ,使用括号内的范围创建切片,其中 `starting_index` 是切片中的第一个位置,`ending_index` 比切片中最后一个位置多一个 。在内部,切片数据结构存储了切片的起始位置和长度,对应于 `ending_index` 减去 `starting_index` 。因此,在 `let world = &s[6..11];` 的情况下,`world` 将是个包含着指向 `s` 的索引 `6` 处字节的 指针,与 `5` 的长度值 。
> **译注**:切片应是一种灵巧指针。
下 图 4-6 以图表的形式, 展示了这一 点。
图 4-7 以图示 展示了这点。

* 图 4-6: 指向某个 `String` 的字符串切片 *
* 图 4-6: 指向 `String` 的一部分 的字符串切片 *
使用 Rust 的 `..` 范围语法,如果 咱们打算从索引 `0` 处开始,咱们可以 去掉两个句点前的值。换句话说,下面这 两个值是相等的:
在 Rust 的 `..` 范围语法下,当 咱们打算从索引 `0` 处开始时 ,咱们可去掉两个句点前的值。换句话说,下面两个值是相等的:
``` rust
@@ -145,7 +140,7 @@ fn second_word(s: &String) -> (usize, usize) {
```
对于同一个字符串令牌,如果咱们的片段包括该 `String` 的最后一个字节,则可以去掉两个句点后的尾数 。这意味着下面两个值是相等的:
同样的道理,当咱们的切片包含 `String` 的最后一个字节时,咱们可去掉尾随数字 。这意味着下面两个值是相等的:
``` rust
@@ -157,7 +152,7 @@ fn second_word(s: &String) -> (usize, usize) {
let slice = & s [ 3 .. ] ;
```
要取用整个字符串时,还可以把开始与结束索引都舍弃掉。那么下面的语句就是等价的了 :
咱们还可同时去掉这两个值来取整个字符串的切片。因此,下面两个值是相等的 :
``` rust
let s = String ::from ( " hello " ) ;
@@ -168,10 +163,10 @@ fn second_word(s: &String) -> (usize, usize) {
let slice = & s [ .. ] ;
```
> **注意**:字符串切片的 范围索引, 必须出现在有效的 UTF-8 字符边界处。如果咱们试图在某个 多字节字符的中间创建字符串片段 ,咱们的程序将报错退出。为介绍字符串切片目的,我们在本节中假设仅有 ASCII 编码;有关 UTF-8 处理的更全面讨论,请参阅第 8 章的 [使用字符串存储 UTF-8 编码文本](../common_collections/strings.md) 小节 。
> **注意**:字符串切片范围索引必须出现在有效的 UTF-8 字符边界处。当咱们尝试在 多字节字符的中间创建字符串切片时 ,咱们的程序将以错误退出 。
有了 这些信息,我们来将 `first_word` 重写 为返回一个切片。表示 “字符串切片” 的类型,被写作 `&str` :
考虑到所 有这些信息,我们来重写 `first_word` 为返回一个切片。表示 “字符串切片” 的类型写为 `&str` :
文件名:`src/main.rs`
@@ -191,9 +186,9 @@ fn first_word(s: &String) -> &str {
```
我们以与清单 4-7 中, 相同的方式获取到该 单词结尾的索引,即查找第一次出现的空格。当我们找到一个空格时,我们使用该字符串的开头,与这个 空格的索引,作为开始和结束索引, 返回一个字符串切片。
我们以与清单 4-7 中相同的方式获取单词结尾的索引,即查找第一次出现的空格。当我们找到一个空格时,我们使用该字符串的开头与 空格的索引,作为开始和结束索引返回一个字符串切片。
现在,当我们调用 `first_word` 时,就 会返回一个与所采用数据相关的值。该值由到切片起点的引用,和切片中元素的数量组成。
现在,当我们调用 `first_word` 时,我们 会返回一个与所采用数据相关的值。该值由到切片起点的引用,和切片中元素的数量组成。
对于 `second_word` 函数来说,返回切片也是可行的: