Rust学习总结之所有权(三)
slice是没有所有权的数据类型,slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。
一:字符串slice
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{} {}",hello,world);
}
运行结果:
变量hello和world都是对s的数据部分进行部分引用,数据只有一份,如下图所示。
可以使用一个由中括号中的 [starting_index..ending_index]
指定的 range 创建一个 slice,其中 starting_index
是 slice 的第一个位置,ending_index
则是 slice 最后一个位置的后一个值。在其内部,slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 ending_index
减去 starting_index
的值。所以对于 let world = &s[6..11];
的情况,world
将是一个包含指向 s
索引 6 的指针和长度值 5 的 slice。
对于 Rust 的 ..
range 语法,如果想要从索引 0 开始,可以不写两个点号之前的值。换句话说,如下两个语句是相同的:
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];
依此类推,如果 slice 包含 String
的最后一个字节,也可以舍弃尾部的数字。这意味着如下也是相同的
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];
也可以同时舍弃这两个值来获取整个字符串的 slice。所以如下亦是相同的:
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];
下面我们用字符串slice来实现从一个英文语句中找到第一个单词
fn main() {
let s = String::from("hello world");
let first_word = first_word(&s);
println!("first_word = {}",first_word);
}
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);
s.clear(); //right
}
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[..]
}
回忆一下借用规则,当拥有某值的不可变引用时,就不能再获取一个可变引用。因为 clear
需要清空 String
,它尝试获取一个可变引用。在调用 clear
之后的 println!
使用了 word
中的引用,所以这个不可变的引用在此时必须仍然有效。Rust 不允许 clear
中的可变引用和 word
中的不可变引用同时存在,因此编译失败。Rust 不仅使得我们的 API 简单易用,也在编译时就消除了一整类的错误!
二:字符串字面量slice
let s = "Hello, world!";
这里 s
的类型是 &str
:它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面量是不可变的;&str
是一个不可变引用。
对于上面定义的first_word函数
fn first_word(s: &String) -> &str {
可以修改成下面的定义,它使得可以对 String
值和 &str
值使用相同的函数,兼容性更强
fn first_word(s: &str) -> &str {
fn main() {
let my_string = String::from("hello world");
// `first_word` 接受 `String` 的切片,无论是部分还是全部
let word = first_word(&my_string[0..6]);
println!("1-word = {}", word);
let word = first_word(&my_string[..]);
println!("2-word = {}", word);
// `first_word` 也接受 `String` 的引用,
// 这等同于 `String` 的全部切片
let word = first_word(&my_string);
println!("3-word = {}", word);
let my_string_literal = "hello world";
// `first_word` 接受字符串字面量的切片,无论是部分还是全部
let word = first_word(&my_string_literal[0..6]);
println!("4-word = {}", word);
let word = first_word(&my_string_literal[..]);
println!("5-word = {}", word);
// 因为字符串字面值**就是**字符串 slice,
// 这样写也可以,即不使用 slice 语法!
let word = first_word(my_string_literal);
println!("6-word = {}", word);
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
运行结果:
三:数组slice
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
这个 slice 的类型是 &[i32]
。它跟字符串 slice 的工作方式一样,通过存储第一个集合元素的引用和一个集合总长度