Rust之常用集合(一):向量(vector)
创始人
2024-01-17 20:05:19
0

开发环境

  • Windows 10
  • Rust 1.64.0

 

  •   VS Code 1.72.2 

项目工程

这里继续沿用上次工程rust-demo

常用集合

Rust的标准库包括许多非常有用的数据结构,称为集合。大多数其他数据类型表示一个特定的值,但是集合可以包含多个值。与内置数组和元组类型不同,这些集合指向的数据存储在堆上,这意味着数据量在编译时不需要知道,并且可以随着程序运行而增长或收缩。每一种收藏都有不同的功能和成本,选择一种适合你当前情况的收藏是一项你需要慢慢培养的技能。我们将讨论Rust程序中经常使用的三个集合:

  • vector允许你存储可变数量的相邻值。
  • string是字符的集合。
  • hash map允许您将一个值与一个特定的键相关联。这是一个更一般的数据结构的特殊实现,称为map

用向量存储值列表

我们要看的第一个集合类型是Vec,也称为vector。向量允许您在一个数据结构中存储多个值,该数据结构将所有值在内存中相邻放置。向量只能存储相同类型的值。当您有一个项目列表时,例如文件中的文本行或购物车中的项目价格时,它们非常有用。

创建向量

为了创建一个新的空向量,我们调用Vec::new函数

fn main() {let v: Vec = Vec::new();       // 创建vector
}

注意,我们在这里添加了一个类型注释。因为我们没有在这个向量中插入任何值,所以Rust不知道我们打算存储什么样的元素。这是很重要的一点。向量是使用泛型实现的;现在,我们知道标准库提供的Vec类型可以保存任何类型。当我们创建一个向量来保存一个特定的类型时,我们可以在尖括号中指定类型。在上例中,我们已经告诉Rust,v中的Vec将保存i32类型的元素。

更常见的是,您将创建一个带有初始值的Vec < T >, Rust将推断出您想要存储的值的类型,因此您很少需要做这种类型注释。Rust方便地提供了vec!宏,它将创建一个新的向量来保存您给它的值。下例中创建了一个新的Vec,它包含值1、2和3。整数类型是i32,因为这是默认的整数类型,

fn main() {let v = vec![1, 2, 3];           // 带有初始值的vector
}

因为我们已经给出了初始i32值,Rust可以推断出v的类型是Vec,类型注释是不必要的。接下来,我们将看看如何修改向量。

更新向量

要创建一个向量,然后向其中添加元素,我们可以使用push方法,

fn main() {let mut v = Vec::new();         // mut关键字,创建可修改的向量vectorv.push(5);                      // 和其他语言类似,传入数据v.push(6);v.push(7);v.push(8);
}

和任何变量一样,如果我们希望能够改变它的值,我们需要使用mut关键字使它可变,如第3章所讨论的。我们放入的数字都是i32类型的,Rust从数据中推断出这一点,所以我们不需要Vec注释。

读取向量的元素

有两种方法可以引用存储在vector中的值:通过索引或者使用get方法。在下面的例子中,为了更加清晰起见,我们对这些函数返回的值的类型进行了注释。

fn main() {let v = vec![1, 2, 3, 4, 5];                // 定义向量let third: &i32 = &v[2];                     // 通过索引获取向量中的值println!("The third element is {}", third);let third: Option<&i32> = v.get(2);          // 通过get接口获取向量中的值match third {Some(third) => println!("The third element is {}", third),None => println!("There is no third element."),}
}

编译运行

cargo run

  结果

 请注意这里的一些细节。我们使用索引值2来获取第三个元素,因为向量是由数字索引的,从零开始。使用&和[]给出了对索引值处元素的引用。当我们使用get方法并将索引作为参数传递时,我们得到一个可以与match一起使用的Option< &T>

Rust提供这两种方法引用元素的原因是,当您试图使用现有元素范围之外的索引值时,您可以选择程序的行为方式。作为一个例子,让我们看看当我们有一个包含五个元素的向量,然后我们尝试用每种技术访问索引为100的元素时会发生什么,

fn main() {let v = vec![1, 2, 3, 4, 5];let does_not_exist = &v[100];         // 获取索引为100的值let does_not_exist = v.get(100);
}

 编译运行

cargo run

 当我们运行这段代码时,第一个[]方法将导致程序崩溃,因为它引用了一个不存在的元素。如果试图访问超过vector末尾的元素,希望程序崩溃时,最好使用这种方法

get方法被传递一个在vector之外的索引时,它返回None而不会死机。如果在正常情况下偶尔会访问vector范围之外的元素,那么可以使用这个方法。然后你的代码将有逻辑来处理有Some(&element)或者None,如之前章节讨论的。例如,索引可能来自输入数字的人。如果他们不小心输入了一个太大的数字,程序得到一个None值,你可以告诉用户当前向量中有多少项,然后再给他们一次输入有效值的机会。这比因为一个打字错误而导致程序崩溃更容易操作!

当程序有一个有效的引用时,借用检查器执行所有权和借用规则以确保这个引用和任何其他对vector内容的引用保持有效。回想一下在同一个作用域中不能有可变和不可变引用的规则。这条规则适用于下例,我们保存了一个对向量中第一个元素的不可变引用,并试图在末尾添加一个元素。如果我们试图在函数的后面引用这个元素,这个程序将无法运行:

fn main() {let mut v = vec![1, 2, 3, 4, 5];          // 可变的向量let first = &v[0];v.push(6);                                // 改变值println!("The first element is: {}", first);
}

编译运行

cargo run

编译错误 

 上例中的代码看起来应该是可行的:为什么对第一个元素的引用要关心向量末尾的变化?这个错误是由vector的工作方式造成的:因为vector将值彼此相邻地放在内存中,所以在vector的末尾添加新元素可能需要分配新的内存,并将旧的元素复制到新的空间,如果没有足够的空间将所有元素彼此相邻地放在vector当前存储的位置。在这种情况下,对第一个元素的引用将指向已释放的内存。借用规则防止程序在这种情况下终止

迭代向量中的值

为了依次访问vector中的每个元素,我们将遍历所有元素,而不是使用索引一次访问一个元素。下例中显示了如何使用一个for循环来获得对i32值的向量中的每个元素的不可变引用,并打印它们。

fn main() {let v = vec![100, 32, 57];for i in &v {                     // for循环println!("{}", i);}
}

编译运行

cargo run

 我们还可以迭代可变向量中每个元素的可变引用,以便对所有元素进行更改。下例中中的for循环将每个元素加50。

fn main() {let mut v = vec![100, 32, 57];for i in &mut v {*i += 50;              // 每个数加 50println!("{}", i);}
}

编译运行

cargo run

 

 要改变可变引用所引用的值,我们必须在使用+=操作符之前使用* 接触引用操作符来获得I中的值。

由于借用检查器的规则,迭代一个向量,无论是不变的还是可变的,都是安全的。如果我们试图在上例中的for循环体中插入或删除项目,我们会得到一个类似于上述其中代码的编译错误。对for循环持有的向量的引用防止了对整个向量的同时修改。

使用枚举存储多种类型

向量只能存储相同类型的值。这可能不方便;肯定有需要存储不同类型的项目列表的用例。幸运的是,一个枚举的变体是在同一个枚举类型下定义的,所以当我们需要一个类型来表示不同类型的元素时,我们可以定义并使用一个枚举

例如,假设我们想从电子表格的一行中获取值,该行中的一些列包含整数、一些浮点数和一些字符串。我们可以定义一个枚举,它的变量包含不同的值类型,所有的枚举变量都被认为是相同的类型:枚举的类型。然后我们可以创建一个向量来保存这个枚举,最终保存不同的类型。

fn main() {enum SpreadsheetCell {          // 枚举Int(i32),Float(f64),Text(String),}let row = vec![                           // 将枚举类型存入向量SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from("blue")),SpreadsheetCell::Float(10.12),];
}

Rust需要知道在编译时vector中有哪些类型,这样它就能准确地知道在堆中需要多少内存来存储每个元素。我们还必须明确在这个向量中允许什么类型。如果Rust允许vector保存任何类型,那么有可能一个或多个类型会导致对vector元素执行的操作出错。使用枚举加match表达式意味着Rust将确保在编译时处理每一种可能的情况。

如果你不知道一个程序在运行时将在vector中存储的所有类型,enum技术就不起作用。

既然我们已经讨论了使用vectors的一些最常见的方法,可以查看Rust API文档,了解标准库在Vec上定义的许多有用的方法。例如,除了push之外,pop方法还移除并返回最后一个元素。

删除向量会删除其元素

像任何其他struct一样,向量在超出范围时被释放,

fn main() {{let v = vec![1, 2, 3, 4];// do stuff with v} // <- v goes out of scope and is freed here         // 有效范围
}

vector被丢弃时,它的所有内容也被丢弃,这意味着它保存的整数将被清除。借用检查器确保仅当向量本身有效时,才使用对向量内容的任何引用。

本章重点

  • 向量vector概念及创建方法
  • 向量的使用 - 更新数据
  • 读取向量中的元素
  • 迭代向量中的值
  • 使用枚举存储多类型的数据

相关内容

热门资讯

人生个性座右铭 关于人生个性座右铭(精选30句)  人生没有绝对的公平,而是相对公平。在一个天平上,你得到越多,势必...
最具创意的个性签名原创版 最具创意的个性签名原创版  随着社交网络和信息技术的飞速发展,越来越多人热衷于在网上设置自己的个性签...
牛的名字105个 牛的名字105个  一、网名的取名原则  1、符合形象定位要求  网名是你自己形象综合的,直接传达,...
好听的公司名字700例 好听的公司名字700例  名字的基本解释  人的称号。古人不仅有“名”,而且有“字”。婴儿出生三个月...
qq飞车好听的个性签名 qq飞车好听的个性签名70条  随着网络社交蓬勃发展,越来越多人习惯于时不时更换自己的个性签名,好的...
读书个性座右铭 关于读书个性座右铭汇总(通用80句)  书是我们时代的生命智慧里没有书籍,就好像鸟儿没有翅膀。以下是...
霸气网络签名摘抄 霸气网络签名摘抄(精选65句)  心仪和喜欢不一样,喜欢是切实的,真实存在的,而心仪是一种愿望,是幻...
英文个性签名设计免费 英文个性签名设计免费  英文个性签名设计A smile, a beautiful memories下...
女生的个性签名 女生的个性签名(精选290句)  随着社交网络的普及,越来越多人会在线上发布个性签名,好的个性签名对...
两个字的名字(精选1740个... 两个字的名字(精选1740个)  一、网名的取名原则  1、符合形象定位要求  网名是你自己形象综合...
简短霸气的励志个性签名 简短霸气的励志个性签名(精选220句)  随着社交网络的普遍使用,越来越多人会在线上发布个性签名,不...
少先队建队节讲座稿 少先队建队节讲座稿  演讲稿可以提高演讲人的自信心,有助发言人更好地展现自己。在快速变化和不断变革的...
女生超级伤感的个性签名 女生超级伤感的个性签名  1、我只是在寻觅,最初的天荒地老,什么时候才能变成现实。  2、从此没有谁...
成熟男人的微信名字昵称大全 成熟男人的微信名字昵称大全  成熟男人的微信名字昵称大全(精选450个)  如果说女性的容貌和身材是...
保险费率表 保险费率表汽车保险费率表摘要机动车辆保险基准费率表非营业用车车辆损失险1年以下1—2年2—6年6年以...
给女朋友的备注 给女朋友的备注  一、什么是备注  备注是一个汉语词语,一指表册上供填写附注的栏目;二指在备注栏内所...
商代盘龙城 商代盘龙城3500年前,今天的武汉市汉口城区是一片水乡泽国,在汉口北郊的傍水岗地上,有一座盘龙城。商...
幽默搞笑个性签名 幽默搞笑个性签名六十五条  1、手拿菜刀砍电线,一路火花带闪电。  2、痛经就像炫迈口香糖,痛感十足...
qq个性签名唯美 qq个性签名唯美  qq个性签名唯美(精选220句)  随着社交网络平台的快速发展,越来越多人习惯于...
性格的个性签名 关于性格的个性签名  寂寞就是你有话想说的时候没有人听,有人听得时候你无话可说。下面是小编整理的关于...