|
|
@ -2,7 +2,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
动态数组类型用`Vec<T>`表示,事实上,在之前的章节,它的身影多次出现,我们一直没有细讲,只是简单的把它当作数组处理。
|
|
|
|
动态数组类型用`Vec<T>`表示,事实上,在之前的章节,它的身影多次出现,我们一直没有细讲,只是简单的把它当作数组处理。
|
|
|
|
|
|
|
|
|
|
|
|
动态数组允许你存储多个值,这些值在内存中一个紧挨着另一个排列,因此访问其中某个元素的成本非常低。动态数组只能存储相同类型的元素,如果你想存储不同类型的元素,可以使用之前讲过的枚举类型或者特征对象.
|
|
|
|
动态数组允许你存储多个值,这些值在内存中一个紧挨着另一个排列,因此访问其中某个元素的成本非常低。动态数组只能存储相同类型的元素,如果你想存储不同类型的元素,可以使用之前讲过的枚举类型或者特征对象。
|
|
|
|
|
|
|
|
|
|
|
|
总之,当我们想拥有一个列表,里面都是相同类型的数据时,动态数组将会非常有用。
|
|
|
|
总之,当我们想拥有一个列表,里面都是相同类型的数据时,动态数组将会非常有用。
|
|
|
|
|
|
|
|
|
|
|
@ -15,13 +15,13 @@
|
|
|
|
let v: Vec<i32> = Vec::new();
|
|
|
|
let v: Vec<i32> = Vec::new();
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这里, `v`被显式地声明了类型`Vec<i32>`,这是因为Rust编译器无法从`Vec::new()`中得到任何关于类型的暗示信息,因此也无法推导出`v`的具体类型,但是当你向里面增加一个元素后,一切又不同了:
|
|
|
|
这里,`v` 被显式地声明了类型`Vec<i32>`,这是因为 Rust 编译器无法从 `Vec::new()` 中得到任何关于类型的暗示信息,因此也无法推导出 `v` 的具体类型,但是当你向里面增加一个元素后,一切又不同了:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
let mut v = Vec::new();
|
|
|
|
let mut v = Vec::new();
|
|
|
|
v.push(1);
|
|
|
|
v.push(1);
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
此时,`v`就无需手动声明类型,因为编译器通过`v.push(1)`,推测出`v`中的元素类型是`i32`,因此推导出`v`的类型是`Vec<i32>`.
|
|
|
|
此时,`v` 就无需手动声明类型,因为编译器通过 `v.push(1)`,推测出 `v` 中的元素类型是 `i32`,因此推导出 `v` 的类型是 `Vec<i32>`。
|
|
|
|
|
|
|
|
|
|
|
|
> 如果预先知道要存储的元素个数,可以使用 `Vec::with_capacity(capacity)` 创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能
|
|
|
|
> 如果预先知道要存储的元素个数,可以使用 `Vec::with_capacity(capacity)` 创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能
|
|
|
|
|
|
|
|
|
|
|
@ -34,7 +34,7 @@ let v = vec![1, 2, 3];
|
|
|
|
同样,此处的 `v` 也无需标注类型,编译器只需检查它内部的元素即可自动推导出 `v` 的类型是 `Vec<i32>` (Rust中,整数默认类型是i32,在[数值类型](../base-type/numbers.md#整数类型)中有详细介绍)。
|
|
|
|
同样,此处的 `v` 也无需标注类型,编译器只需检查它内部的元素即可自动推导出 `v` 的类型是 `Vec<i32>` (Rust中,整数默认类型是i32,在[数值类型](../base-type/numbers.md#整数类型)中有详细介绍)。
|
|
|
|
|
|
|
|
|
|
|
|
## 更新Vector
|
|
|
|
## 更新Vector
|
|
|
|
向数组尾部添加元素,可以使用`push`方法:
|
|
|
|
向数组尾部添加元素,可以使用 `push` 方法:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
let mut v = Vec::new();
|
|
|
|
let mut v = Vec::new();
|
|
|
|
v.push(1);
|
|
|
|
v.push(1);
|
|
|
@ -44,7 +44,7 @@ v.push(1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Vector与其元素共存亡
|
|
|
|
## Vector与其元素共存亡
|
|
|
|
跟结构体一样,`Vector`类型在超出作用域范围后,会被自动删除:
|
|
|
|
跟结构体一样,`Vector` 类型在超出作用域范围后,会被自动删除:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
{
|
|
|
|
{
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
@ -53,10 +53,10 @@ v.push(1);
|
|
|
|
} // <- v超出作用域并在此处被删除
|
|
|
|
} // <- v超出作用域并在此处被删除
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
当`Vector`被删除后,它内部存储的所有内容也会随之被删除。目前来看,这种解决方案简单直白,但是当`vector`中的元素被引用后,事情可能会没那么简单。
|
|
|
|
当 `Vector` 被删除后,它内部存储的所有内容也会随之被删除。目前来看,这种解决方案简单直白,但是当 `Vector` 中的元素被引用后,事情可能会没那么简单。
|
|
|
|
|
|
|
|
|
|
|
|
## 从Vector中读取元素
|
|
|
|
## 从Vector中读取元素
|
|
|
|
读取指定位置的元素有两种方式可选: 通过下标索引访问或者使用`get`方法:
|
|
|
|
读取指定位置的元素有两种方式可选:通过下标索引访问或者使用 `get` 方法:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
let v = vec![1, 2, 3, 4, 5];
|
|
|
|
let v = vec![1, 2, 3, 4, 5];
|
|
|
|
|
|
|
|
|
|
|
@ -98,7 +98,7 @@ v.push(6);
|
|
|
|
println!("The first element is: {}", first);
|
|
|
|
println!("The first element is: {}", first);
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
先不运行,来推断下结果,首先`first = &v[0]`进行了不可变借用,`v.push`进行了可变借用,如果`first`在`v.push`之后不再使用,那么该段代码可以成功编译(原因见[引用的作用域](../ownership/borrowing.md#可变引用与不可变引用不能同时存在)).
|
|
|
|
先不运行,来推断下结果,首先 `first = &v[0]` 进行了不可变借用,`v.push` 进行了可变借用,如果 `first` 在 `v.push` 之后不再使用,那么该段代码可以成功编译(原因见[引用的作用域](../ownership/borrowing.md#可变引用与不可变引用不能同时存在))。
|
|
|
|
|
|
|
|
|
|
|
|
可是上面的代码中,`first` 这个不可变借用在可变借用 `v.push` 后被使用了,那么妥妥的,编译器就会报错:
|
|
|
|
可是上面的代码中,`first` 这个不可变借用在可变借用 `v.push` 后被使用了,那么妥妥的,编译器就会报错:
|
|
|
|
```console
|
|
|
|
```console
|
|
|
@ -122,7 +122,7 @@ error: could not compile `collections` due to previous error
|
|
|
|
|
|
|
|
|
|
|
|
其实,按理来说,这两个引用不应该互相影响的:一个是查询元素,一个是在数组尾部插入元素,完全不相干的操作,为何编译器要这么严格呢?
|
|
|
|
其实,按理来说,这两个引用不应该互相影响的:一个是查询元素,一个是在数组尾部插入元素,完全不相干的操作,为何编译器要这么严格呢?
|
|
|
|
|
|
|
|
|
|
|
|
原因在于:数组的大小是可变的,当老数组的大小不够用时,Rust会重新分配一块更大的内存空间,然后把老数组拷贝过来。这种情况下,之前的引用显然会指向一块无效的内存,这非常rusty - 对用户进行严格的教育。
|
|
|
|
原因在于:数组的大小是可变的,当旧数组的大小不够用时,Rust会重新分配一块更大的内存空间,然后把旧数组拷贝过来。这种情况下,之前的引用显然会指向一块无效的内存,这非常rusty - 对用户进行严格的教育。
|
|
|
|
|
|
|
|
|
|
|
|
其实想想,**在长大之后,我们感激人生路上遇到过的严师益友,正是因为他们,我们才在正确的道路上不断前行,虽然在那个时候,并不能理解他们**,而 Rust 就如那个良师益友,它不断的在纠正我们不好的编程习惯,直到某一天,你发现自己能写出一次性通过的漂亮代码时,就能明白它的良苦用心。
|
|
|
|
其实想想,**在长大之后,我们感激人生路上遇到过的严师益友,正是因为他们,我们才在正确的道路上不断前行,虽然在那个时候,并不能理解他们**,而 Rust 就如那个良师益友,它不断的在纠正我们不好的编程习惯,直到某一天,你发现自己能写出一次性通过的漂亮代码时,就能明白它的良苦用心。
|
|
|
|
|
|
|
|
|
|
|
@ -130,7 +130,7 @@ error: could not compile `collections` due to previous error
|
|
|
|
|
|
|
|
|
|
|
|
## 迭代遍历Vector中的元素
|
|
|
|
## 迭代遍历Vector中的元素
|
|
|
|
|
|
|
|
|
|
|
|
如果想要依次访问数组中的元素,可以使用迭代的方式去遍历数组,这种方式比用下标的方式去遍历数组更安全也更高效(每次下标访问都会触发数组边界检查):
|
|
|
|
如果想要依次访问数组中的元素,可以使用迭代的方式去遍历数组,这种方式比用下标的方式去遍历数组更安全也更高效(每次下标访问都会触发数组边界检查):
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
|
for i in &v {
|
|
|
|
for i in &v {
|
|
|
@ -147,7 +147,7 @@ for i in &mut v {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 存储不同类型的元素
|
|
|
|
## 存储不同类型的元素
|
|
|
|
在本节开头,有讲到数组的元素必需类型相同,但是也提到了解决方案: 那就是通过使用枚举类型和特征对象来实现不同类型元素的存储。先来看看通过枚举如何实现:
|
|
|
|
在本节开头,有讲到数组的元素必需类型相同,但是也提到了解决方案:那就是通过使用枚举类型和特征对象来实现不同类型元素的存储。先来看看通过枚举如何实现:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
enum IpAddr {
|
|
|
|
enum IpAddr {
|
|
|
@ -203,7 +203,7 @@ fn main() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
比枚举实现要稍微复杂一些,我们为`V4`和`V6`都实现了特征`IpAddr`,然后将它俩的实例用`Box::new`包裹后,存在了数组`v`中,需要注意的是,这里必需手动的指定类型:`Vec<Box<dyn IpAddr>>`,表示数组`v`存储的是特征`IpAddr`的对象,这样就实现了在数组中存储不同的类型.
|
|
|
|
比枚举实现要稍微复杂一些,我们为 `V4` 和 `V6` 都实现了特征 `IpAddr`,然后将它俩的实例用 `Box::new` 包裹后,存在了数组 `v` 中,需要注意的是,这里必需手动的指定类型:`Vec<Box<dyn IpAddr>>`,表示数组 `v` 存储的是特征 `IpAddr` 的对象,这样就实现了在数组中存储不同的类型。
|
|
|
|
|
|
|
|
|
|
|
|
在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于[特征对象非常灵活](../trait/trait-object.md),而编译器对枚举的限制较多,且无法动态增加类型。
|
|
|
|
在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于[特征对象非常灵活](../trait/trait-object.md),而编译器对枚举的限制较多,且无法动态增加类型。
|
|
|
|
|
|
|
|
|
|
|
|