5分钟速读之Rust权威指南(六)

562 阅读3分钟

切片

上一节中提到了所有权概念,所有权只能归属于一个变量,而使用引用可以在不获取或有权的情况下方便地获取变量的值。除了引用,Rust还有另外一种不持有所有权的数据类型:切片(slice)。切片允许我们引用集合中某一段连续的元素序列,而不是整个集合。

字符串切片

字符串切片是指向String对象中某个连续部分的引用,创建一个字符串:

let s = String::from("hello world");

创建一个字符串切片,从索引0开始到索引5结束,但不包括5:

let s1 = &s[0..5]; // hello

// 更简单的写法,与上面等价
let s2 = &s[..5] // hello

创建从索引6开始到索引11的切片:

let s3 = &s[6..11]; // world

// 更简单的写法,与上面等价
let s4 = &s[6..] // world

创建完整的切片:

let s5 = &str[..]; // hello world

可以尝试编写一个函数,利用切片获取一句英文的首单词:

fn first_word(s: &String) -> &str /* 字符串的切片类型 */ {
  // 将字符串转为字节,例如单个字符a会转为97
  let bytes = s.as_bytes();

  // 我们通过句子中的第一个空格来判断首单词
  // 这里创建一个空格的字面量字节,等同于32
  let blank = b' ';

  // 通过iter()创建迭代器,再使用enumerate()将迭代成员附带索引
  // 每次迭代得到一个元组,包含索引和对应成员的引用
  for (i, &item) in bytes.iter().enumerate() {
    // 如果当前这个字节是空格的话
    // 那么这个空格之前的字母就是首单词
    if item == blank {
      // 利用切片截取首单词并返回
      return &s[0..i];
    }
  }
  // 没有找到空格,说明传入参数本身就是一个单词
  // 可以直接返回完整的切片
  return &s[..];
}

下面来使用这个函数:

let s = String::from("hello world");
let word = first_word(&s);
println!("{}", word);
// hello

当字符串是一个完整的单词时:

let s = String::from("hello");
let word = first_word(&s);
println!("{}", word);
// hello

字符串字面量就是切片

其实在我们使用字面量的方式定义字符串时,创建的就是一个字符串切片&str,由于&str是一个不可变的引用,所以字符串字面量自然是不可变的:

let s = "hello world";
// 显式标注类型
let s: &str = "hello world";

将字符串切片作为参数

注意一下上边的first_word的参数要求是&String,所以当我们使用切片&str作为参数时,将会报错:

let s = "hello world";
first_word(&s) // error,预期&String,却得到了&str

为了函数更好的兼容性,可以使用切片类型来兼容两种字符串:

// 将参数改为切片类型&str
fn first_word(s: &str) -> &str {
  /* 省略 */
}
first_word("hello world"); // hello
first_word(&String::from("hello world")); // hello

其他类型切片

除了字符串以外,数组也支持数组切片,下面创建一个数组:

let a = [1, 2, 3, 4, 5];

从开始到索引2的切片:

let a1 = &a[..2];
// [1, 2]

从索引2到结束的切片:

let a2 = &a[2..];
// [3, 4, 5]

从索引1到索引3的切片:

let a3 = &a[1..3];
// [2, 3]

切片的存储

切片数据结构在内部存储了指向起始位置的引用和一个描述切片长度的字段,这个描述切片长度的字段等价于结束索引减去开始索引:

let s = String::from("hello world");
let world = &s[6..] // world

下图展示了字符串s和切片world的在内存中的存储方式,s指向了索引0的字符h,长度是11,容量是11;world指向了索引6的字符w,长度是5,由于world只是s的一段切片引用,所有不存在容量属性:

Untitled.png