目录
3. 切片(Slice)类型
3.1 String slice(字符串切片)
3.2 其它切片
3. 切片(Slice)类型
切片可以用来获取一个集合中连续的元素序列,且切片是一种引用类型,因此不具有所有权。
如下是一个小的编程示例:编写一个函数,该函数接受一个字符串,并返回它在该字符串中找到的第一个单词。如果函数在字符串中没有找到空格,则整个字符串必须是一个单词,因此应该返回整个字符串。
在不使用Slice且不获取所有权时,可以使用只读的引用作为形参:
fn first_word(s: &String) -> ?
至于返回值,则可以返回首个单词的索引。如下:
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()
}
iter()返回的迭代器将在第13章节介绍,enumerate()包装成一个具有索引和索引值的可迭代元组,元组首元素是整型索引,次元素是字节值的引用。(i, &item)是一种对元组的解引用方式,第6章节将详细介绍。
上述实现返回首个单词的起始索引位置,usize类型。该实现的问题是,返回值只在String参数中有效,且一旦String参数的变量在调用函数中被改变,该返回值将不再有意义。
3.1 String slice(字符串切片)
语法如下示例:
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
hello是整个字符串变量的一部分,通过[start_index..ending_index]的中括号语法形式来创建一个切片,切片中元素包含start_index,但不包含ending_index指向的元素。
从实现的角度:String切片数据结构中存放了Slice的起始位置,以及长度,原理如下图:
rust的..
形式的范围语法,比较灵活方便:
1)可以省略起始索引
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];
2)可以省略结束索引
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];
3) 可以省略首尾双索引
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];
使用字符串切片重写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[..]
}
从返回值可以看出,字符串切片的标识语法为 &str 。
first_word返回与底层数据结构绑定的切片(引用)类型,编译器会确保到String类型的引用是始终有效的(无论具有所有权的String变量指向的值是被释放或者修改),具有一个只读的切片引用时,原始String变量无法被修改后,或者至少修改后,无法再次通过切片引用进行访问。如下:
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
编译器报错:
# cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` due to previous error
clear()方法需要修改字符串,因此需要可变引用。由于word已经持有原始字符串的不可变引用,clear()的调用在rust中是不允许的,除非后面没有对word的访问。
-
String字符串常量本质是切片
二进制中存放的字符串常量,无法被修改。因此指向(引用)该常量值的的变量是不可变的。由此可知,以下代码:
let s = "Hello, world!";
s的实际类型是&str类型,本质是一个指向二进制特定位置的切片。而切片,是一种不可变的引用类型。
-
String切片作为参数
函数参数如果本身就是字符串常量,完全可以使用切片类型。函数声明从fn second_word(s: &String) -> &str,变为fn second_word(s: &str) -> &str。
这样做有一个好处:后者,既可以接收&String类型,也可以接收&str类型的值。
如果是String切片,可以直接传递给&str进行函数调用。
如果是String类型,既可以传递String的一个切片,也可以传递String的一个引用调用函数。这种灵活性实际使用了rust的“强制解引用”,这将在第15章节介绍。
如下:
fn main() {
let my_string = String::from("hello world");
// `first_word` works on slices of `String`s, whether partial or whole
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` also works on references to `String`s,
// which are equivalent to whole slices of `String`s
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` works on slices of string literals, whether partial or whole
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
3.2 其它切片
其它切片类型类似于String切片类型,整型切片如下:
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
上述切片类型是&[i32],如同String的切片类型,该切片类型也存放到数组首元素的引用,以及切片长度。其它的集合类型的切片,也同样是该类型。
Rust的所有权、借用和切片的概念,使得rust在在编译器确保其应用程序的内存安全性。如同其它编程语言,rust允许编程控制内存使用,且在拥有所有权的变量离开作用域时,自动对数据的所有权进行转移和清理。
“所有权”影响了rust编程的方方面面,后文会进一步涉及到。
关于作者:
犇叔,浙江大学计算机科学与技术专业,研究生毕业,而立有余。先后在华为、阿里巴巴和字节跳动,从事技术研发工作,资深研发专家。主要研究领域包括虚拟化、分布式技术和存储系统(包括CPU与计算、GPU异构计算、分布式块存储、分布式数据库等领域)、高性能RDMA网络协议和数据中心应用、Linux内核等方向。
专业方向爱好:数学、科学技术应用
关注犇叔,期望为您带来更多科研领域的知识和产业应用。
内容坚持原创,坚持干货有料。坚持长期创作,关注犇叔不迷路