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]
转换为直接指向数据切片的引用。一起来看下切片引用在内存中是如何表现的:
image
从上图很清楚的可以看到切片在内存中由两部分组成且在栈上保存,切片的第一部分保存着指向切片的第一个元素的指针,第二部分保存着切片中元素的个数。包括上节课讲的向量是由三部分组成的,其内存中的表现形式就是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 小结
本节简单介绍了切片,数组,向量在内存中的表现形式,着重介绍了切片的使用。如果你没有编程语言基础,那么看到这里可能会有一点儿懵,本人建议初学者可以暂时跳过本节课,之所以将切片放到这里,也算是数组和向量的一个补充知识吧。可以等后面了解了内存栈、堆、引用、借用、指针等概念再回过头来看下本节课,到那时也许就不会觉得很难理解了。