• Rust开发——切片(slice)类型


    1、什么是切片

    Rust 中,切片(slice)是一种基本类型和序列类型。在 Rust 官方文档中,切片被定义为“对连续序列的动态大小视图”。

    但在rust的Github 源码中切片被定义如下:

    切片是对一块内存的视图,表示为指针和长度。

    其实这个定义更有帮助。从这里的定义可以知道,切片是一个“宽指针”(fat pointer)。所以基本上,当创建一个数组的切片时,切片包含以下内容:

    • 指向数组中切片起始元素地址的指针
    • 描述切片长度的值

    2、切片示例

    在 Rust 中,切片可以是对支持的数组的视图,也可以是对其他序列(例如向量或字符串)的视图。如果切片是对字符串的视图,它被称为字符串切片或字符串字面量,并且通常以其借用形式 &str 出现。

    以下是一个示例数组和该数组生成的两个切片:
    在这里插入图片描述
    左边和右边展示了对中间显示的数组提供视图的两个切片。数组和切片的定义如下:

    let array: [i32; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    let slice1 = &array[5..10];
    let slice2 = &array[3..7];
    
    • 1
    • 2
    • 3

    从上图可以看到,在 slice1 中,切片的指针指向数组的索引 5。slice1 的长度为 5。这意味着切片将包含数组中的 5 个元素。下面是切片相关的索引和值。切片本身的索引从 0 到 4。

    右侧是 slice2。该切片的指针指向元素 3,且切片的长度为 4。

    3、切片常规操作

    定义一个数组,然后对数组进行切片操作:

    let array: [i32; 7] = [0, 1, 2, 3, 4, 5, 6];
    
    let slice = &array[..]; // [ 0, 1, 2, 3, 4, 5, 6 ]
    let slice = &array[0..3]; // [ 0, 1, 2 ]
    let slice = &array[..3]; // [ 0, 1, 2 ]
    let slice = &array[2..4]; // [ 2, 3 ]
    let slice = &array[2..]; // [ 2, 3, 4, 5, 6 ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面定义了不可变数组以及创建数组切片的几种方法。在切片定义后的注释中展示了 dbg!(slice); 的输出结果。

    之后再创建一个可变的切片:

    let mut array: [i32; 7] = [0, 1, 2, 3, 4, 5, 6];
    let array_slice = &mut array[0..5]; // [ 0, 1, 2, 3, 4 ]
    
    • 1
    • 2

    在这里插入图片描述
    检查切片的长度并迭代索引/值:

    slice.len(); // 5
    
    for (index, item) in slice.iter().enumerate() {
        println!("index: {:?} element {:?}", index, item);
    }
    /*
    index: 0 element 0
    index: 1 element 1
    index: 2 element 2
    index: 3 element 3
    index: 4 element 4
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    从切片中检索一个值:

    slice[1]; // 1
    
    • 1

    切片的长度在编译时并不总是已知的。如果访问超出边界的索引值,编译器将不会保存:

    slice[100];
    
    • 1

    会报如下错误:

    thread ‘main’ panicked at ‘index out of bounds: the len is 5 but the index is 100’

    为了安全地从切片中获取值,可以使用 get() 方法:

    slice.get(2); // Some(2)
    slice.get(100); // None
    
    • 1
    • 2

    在切片中查找值:

    slice.iter().position(|v| v == &120); // None
    slice.iter().position(|v| v == &4); // Some(4)
    
    • 1
    • 2

    改变切片中元素的值:

    slice[0] = 100;
    dbg!(slice); // [100, 1, 2, 3, 4]
    dbg!(array); // [100, 1, 2, 3, 4, 5, 6]
    
    • 1
    • 2
    • 3

    4.对不同类型的切片进行操作

    可以从数组、向量和字符串中获取切片:

    let array: [i32; 4] = [0, 1, 2, 3];
    let array_slice = &array[..2]; // [0, 1]
    let vector = vec![1, 2, 3, 4];
    let vector_slice = &vector[0..2]; // [1, 2]
    let string = String::from("string slice");
    let string_slice = &string[0..6]; // "string"
    println!("{:?} {:?} {:?}", array_slice, vector_slice, string_slice);
    // [0, 1] [1, 2] "string"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    之前定义的数组和向量包含 i32 类型,之后可以创建一个同时适用于 vector_slicearray_slice 的函数:

    fn return_second(n: &[i32]) {
        println!("{}", n[1]);
    }
    return_second(array_slice); // 1
    return_second(vector_slice); // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5

    字符串切片是一个 &str,因此不能将其传递给 return_second 函数。事实上,字符串切片有点特殊。在 Rust 中,所有的字符串都是 UTF-8 编码的,因此字符的大小可以不同。iter() 方法不能用在字符串切片上,相反,需要使用 chars() 方法。要从切片中取第 n 个字符,则需要使用索引 n。

    let string = String::from("Rust is 😍");
    let string_slice = &string[..];
    
    fn return_second_char(n: &str) {
        println!("{:?}", n.chars().nth(1));
    }
    
    return_second_char(string_slice); // Some('u')
    
    for c in string_slice.chars() {
        println!("{}", c)
    }
    /*
    R
    u
    s
    t
    
    i
    s
    
    😍
    */
    for (i, c) in string_slice.chars().enumerate() {
        println!("{} {}", i, c)
    }
    /*
    0 R
    1 u
    2 s
    3 t
    4
    5 i
    6 s
    7
    8 😍
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    5.指针

    Rust中的宽指针(fat pointers)与窄指针(thin pointers)是指针类型的两种概念。

    • 窄指针(Thin Pointers):指针仅包含目标内存地址信息,不包含其他附加信息。比如,裸指针 *const T*mut T 就是窄指针,它们只存储指向某个类型 T 的内存地址。

    • 宽指针(Fat Pointers):指针除了存储目标内存地址外,还包含其他信息,例如动态数组的长度。切片 &[T] 或者动态 trait 对象 &dyn Trait 就是宽指针的例子,它们除了指向内存的地址外,还存储着长度等其他信息。

    宽指针包含更多的信息,但也会带来一些额外的存储开销。窄指针更加轻量,但缺乏一些额外的信息。在Rust中,切片是一种宽指针,因为它包含指向数据的指针和数据长度。

    use std::mem;
    let array: [i32; 500] = [0; 500];
    let slice = &array[..];
    let array_pointer = &array;
    let slice_pointer = &slice;
    let start_of_array_slice = &array[0];
    println!("--------------------------------------------");
    println!("array_pointer address: {:p}", array_pointer);
    println!("slice_pointer address: {:p}", slice_pointer);
    println!("start_of_array_slice address: {:p}", start_of_array_slice);
    println!("slice occupies {} bytes", mem::size_of_val(&slice));
    println!(
        "array_pointer occupies {} bytes",
        mem::size_of_val(&array_pointer)
    );
    println!("array occupies {} bytes", mem::size_of_val(&array));
    println!("--------------------------------------------");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    --------------------------------------------
    array_pointer address: 0x9def68
    slice_pointer address: 0x9df738
    start_of_array_slice address: 0x9def68
    slice occupies 16 bytes
    array_pointer occupies 8 bytes
    array occupies 2000 bytes
    --------------------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    数组的总大小为 2000 字节。整个数组的切片(宽指针)占据 16 字节。如果获取数组的指针,得到的是一个占据 8 字节的窄指针。数组指针和切片起始地址的内存地址是相同的。

  • 相关阅读:
    网络层解析——IP协议、地址管理、路由选择
    Kubernetes:(二)了解k8s组件
    七大排序 (9000字详解直接插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序)
    使用Generator处理二叉树的中序遍历
    【编程题】【Scratch二级】2019.09 制作蝙蝠冲关游戏
    【Multiwfn学习】-Multiwfn批量读入xyz结构文件并生成ORCA输入文件
    基于袋獾算法的无人机航迹规划-附代码
    企业实施MES系统的关键点详解
    Java反射获取对象的属性值
    测试/开发程序员的焦虑,感激痛并快乐着......
  • 原文地址:https://blog.csdn.net/matt45m/article/details/126448719