Rust能力养成之(6):集合体与迭代器

图片

前言

上一篇,介绍了Rust语言的

  • 结构体的函数和方法实现

  • 枚举的函数和方法实现

  • 模块简介

这一篇,我们介绍一下Rust中的复杂数据类型

  • 集合体

    • 数组

    • 元组

    • 向量

    • 哈希

    • 切片

  • 迭代器

集合体(Collections)

在编程实践中,显然会遇到处理“组团”数据的情况,这自然需要复杂数据类型来呼应,那么集合体就出现了,Rust提供了多种这样的内建类型。

本节,首先看下数组元组。然后,看下标准库中的动态集合类型,主要介绍向量vectors(列表list)和哈希图hashmaps(键/值,key/value)。最后,引出切片slices,以其进入复杂数据的内部一看究竟。

数组(Arrays)

先从Rust数组开始,与其他语言的数组类似,要求定长和同类型,用[T, N]表示,其中T为任意类型,N为数组中元素的个数,不能是一个变量,建议使用常量usize值

看下这个代码实例:

// arrays.rs
fn main() {     let numbers: [u8; 10] = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11];     let floats = [0.1f64, 0.2, 0.3]; 
    println!("Number: {}", numbers[5]);    println!("Float: {}", floats[2]);}

 

以上代码结果为:

图片

第4,5行分别给出了两种初始化数组的方式,第一种是标准方式,第二种是把类型放在一个元素的位置并加上前缀指定类型,显得很灵活。

 

元组(Tuples)

与数组不同的是,元组中可以存放不同类型的数据,因此是一种异质性的集合体,在为函数传参或者做返回值方面用处颇大

// tuples.rs
fn main() {     let num_and_str: (u8, &str) = (40, "Have a good day!");    println!("{:?}", num_and_str);    let (num, string) = num_and_str;    println!("From tuple: Number: {}, String: {}", num, string);}

以上代码结果为

图片

可见,第4-5行,定义并提取一个元组的值,第6-7行,对元组进行分解赋值。

向量(Vectors)

比起数组,向量的好处,是不必提前定义内容和长度,这是一种与时俱进的类型,显然是动态的,在栈上分配空间,可以通过调用Vec::new构造函数或使用Vec ![]宏进行创建。

好,上代码:​​​​​​​

// vec.rs
fn main() {     let mut numbers_vec: Vec<u8> = Vec::new();     numbers_vec.push(1);     numbers_vec.push(2); 
    let mut vec_with_macro = vec![1];     vec_with_macro.push(2);    let _ = vec_with_macro.pop();    // value ignored with `_`
    let message = if numbers_vec == vec_with_macro {         "They are equal"    } else {         "Nah! They look different to me"    };
    println!("{} {:?} {:?}", message, numbers_vec, vec_with_macro); }

简单分析一下,这里分别用两种方式创建向量:第4行的number_vec 和 第8行的vec_with_macro ;而后利用push()和pop()进行增删数据。

在这里还有诸多用法,

https://doc.rust-lang.org/std/vec/struct.Vec.html

读者可以自己再跑跑。实际上向量也可以利用for 循环来体现其迭代器性质。

上述代码结果如下

图片

哈希(Hashmaps)

Rust不会忘记提供映射格式来存储键值数据的,相关功能来自于标准库std::collections模块,名为HashMap,通过HashMap::new函数来创建。

看下代码:​​​​​​​

// hashmaps.rs
use std::collections::HashMap; 
fn main() {     let mut fruits = HashMap::new();     fruits.insert("apple", 3);    fruits.insert("mango", 6);    fruits.insert("orange", 2);    fruits.insert("avocado", 7);    for (k, v) in &fruits {         println!("I got {} {}", v, k);    }
    fruits.remove("orange");    let old_avocado = fruits["avocado"];    fruits.insert("avocado", old_avocado + 5);    println!("\nI now have {} avocados", fruits["avocado"]);}

我们读一下:

  • 第3行,载入需要的模块std::collections::HashMap

  • 第6行,创建新的哈希图变量,fruits

  • 第7-10行,为该变量赋键值,使用inser方法

  • 第11-13行,打印赋值结果,涉及for循环,这里面是循环变量是元组(k,v),分别对应keys()和values()两种方法,面向引用变量&fruits进行迭代

  • 第15行,删除一个键,一对键值同时删除

  • 第16行,创建一个简单变量old_avocado,取得avocado在fruits中的数值

  • 第17行,为fruits中的avocado插入新值

  • 第18行,打印修改后的fruits["avocado"]数值

上述代码结果如下

图片

一般而言,用于对HashMap类型的键进行散列的算法基于Robin hood开放寻址方案,但可以根据用例和性能使用自定义散列器替换

切片(Slices)

切片是一种”瞥见“集合类型数据内容的通用方法,大多数用例在于获得对集合类型中特定范围项的只读访问。切片基本上是一个指针或引用,指向由其他变量现所拥有的集合类型中的一个连续区间。

在底层,切片是指向堆栈或堆中某处数据的胖指针(fat pointer),这意味着切片,除了包含指向该数据的指针,还拥有指向多少数据的信息。切片用&[T]表示,其中T是类型,用法与数组很相似。

我们看下代码:​​​​​​​

// slices.rs
fn main() {     let mut numbers: [u8; 4] = [1, 2, 3, 4];    {         let all: &[u8] = &numbers[..];        println!("All of them: {:?}", all);    }
    {         let first_two: &mut [u8] = &mut numbers[0..2];        first_two[0] = 100;        first_two[1] = 99;    }
    println!("Look! I can modify through slices: {:?}", numbers);}

我们读一下代码:

  • 第4行,创建一个可变绑定类型的数组变量 numbers

  • 第6行,创建&[u8]类型的切片,并使用&numbers指向数组number

    • [..]意味着全部引用该数组的数据

    • 之所以使用&是由于切片不能把数组数据拿来,只能引用,根源在于切片是unsized types,而这又是一个后续要详细谈的内容,读者莫急

  • 第11行,创建切片引用该数组的前两个位置

  • 第12-16行,通过切片改变原来数组的值,打印结果,成功

上述代码结果如下

图片

不知道读者是否看到,代码中的第5,8,10,14行中有两组大括号,用来与不可变绑定变量进行区隔,否则不会通过编译的,这一点依然要到后续篇章才能讲清楚。

迭代器(Iterators)

迭代器不是什么新概念,其出现是以一种高效的方式过一下集合体中的元素,很多语言中都有,比如Python的iter(some_list) 和C++的 vector.begin() 。其优势体现在

  • 提供一种优雅高级的遍历集合体数据的方式,而不用手写for循环了

  • 迭代器不会直接读取全部数据,而是采取懒惰(lazy)的方式,体现在

    • 如果只需要一个数据,那么就只访问该条数据

    • 还可以与多个转换操作(multiple transformation operations)链接在一起,

      • 比如根据条件筛选元素,并且在需要时才对转换进行计算

      • 提供next()方法,为读取下一条做准备

在Rust中,迭代器可以是实现其特性的任何类型,然后可以在for循环中使用此类型遍历其项,并实现在大多数标准库集合类型上,如Vector、HashMap、BTreeMap等等,也可以实现在自定义的类型上。

处理Rust中的集合类型时,迭代器是常用器械。实际上,Rust的for循环就被退化并隐藏为一个常规的包含next方法的match表达式,调用遍历对象。此外,可以通过调用iter()或into_iter()将大多数集合类型转换为迭代器。

后边篇章还要结合新的知识点来进一步介绍迭代器的内容,本篇先到这里,很庆幸吧,这一节没有代码。

结语

本篇讲过了集合体和迭代器,里面蕴含了一些尚未揭开的谜团,请读者耐心一下,后面都会一一说明。

下一篇会先讲一下Rust中的项目管理

主要参考和建议读者进一步阅读的文献
https://doc.rust-lang.org/book

1.Rust编程之道,2019, 张汉东

2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger

3.Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger

4.Beginning Rust ,2018,Carlo Milanesi

5.Rust Cookbook,2017,Vigneshwer Dhinakaran

猜你喜欢

转载自blog.csdn.net/qq_40433634/article/details/113073429
今日推荐