【RUST学习日记】第12课 切片引用(Slice)

简介: 【RUST学习日记】第12课 切片引用(Slice)

0x00 回顾与开篇


上一篇文章介绍了向量的知识,简单介绍了向量的基础用法,数组和向量的区别等,这节再了解Rust中另一种数据类型——切片(Slice)。它跟数组和向量又存在一定的关系,那么它们之间又有什么区别呢?这节课给你答案~


PS:本节课可能会涉及到一些关于“引用”,“内存”等概念,如果你是编程初学者,可以先了解下或者略过本节课,等后面讲解了内存,引用,指针的概念后再回顾本节课。


0x01 切片的定义


什么是切片?官方文档中此类型叫做Slice。翻译成中文(如下图)名词的意思是片,薄片,动词的意思是裁,切,切片。大多数的书都翻译为切片。通过意思,大致可以猜出是从某数据上切割下来的一部分数据。


0a2653c851af460fa595bd959398a8f1.png


官方定义: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],如下图所示:


2d65d23f6d4748949b924e4057485923.png


当这两行代码运行时,Rust会自动把引用&Vec<i32>&[i32;5]转换为直接指向数据切片的引用。一起来看下切片引用在内存中是如何表现的:


6de278e6d6694ce5bb08e7e842b7e74b.png


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 小结


本节简单介绍了切片,数组,向量在内存中的表现形式,着重介绍了切片的使用。如果你没有编程语言基础,那么看到这里可能会有一点儿懵,本人建议初学者可以暂时跳过本节课,之所以将切片放到这里,也算是数组和向量的一个补充知识吧。可以等后面了解了内存栈、堆、引用、借用、指针等概念再回过头来看下本节课,到那时也许就不会觉得很难理解了。

相关文章
|
9天前
|
存储 Rust 网络协议
【Rust学习】10_定义枚举
在这一章我们学习 枚举(enumerations),也被称作 enums。枚举允许你通过列举可能的 成员(variants) 来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 Option,它代表一个值要么是某个值要么什么都不是。然后会讲到在 match 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后,我们将学习 if let 结构,另一个简洁方便处理代码中枚举的结构。
25 7
|
30天前
|
Rust 算法 安全
学习Rust
【10月更文挑战第13天】学习Rust
52 8
|
1月前
|
Rust 安全 算法
Rust的学习
【10月更文挑战第12天】Rust的学习
27 2
|
1月前
|
Rust 算法 安全
如何学习Rust编程?
【10月更文挑战第12天】如何学习Rust编程?
41 1
|
1月前
|
Rust API
【Rust学习】09_方法语法
结构体让你可以创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。在 impl 块中,你可以定义与你的类型相关联的函数,而方法是一种相关联的函数,允许您指定结构体的实例具有的行为。 但是结构体并不是创建自定义类型的唯一方式:让我们转向 Rust 的 enum 功能,将另一个工具添加到你的工具箱中。
14 0
|
2月前
|
Rust 索引
【Rust学习】08_使用结构体代码示例
为了了解我们何时可能想要使用结构体,让我们编写一个计算长方形面积的程序。我们将从使用单个变量开始,然后重构程序,直到我们改用结构体。
92 2
|
2月前
|
存储 Rust 编译器
【Rust学习】07_结构体说明
**struct**或 ***structure***是一种自定义数据类型,允许您命名和包装多个相关的值,从而形成一个有意义的组合。如果您熟悉面向对象的语言,那么**struct**就像对象中的数据属性。在本章中,我们将比较和对比元组与结构体,在您已经知道的基础上,来演示结构体是对数据进行分组的更好方法。
24 1
|
2月前
|
Rust 安全 索引
30天拿下Rust之切片
在Rust中,切片是一种非常重要的引用类型。它允许你安全地引用一段连续内存中的数据,而不需要拥有这些数据的所有权。切片不包含分配的内存空间,它仅仅是一个指向数据开始位置和长度的数据结构。切片是对数组的一个连续引用,它提供了一种方便、高效的方式来操作数组的一部分。切片本身并不拥有数据,它只是原始数组的一个视图,因此创建切片通常是一个低开销的操作。
20 1
|
3月前
|
存储 Rust 安全
【Rust学习】06_切片
所有权、借用和切片的概念确保了 Rust 程序在编译时的内存安全。Rust 语言提供了跟其他系统编程语言相同的方式来控制你使用的内存,但拥有数据所有者在离开作用域后自动清除其数据的功能意味着你无须额外编写和调试相关的控制代码。
25 1
|
2月前
|
Rust Linux Go
Rust/Go语言学习
Rust/Go语言学习