当前位置: 首页 > article >正文

【Rust自学】8.1. Vector

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

8.1.0. 本章内容

第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。

第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编译时就确定,在运行时它们可以动态地变大或变小。

本章主要会讲三种集合:Vector(本文)、String和HashMap

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

8.1.1. 使用Vector存储多个值

Vector这个类型的写法是Vec<T>,同样的,T代表泛型变量,在实际使用时替换成自己需要的数据类型即可。

Vector由标准库提供,在Vector里可以存储多个值,而且这些值的类型是相同的,它们在内存中是连续存放的。可以把它视为可以扩展的数组。

创建Vector可以使用Vec::new这个函数,我们看个例子:

fn main() {  
    let v:Vec<i32> = Vec::new();  
}

这个例子很简单,就是使用Vec::new这个函数声明了一个Vector里边元素是i32的变量。

在这里需要填在Vec<>里填i32是因为Vec::new它创建的是一个空的Vector,里面没有元素,又因为没有前后文供Rust推断,所以Rust就推断不出来这个变量里的元素是什么类型的,就会报错。所以这里需要显式声明。如果有前后文供Rust推断,Rust就能够自行判断Vector里的元素类型。

而在一般情况下,会使用指定初始值的方式来创建Vector,这样的方式就可以使用Veec!这个宏。如下例:

fn main() {  
    let v = vec![1, 2, 3];  
}

这里就不需要显式声明Vector里的元素类型了,因为Rust编译器根据初始值1、2、3推断出了元素类型是i32

8.1.2. 如何更新Vector

1. 添加元素

向Vector里添加元素使用push这个方法。如下例:

fn main() {  
    let mut v = Vec::new();  
    v.push(1);  
}
  • 注意,向Vector里添加元素的前提是这个Vector是可变变量,所以在声明的时候需要mut关键字。
  • 这里的let mut v = Vec::new();也没有显式声明元素类型,但是Rust编译器通过下文向Vector里添加1的操作推断出了元素类型是i32

2. 删除Vector

与任何其他的struct结构体一样,当Vector离开作用域后,它和它里面的元素就会被清理掉。

3. 读取Vector的元素

一共有两种方式可以应用Vector里面的值,一种是使用索引,一种是使用get方法。如下例:一个Vector,里面存有1 2 3 4 5,访问并打印出第三个元素。

fn main() {  
    let v = vec![1, 2, 3, 4, 5];  
    let third = &v[2];//索引  
    println!("The third element is {}", third);  
  
    match v.get(2) {  //get方法加match
        Some(third) => println!("The third element is {}", third),  
        None => println!("There is no third element."),  
    };  
}
  • let third = &v[2];是使用索引的方式,访问第三个元素就是索引2的位置,所以[]内写2。而在变量v前加上&表示是引用。
  • v.get(2)就是使用get方法来实现读取的,但由于get的返回值是Option这个枚举类型(在6.2. Option枚举中讲过,这里不再赘述),所以要使用match表达式(在6.3. 控制流运算符-match中讲过match)来解包。
    如果能从这个索引取到值,那么就会把这个索引下的值绑定给third这个变量,然后在后面的代码块中输出。如果不能,返回的是None这个变体,就会打印"There is no third element."。

这两者的实现方法比较不同,效果是一样的。但如果是非法的访问(比如访问的索引越界了,超过了实际Vector的长度),两种将会有一些区别。

先试试使用索引:

fn main() {
	let v = vec![1, 2, 3, 4, 5];
	let third = &v[100]; //索引100越界了
	println!("The third element is {}", third);
}

输出:

index out of bounds: the len is 5 but the index is 100

程序触发了panic!,终止了程序执行,警告了索引越界。

再试试使用get:

fn main() {
	let v = vec![1, 2, 3, 4, 5];
	match v.get(100) {  //索引100越界了
        Some(third) => println!("The third element is {}", third),  
        None => println!("There is no third element."),  
    };  
}

输出:

There is no third element.

因为get函数不能从索引100上获取东西,所以它就会返回None

在写代码时,就需要确定自己的需求。遇到越界的情况,想要直接触发panic!结束程序就用所以找元素,其余的情况用get函数最好。

8.1.3. 所有权和借用规则

还记得在4.2. 所有权规则、内存与分配中讲的借用规则吗?同一个作用域内不能同时有可变和不可变引用。这个规则在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);
}

输出:

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &v[0];
  |                  - immutable borrow occurs here
4 |     v.push(6);
  |     ^^^^^^^^^ mutable borrow occurs here
5 |     println!("The first element is {}", first);
  |                                         ----- immutable borrow later used here
  • push函数的参数是&mut self, value: T&mut表示push函数会把传进来的变量作为可变引用来处理。在例子中就是v在这里有一个可变的引用。
  • let first = &v[0];这里的firstv的不可变引用,两者又在同一个作用域下,所以会报错
  • println!会把传进去的变量作为不可变引用。

在这个作用域内,同时出现了可变和不可变引用,所以程序会报错。

但有人可能会疑惑——push函数是往Vector的后面加东西,而前面的元素不会受影响,为什么Rust要搞这么麻烦的设计?

这是因为在内存中Vector的元素是连续存储的,如果往后面加一个元素,正好又有东西占用了后面的内存,腾不出地方放新的元素,系统就得重新分配内存,找个足够大的地方来放置添加了元素之后的Vector。这样的话,原来的那块内存就会被释放或者重新分配掉,但引用仍然会指向原先的那内存地址,造成悬空引用(在4.4. 引用与借用中有讲)

8.1.3. 遍历Vector里的值

使用for循环是最常见的方法。如下例:

fn main() {  
    let v = vec![1, 2, 3, 4, 5];  
    for i in &v {  
        println!("{}", i);  
    }  
}

输出效果:

1
2
3
4
5

当然,如果想要在循环里修改元素也是可以的,只需要把v声明成可变的,把&v改成&mut v即可:

fn main() {  
    let mut v = vec![1, 2, 3, 4, 5];  
    for i in &mut v {  
        *i += 10;  
    }  
    for i in v {  
        println!("{}", i);  
    }  
}

注意:第四行的*i前面之所以有个*是因为i在本质上是&mut i32类型,存储的是指针而不是实际的i32值,需要先解引用,使i变为i32类型获得实际的值才能进行加减操作。

输出效果:

11
12
13
14
15

http://www.kler.cn/a/455945.html

相关文章:

  • 基于 Ragflow 搭建知识库-初步实践
  • Java网约车项目实战实现抢单功能详解
  • 系统分析师案例分析100问
  • 如何使用fetch函数获取多个数据并同时使用(在嵌套的fetch函数之间传递数据)
  • 【RabbitMQ高级篇】消息可靠性问题(1)
  • Socket学习(一):控制台聊天demo
  • LeetCode75. 颜色分类(2024冬季每日一题 40)
  • PhPMyadmin-cms漏洞复现
  • xdoj最长的整数序列
  • node.js和js
  • MYSQL无法被连接问题
  • diffusion model evolution
  • 常用数据结构 - 前缀树
  • 七、队列————相关概念详解
  • “图书馆服务自动化”:基于SSM框架的图书借阅系统开发
  • WebSocket实现直播弹幕滚动推送效果
  • 【环境配置】Jupyter Notebook切换虚拟环境
  • Html——10 关键字和描述
  • CSS基础入门【2】
  • Python爬虫(一)- Requests 安装与基本使用教程
  • [Android]init中添加新的command
  • 高中数学刷题版:函数奇偶性[干货]
  • GaussDB典型SQL调优点之自诊断和语句下推调优
  • 五模型对比!Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测
  • Kafka_2.13-3.6.0 常用命令快速指南
  • .net core 的软件开发流程