哪些集合不能使用迭代器_Rust能力养成之(6):集合体与迭代器

147ed7b267273ae16899ffa3d27b1df2.png

前言

上一篇,介绍了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]);
}

以上代码结果为

0d687ba457b9600db5c331d772ae19f8.png

第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);
}

以上代码结果为

81bf48d8a0e4634143ba60e04c22a05a.png

可见,第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()进行增删数据。

在这里还有诸多用法,读者可以自己再跑跑。

std::vec::Vec - Rust​doc.rust-lang.org

实际上向量也可以利用for 循环来体现其迭代器性质。

上述代码结果如下

14e31017cad4dbb226d9f93704b43221.png

哈希(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"]数值

上述代码结果如下

75fa9d854461e745851143fc3265a128.png

一般而言,用于对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行,通过切片改变原来数组的值,打印结果,成功

上述代码结果如下

351480bd3e2d1caf2583802db3dadaab.png

不知道读者是否看到,代码中的第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中的项目管理。

主要参考和建议读者进一步阅读的文献

The Rust Programming Language​doc.rust-lang.org

Rust编程之道,2019, 张汉东

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

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

Beginning Rust ,2018,Carlo Milanesi

Rust Cookbook,2017,Vigneshwer Dhinakaran

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值