【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];
这里的first
是v
的不可变引用,两者又在同一个作用域下,所以会报错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