RUST 学习日记 第12课 ——切片

RUST 学习日记 第12课 ——切片(Slice)


RUST  学习日记 第12课 ——切片

0x00 回顾与开篇

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

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

0x01 切片的定义

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

RUST  学习日记 第12课 ——切片

官方定义: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  学习日记 第12课 ——切片

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

RUST  学习日记 第12课 ——切片

从上图很清楚的可以看到切片在内存中由两部分组成且在栈上保存,切片的第一部分保存着指向切片的第一个元素的指针,第二部分保存着切片中元素的个数。包括上节课讲的向量是由三部分组成的,其内存中的表现形式就是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)

下节预告——字符串。

上一篇:Java/C++实现代理模式---婚介所


下一篇:微信小程序自制底部菜单栏