【Rust】第五节:所有权-3 切片slice

时间:2024-01-26 13:32:21

3.1 为什么要切片slice

还有一个没有所有权的数据类型slice
他为什么叫切片?刚才我们已经了解了“引用”,而切片则允许你引用集合中的一段连续的元素序列,而不用引用整个集合。
举个例子,用一个函数获取字符串中的第一个单词,如果字符串中没有出现空格,则认为整个字符串就是一个单词。

// 部分方法可能暂时看不懂,会在后面的章节展开
fn first_word(s: &String) -> usize { //.. 返回一个独立的usize
    let bytes = s.as_bytes(); // 将字符串转化为字节数组
    for (i, &item) in bytes.iter().enumerate() {
        // 创建了一个iter迭代器,enumerate返回元组
        if item == b' ' {
            return i;
        }
    }
    s.len() // 返回单词结尾的索引
}
fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); // word 的值为 5
    s.clear(); // 这清空了字符串,使其等于 ""
    // word 在此处的值仍然是 5,
}

这种情况下存在一个问题,就是实际的s与获得的word是分离的,也就是s改变了之后,word依然是原来的值,这可能导致事情的管理更加复杂、代码的运行容易出错。
如果再写一个second_word函数,会更容易出错fn second_word(s: &String) -> (usize, usize) {,因为你需要同时跟踪一个开始索引、一个结尾索引,并且都与输入的字符串有关,也就是说有三个分离的变量需要同步维护!

因此,需要使用字符串slice。

3.2 字符串slice

他看起来是这样的,而在具体数据结构上,他其实存储了开始位置与长度,比如 let world = &s[6..11];world将是一个包含指向s索引 6 的指针和长度值 5的slice。

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

    let hello = &s[0..5];
    let world = &s[6..11];
    
    // 可以省略头/尾
    let slice = &s[0..2];
    let slice = &s[..2];
    let slice = &s[3..len];
    let slice = &s[3..];
    let slice = &s[0..len];
    let slice = &s[..];
}

基于slice,改造一下上面的函数

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}
fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);
    s.clear(); // error!
    println!("the first word is: {}", word);
}

这里rust就会正确地报错了,但为什么会报错呢?

还记得上文说的借用吗?
1、s.clear()尝试清除一个不可变引用,这意味着clear在尝试获取一个s的可变引用然后再改变s
2、word使用了&s也就是不可变引用
3、word的作用域一直延伸到了最后一行

这意味着在s.clear()的时候,同时出现了s的可变引用、不可变引用!所以编译器就报错了。

另外,补充一下,现在我们的签名是这样的:
fn first_word(s: &String) -> &str {
而既然能够获取字面量和String,那我们也可以把签名写成这样,这样会更好:
fn first_word(s: &str) -> &str {
当然,在调用的时候,也需要传入一个slice切片。

3.3 其他slice

上文的字符串slice是针对字符串的,但也有通用的slice。

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

你可以对数组进行切片引用,也可以对其他集合使用这类slice,在之后降到vector的章节时会继续展开。