slice 类型
slice 类型允许引用集合中一段连续的元素序列,而不用引用整个集合。
slice 是一类引用,所以没有所有权。
fn main() {
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
println!("{}", first_word(&String::from("hello world")));
}
在上面代码,实现了一个函数,该函数接收一个用空格分隔单词的字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,所以应该返回整个字符串。
as_bytes() 函数将 String 转化为字节数组,再通过 iter 方法在字节数组上创建一个迭代器。iter 方法会返回集合中的每一个元素,而 enumerate 方法包装了 iter 的结果,将这些元素作为元组的一部分来返回。enumerate 返回的元组中,第一个元素是索引,第二个元素是集合中元素的引用。在 for 循环中,通过字节的字面值语法来寻找代表空格的字节。
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word 的值为 5
s.clear(); // 这清空了字符串,使其等于 ""
// word 在此处的值仍然是 5,
// 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效!
}
以上代码会造成 word 索引与 s 中的数据不同步,这会造成问题。
如何解决这个问题呢?字符串 slice 可以解决。
字符串 slice
字符串 slice(string slice)是 String 中一部分的值的引用
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{} {}", hello, world)
}
不同于整个 String 的引用,hello 和 world 都是一部分 String 的引用,可以使用一个由中括号的 [start_index, end_index] 指定的 range 创建一个 slice 。
对于字符串的 range 语法来说:
-
如果想要从索引
0开始,可以不写两个点号前面的值let s = &s[0..3]; // 等价于 let s = &s[..3]; -
如果
slice包含String得最后一个字节,也可以不写两个点号后面的值let s = String::from("hello"); let len = s.len(); let slice = &s[3..len]; // 等价于 let slice = &s[3..]; -
也可以同时舍弃前后两个值,来获取整个字符串的
slicelet s = String::from("hello"); let len = s.len(); let slice = &s[0..len]; // 等价于 let slice = &s[..];
字符串 slice 的类型声明是 &str
fn main() {
fn first_world(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
println!("{}", first_world(&String::from("Hello World")));
}
这时当调用了 first_world 函数后,它会返回一个与底层数据相关联的值,这个值由一个 slice 开始位置的引用和 slice 中元素的数量组成。
再回到最开始的问题,如果这时候将 first_world 方法使用 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); // 值为 slice 类型
s.clear(); // 错误!
println!("the first word is: {}", word);
}
上方代码进行编译后,会出现如下错误:
|
113 | let word = first_word(&s);
| -- immutable borrow occurs here
114 |
115 | s.clear(); // 错误!
| ^^^^^^^^^ mutable borrow occurs here
116 |
117 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
word 变量是一个不可变引用,当调用 clear 方法后,变量 s 尝试获取一个可变引用。回顾一下借用原则:不可变引用不能与可变引用共存,也就是说一个值有了不可变引用,就不能再获取一个可变引用。因此导致编译失败!
字符串字面值就是 slice
let s = "Hello World";
这里的变量 s 就是 slice 类型,即 &str 。它是一个指向二进制程序特定位置的 slice 类型,这就是为什么字符串字面值是不可变的,因为 &str 是一个不可变引用。
字符串 slice 作为参数
fn main() {
fn first_world(s: &str) -> &str {
...
}
}
如果参数是一个字符串 slice ,则可以直接传递一个字符串 slice ,也可以传递整个 String 的 slice 或对 String 的引用。这样使得 API 更加通用且不会丢失任何功能。