Rust的Slice切片
Slice: 切片。slice 允许你 引用集合中一段连续的元素序列,而 不用引用整个集合。slice 是一类引用,所以它没有所有权。
Slice的引入
编写一个函数,该函数接收一个用空格分隔单词的字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,所以应该返回整个字符串。
我们尝试不适用Slice,来完成这个函数,看看会有什么问题:
fn main() {
let mut str=String::from("abcdef Hello ");
let num=second_world(&str);
//获得一个索引
println!("{}",num);
}
fn first_world(string:&String)->usize
{
//其实x就是索引值,elem就是这个字符所对应的ASCII码
let str=string.as_bytes();
for (x,&elem) in str.iter().enumerate()
{
if elem==b' ' //b' '的ASCII码为32 ,判断每个字符是否为32,如果遇到了空格,则退出
{
return x;
}
}
string.len()
}
解释:
- 我们使用引用作为函数的参数,返回一个索引下标
- as_bytes:将string转换为字节,需要用 as_bytes 方法将 String 转化为字节数组
- 使用 iter 方法在字节数组上创建一个迭代器
- 只需知道 iter 方法返回集合中的每一个元素,而 enumerate 包装了 iter 的结果,将这些元素作为元组的一部分来返回。enumerate 返回的元组中,第一个元素是索引,第二个元素是集合中元素的引用。这比我们自己计算索引要方便一些。
编译运行:
第一个空格出现的位置是6,即,我们可以通过循环遍历0-6个元素,这样就找到了我们需要的第一个空格前的单词。
但是,当我们使用clear释放String字符串时:
str.clear();
函数返回的下标仍然存在num,但是字符串str已经不复存在了,因为它被clear了,是一个空字符串。num和str分属于不同的领域,因为 num与 str 状态完全没有联系,所以 num仍然包含值 6。可以尝试用值 6 来提取变量 str 的第一个单词,不过这是有 bug 的,因为在我们将 6 保存到 str 之后 s 的内容已经改变了。
但是这时候,编译器不会出错,但是,我们已经无法访问了字符串了。
使用Slice
看下面的代码:
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
}
不同于String的引用,hello是一部分string的引用,它由[start_index…end_index]组成,start_index是切片的起始位置,end_index是切片的终点位置(不包括这个终点位置),切片的长度等于终点位置减去起始位置。
可知。&str表示一部分String的切片。
当我们使用切片求解得到第一个空格前的单词:
fn main() {
let mut str=String::from("abcdef Hello ");
let num=second_world(&str);
//str.clear();
println!("{}",num);
}
fn second_world(string:&String)->&str
{
let len=string.as_bytes();
for (i,&elem) in len.iter().enumerate()
{
if elem==b' '
{
//到达空格,返回在这以前的单词
return &string[..i];
}
}
//没有找到
&string[..] //原地返回
}
注意:
- 函数的返回值: 字符串 slice”的类型声明写作
&str
- 我们返回一个字符串 slice,它使用字符串的开始和空格的索引作为开始和结束的索引。
编译运行:我们就得到了切片的字符串
如果,我们像上面那样,清除str:
fn main() {
let mut str=String::from("abcdef Hello ");
let num=second_world(&str);
str.clear();
println!("{}",num);
}
那么就会发现,编译器会直接给我们报错:
说的是什么意思?
大概是说,我们的函数返回后得到一个切片,切片是一个不可变引用&str,赋予了num,然后我们使用clear时,可以看到,clear其实获取了一个可变的引用,上一节我们讲到不可变引用与可变引用不能同时存在,尤其是clear的可变引用还在生命周期内,我们在下面的println!函数中调用了打印num,Rust 不允许 clear 中的可变引用和 word 中的不可变引用同时存在,因此编译失败。
所以我们就知道了我们在此后不能打印字符串了,Rust 不仅使得我们的 API 简单易用,也在编译时就消除了一整类的错误!
其他Slice
Slice字符串字面值
现在知道 slice 了,我们就可以正确地理解字符串字面值了:
let s = "Hello, world!";
这里 s 的类型是 &str:它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;&str 是一个不可变引用。
Slice作为函数参数
fn second_world(string:&str)->&str
相对于我们上面编写的&String版本的函数参数,此版本的&str更受我们的欢迎。因为函数参数是一个切片,我们就可以指定传入的参数的大小:(注意:如果参数是String类型,则无法切片)。
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[..]
}
fn main() {
let my_string = String::from("hello world");
// `first_word` 适用于 `String`(的 slice),整体或全部
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` 也适用于 `String` 的引用,
// 这等价于整个 `String` 的 slice
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` 适用于字符串字面值,整体或全部
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// 因为字符串字面值已经 **是** 字符串 slice 了,
// 这也是适用的,无需 slice 语法!
let word = first_word(my_string_literal);
}
注意:字符串字面值已经是Slice了,无需使用&符号,而函数参数直接对应&str切片类型。
获取数组切片
#![allow(unused)]
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
}
这个 slice 的类型是 &[i32]。它跟字符串 slice 的工作方式一样,通过存储第一个集合元素的引用和一个集合总长度。
下期预告:结构体
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209714.html