RUST 学习日记 第12课 ——切片(Slice)
0x00 回顾与开篇
上节课讲解了向量的知识,简单介绍了向量的基础用法,数组和向量的区别等,这节再了解Rust中另一种数据类型——切片(Slice)。它跟数组和向量又存在一定的关系,那么它们之间又有什么区别呢?这节课给你答案~
PS:本节课可能会涉及到一些关于“引用”,“内存”等概念,如果你是编程初学者,可以先了解下或者略过本节课,等后面讲解了内存,引用,指针的概念后再回顾本节课。
0x01 切片的定义
什么是切片?官方文档中此类型叫做Slice。翻译成中文(如下图)名词的意思是片,薄片,动词的意思是裁,切,切片。大多数的书都翻译为切片。通过意思,大致可以猜出是从某数据上切割下来的一部分数据。
官方定义:Slice是对向量或者数组中部分元素序列的引用。其签名形式为&[T]和&mut [T],分别叫做T类型的共享切片和T类型的可修改切片。通俗易懂点说,Slice就是表示数组或者向量的一个范围。Slice从严格意义上讲,应该叫做对切片的引用。由于提到切片大都是指对它的引用,所以习惯上就把“切片引用”省略为“切片”了。
示例代码如下:
let vec = vec![1, 3, 5, 7, 9];
let array = [0, 2, 4, 6, 8];
let vec_slice: &[i32] = &vec;
let array_slice: &[i32] = &array;
dbg!(vec_slice);
dbg!(array_slice);
代码执行结果:
[src\main.rs:8] vec_slice = [
1,
3,
5,
7,
9,
]
[src\main.rs:9] array_slice = [
0,
2,
4,
6,
8,
]
0x02 切片在内存中的表现形式
看了上面的代码,你会发现,只要在数组和向量名称前面添加&,就可以变成切片引用。如果你使用CLion,且声明切片引用时不指定类型,你会发现vec_slice
会推断为&Vec<i32>
,array_slice
会推断为&[i32;5]
,如下图所示:
当这两行代码运行时,Rust会自动把引用&Vec<i32>
和&[i32;5]
转换为直接指向数据切片的引用。一起来看下切片引用在内存中是如何表现的:
从上图很清楚的可以看到切片在内存中由两部分组成且在栈上保存,切片的第一部分保存着指向切片的第一个元素的指针,第二部分保存着切片中元素的个数。包括上节课讲的向量是由三部分组成的,其内存中的表现形式就是vec
变量。当然数组也是保存在栈上的。另外,切片的引用是一个胖指针(Fat Pointer)。关于胖指针的概念后面章节会介绍,这里了解即可。
0x03 在切片中使用范围
在切片中可以使用范围来切割数组或者向量,签名为:&数组或向量名称[范围]。切片中的范围分为以下3种:
1、前闭后开。形如:[a..b]
。表示从a到b的范围,包含a不包含b。
2、0到指定位置。形如:[..b]
。表示从0到b的位置,不包含b。
3、指定位置到结束。形如:[a..]
。表示从a到结束,包含a。
4、全部。形如:[..]
。表示从0到结束的全部。等同于直接引用。
示例代码如下:
let vec = vec![1, 3, 5, 7, 9];
let array = [0, 2, 4, 6, 8];
let vec1 = &vec[1..3];
let vec2 = &vec[..2];
let vec3 = &vec[3..];
let vec4 = &vec[..];
println!("vec1 => vec中下标1到下标3的 元素 {:#?}", vec1);
println!("vec2 => vec中下标0到下标2的 元素 {:#?}", vec2);
println!("vec3 => vec中下标3到结束的 元素 {:#?}", vec3);
println!("vec4 => vec中下标0到结束的 元素 {:#?}", vec4);
// 相同
assert_eq!(&vec[..], &vec);
let array1 = &array[1..3];
let array2 = &array[..2];
let array3 = &array[3..];
let array4 = &array[..];
println!("array1 => array中下标1到下标3的 元素 {:#?}", array1);
println!("array2 => array中下标0到下标2的 元素 {:#?}", array2);
println!("array3 => array中下标3到结束的 元素 {:#?}", array3);
println!("array4 => array中下标3到结束的 元素 {:#?}", array4);
// 相同
assert_eq!(&array[..], &array);
代码运行结果:
vec1 => vec中下标1到下标3的 元素 [
3,
5,
]
vec2 => vec中下标0到下标2的 元素 [
1,
3,
]
vec3 => vec中下标3到结束的 元素 [
7,
9,
]
array1 => array中下标1到下标3的 元素 [
2,
4,
]
array2 => array中下标0到下标2的 元素 [
0,
2,
]
array3 => array中下标3到结束的 元素 [
6,
8,
]
assert_eq!
也是一个宏,这是一个断言,断言参数里面的两个值相等。如果不相等则会抛出错误。
PS:如果在使用范围时超出了数组或者向量的长度,则会发生越界的错误。
0x03 切片元素的访问
切片的访问与数组和向量类似,同样也会坚持切片的索引是否有效,如果超出长度则会出现错误。
示例代码如下:
let vec = [1, 3, 5, 7, 9];
let vec_s = &vec[1..4];
dbg!(vec_s[2]);
代码运行结果:
[src\main.rs:47] vec_s[2] = 7
0x04 切片元素的修改
想要修改切片中的元素,切片引用的原数组或者向量必须是可变的,也就是必须使用mut
关键字修饰,且切片类型为可修改切片&mut [T]。切片的值一旦被修改,切片引用的原数组或者向量的值同样被修改。因为切片指向的值和原数组或者向量的值是同一块内存区域(可以参考0x02中的内存图)。
示例代码如下:
// 必须mut修饰
let mut vec = [1, 3, 5, 7, 9];
// 声明为可修改的切片
let vec_m = &mut vec[1..4];
vec_m[2] = 10;
dbg!(vec_m);
dbg!(vec);
代码运行结果如下:
[src\main.rs:56] vec_m = [
3,
5,
10,
]
[src\main.rs:57] vec = [
1,
3,
5,
10,
9,
]
0x05 切片的常用方法
1、len()
——获取切片的长度。
2、is_empty()
——判断切片是否为空,返回值为布尔型。
示例代码如下:
let vec = [1, 3, 5, 7, 9];
let vec_s = &vec[0..0];
println!("切片 vec_s 的长度是{} ", vec_s.len());
println!("切片 vec_s 是空吗?{} ", vec_s.is_empty());
代码运行结果如下:
切片 vec_s 的长度是0
切片 vec_s 是空吗?true
0x06 小结
本节简单介绍了切片,数组,向量在内存中的表现形式,着重介绍了切片的使用。如果你没有编程语言基础,那么看到这里可能会有一点儿懵,本人建议初学者可以暂时跳过本节课,之所以将切片放到这里,也算是数组和向量的一个补充知识吧。可以等后面了解了内存栈、堆、引用、借用、指针等概念再回过头来看下本节课,到那时也许就不会觉得很难理解了。
0x7 本节源码
013 · StudyRust - 码云 - 开源中国 (gitee.com)
下节预告——字符串。