【百例RUST - 009】容器 Vector
第一章 基础用法
第01节 快速定义和使用
案例代码
rust
fn main(){
// 创建空的 Vector 因为他是一个容器, 如果是不可变的没有意义, 需要存放内容, 所以一般定义为可变类型
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 需要获取容器的内容, 输出
println!("one 的 0号索引位置 = {}", &one[0]);
println!("one 的 1号索引位置 = {}", &one[1]);
println!("one 的 2号索引位置 = {}", &one[2]);
println!("one 的 3号索引位置 = {}", &one[3]);
}
// one 的 0号索引位置 = 11
// one 的 1号索引位置 = 22
// one 的 2号索引位置 = 33
// one 的 3号索引位置 = 44
通过上述案例,我们可以知道,
可变空的Vector的定义格式如下
let mut 变量名称: Vec<数据类型> = Vec::new();
存放数据采用的是
push(元素)
获取指定所有位置的元素方式1
&变量名称[索引值]
第02节 直接带有值的定义
前面介绍的是 可变的、空的 容器定义方式
下面介绍的是 带有值的定义方式
rust
fn main(){
// 创建有值的容器
let mut one : Vec<i32> = vec![1,2,3];
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 需要获取容器的内容, 输出
println!("one 的 0号索引位置 = {}", &one[0]);
println!("one 的 1号索引位置 = {}", &one[1]);
println!("one 的 2号索引位置 = {}", &one[2]);
println!("one 的 3号索引位置 = {}", &one[3]);
}
// one 的 0号索引位置 = 1
// one 的 1号索引位置 = 2
// one 的 2号索引位置 = 3
// one 的 3号索引位置 = 11
第03节 推荐获取值的方式
rust
fn main(){
// 创建空的 Vector 因为他是一个容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 通过索引, 获取指定位置的数据, 该数据类型是一个 Option<泛型> 类型的数据
let opt = one.get(0);
// 针对于 Option 进行 match 匹配
match opt {
None => println!("nothing"),
Some(i) => println!("result = {}", i),
}
}
// result = 11
推荐这种方式获取值,是因为存在容错机制处理,如果访问了超过索引范围的数据值,不会出现崩溃!
第04节 遍历容器
不可变的遍历方式。 变量过程中,值没有改变
rust
fn main(){
// 创建空的 Vector 因为他是一个容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 遍历容器的数据
for i in &one{
println!("内容: {}", i);
}
}
// 内容: 11
// 内容: 22
// 内容: 33
// 内容: 44
可变的遍历方式。遍历过程中,值可以改变。
rust
fn main(){
// 创建空的 Vector 因为他是一个容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 遍历容器的数据
for i in &mut one{
*i = *i+1;
println!("内容: {}", i);
}
}
// 内容: 12
// 内容: 23
// 内容: 34
// 内容: 45
知识点: 这里的
*i是什么意思?理解这个问题,需要解答下面的几个问题?
1、在循环当中使用的
i是什么?2、为什么必须加上星号(解引用)?
3、不加星号,会怎样?
4、深入理解,自动解引用的场景?
问题一
问题: 在循环当中使用的 i 是什么?
回答:
在我们的代码当中 for i in &mut one
这种写法, 会让 i 变成 可变引用( &mut i32) 我们可以将 i 想象成为一张 "指向内存地址的小纸条"
问题二
问题: 为什么必须加上星号(解引用)?
回答:
在 rust 代码当中, * 是 【解引用】的操作符。
i 的身份: 他是 (&mut i32) 也就是 指向某个整数的指针引用。他存放的是地址。
*i 的身份: 他是 i32 也就是那个地址里面, 存放的具体整数值。
*i = *i + 1 的逻辑过程:
第一步: 取值, 右边的 *i 去地址里面把那个整数(比如 11)取出来
第二步: 运算, 执行 11+1 得到 12
第三步: 赋值, 左边的 *i 把计算得到的结果 12 写回到那个地址所指向的内存空间里面。
问题三
问题: 不加星号会怎样呢?
回答:
如果我们尝试写 i=i+1 编辑器会对我们 发出歇斯底里的"咆哮" SB 原因有两点:
1、类型不匹配:
i 是一个 "引用/地址" 而 1 是一个整数。 我们不能把一个地址和一个整数直接相加。
2、所有权与赋值限制:
在循环当中, i 作为一个迭代的变量, 如果我们试图改变 i 本身, 也就是让这张纸条指向另外的一个地址,
在 Rust 的for循环语法当中, 通常情况下是不被允许的。
问题四
问题: 深入理解,自动解引用的场景
回答:
我们可能会存在疑问, 为什么下面写的内容 println!("内容: {}", i); 没有添加星号, 也可以打印出数字呢?
这里原因是因为, RUST 很贴心, println! 宏会自动处理引用, 他会自动寻找引用背后的值, 并且打印出来。
所以:
println!("{}", i); // 自动解引用, 方便阅读
println!("{}", *i); // 手动解引用, 效果一样
因此:
如果我们在做数学运算(如+) 或者重新赋值的时候, RUST 要求我们必须显示使用 *星号 来明确我们的意图: 我们是想要操作那个值。
第05节 在容器中使用枚举
案例代码
rust
#[derive(Debug)]
enum Context {
Text(String),
Float(f32),
Int(i32),
}
fn main(){
// 使用枚举存放到容器中
let mut one : Vec<Context> = Vec::new();
// 向容器里面存放几个数据
one.push(Context::Text(String::from("hellowrold")));
one.push(Context::Int(32));
one.push(Context::Float(3.66));
// 遍历容器的数据
for con in & one{
println!("内容: {:?}", con);
}
}
// 内容: Text("hellowrold")
// 内容: Int(32)
// 内容: Float(3.66)
第二章 常用函数
第01节 添加函数
直接添加
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
// 遍历容器的数据
for i in & one{
println!("内容: {}", i);
}
}
// 内容: 11
// 内容: 22
// 内容: 33
插入元素
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 1、向指定的索引位置添加元素, 之前的位置靠后移动。
// 2、这里的第一个参数, 与前面容器的容量有关,
// 前面的容器容量大小是4, 这里第一个参数的取值是 0,1,2,3,4
// 如果这里的第一个参数, 超过了4, 例如(5,6,7,8...)则会出现崩溃!
one.insert(2, 66);
// 遍历容器的数据
for i in & one{
println!("内容: {}", i);
}
}
// 内容: 11
// 内容: 22
// 内容: 66
// 内容: 33
// 内容: 44
第02节 删除元素
移除最后的一个元素
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 移除最后的一个元素
one.pop();
// 遍历容器的数据
for i in & one{
println!("内容: {}", i);
}
}
// 内容: 11
// 内容: 22
// 内容: 33
移除指定索引位置的元素
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 移除指定索引位置的元素
// 这里的参数, 与前面容器的容量有关, 表示的是索引位置, 从0开始到最大容量-1
// 例如: 当前容器存放了4个元素, 那么参数的取值只能是 (0,1,2,3)
let num = one.remove(2);
println!("被移除的元素是: {}", num);
// 遍历容器的数据
for i in & one{
println!("内容: {}", i);
}
}
// 被移除的元素是: 33
// 内容: 11
// 内容: 22
// 内容: 44
第03节 修改元素
修改指定索引位置的元素值
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 修改指定索引位置的元素值
one[2] = 666;
// 遍历容器的数据
for i in & one{
println!("内容: {}", i);
}
}
// 被移除的元素是: 33
// 内容: 11
// 内容: 22
// 内容: 666
// 内容: 44
更加安全的做法
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 查询当前容器的容量大小
let number: Option<&mut i32> = one.get_mut(2);
match number {
None => print!("数据不存在"),
Some(x) => *x = 666,
}
// 遍历容器的数据
for i in & one{
println!("内容: {}", i);
}
}
// 内容: 11
// 内容: 22
// 内容: 666
// 内容: 44
第04节 查询内容
查询容量大小
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 查询当前容器的容量大小
let len: usize = one.len();
println!("result = {}", len);
}
// result = 4
查询当前容器是否是空数据
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 查询当前容器是否是空数据
let empty: bool = one.is_empty();
println!("result = {}", empty);
}
// result = false
第三章 几个问题
第01节 查找指定元素
查询容器中,是否包含有指定的元素,如果存在则返回当前元素第一次出现的索引位置,如果不存在,则返回-1
案例代码如下:
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
one.push(33);
one.push(66);
// 查询容器当中, 是否存在指定的元素, 如果存在则返回当前元素第一次出现的索引位置, 否则返回-1
let target: i32 = 33;
// 实现查询的逻辑, 采用 iter() 获取迭代器
let result = one.iter().position(|&x|x == target);
// 将结果转换为 i32 类型, 如果没有找到则返回 -1
let index: i32 = match result {
Some(i) => i as i32,
None => -1,
};
// 输出结果
println!("元素{} 的索引位置是{}", target, index);
}
// 元素33 的索引位置是2
详细说明一下上面的代码
1、寻找位置 iter().position(....)
let result = one.iter().position(|&x|x == target);
上述代码, 主要做了三件事情:
第一件事情:
iter()
它把 Vec<i32> 变成了一个迭代器, 迭代器就像传送带一样, 把容器当中的元素, 一个一个的交给后面的函数处理。
第二件事情:
pisition(...)
这里迭代器当中的内置方法。 它会从左到右检查每一个元素,直到找到符合条件的元素为止。
需要注意的是, 他返回的结果是 Option<usize> 类型
第三件事情:
|&x| x == target
这是一个匿名的函数, 做了一个闭包的操作。
&x 因为 .iter() 提供的是元素的引用, 所以我们使用 &x 来解构。直接拿到数据值 x
x == target 这是一个逻辑判断, 如果相等, position 就会停止搜索。
2、处理结果 match 模式匹配
因为 position 返回的是 Option 类型, (要么是"有", 要么是"无")
RUST 强制要求我们必须处理这两种可能性。
第02节 所有权的问题
问题代码
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 获取第一个元素的引用
let first: &i32 = &one[0];
one.push(55);
// 目前这里就会出现问题了
println!("first = {}", first);
}
分析原因
一句话解释问题:
我们不能在手里拿着某个东西的 "票据"(引用)的时候, 又让人去改变这个东西本身(修改容器)
详细说明一下:
我们理解一下 Vec 在内存当中的工作流程
1、内存分配
当我们创建了 Vec 并且向里面 push 元素的时候, RUST 会在堆内存当中申请一块空间。
2、获取引用
执行了 let first = &one[0]; 的时候, first 指向了这块内存的首地址。
3、重新分配(关键点)
当我们执行 one.push(55); 的时候, 如果原本申请的内存空间就不够用了, 那么 Vec 会执行下面的操作:
A. 申请一块更大的 新的内存
B. 将就得数据拷贝到新的内存中
C. 释放旧内存
4、悬垂指针(问题出现)
如果允许 push 成功,那么 first 仍然会指向那块已经被释放的旧内存。
这个时候, 如果我们继续打印 first 程序就会访问到非法的地址,导致崩溃。
这在 C++当中是常见的 Bug 但是在 RUST 当中, 被编译器禁止了。
如何修改这个问题呢?
我们只是获取第一个元素的值,不去获取 容器内部的引用,直接把值拷贝出来,就可以了。
修改方案: 去掉了 & 引用符
rust
fn main(){
// 定义容器
let mut one : Vec<i32> = Vec::new();
// 向容器里面存放几个数据
one.push(11);
one.push(22);
one.push(33);
one.push(44);
// 获取第一个元素的值
let first: i32 = one[0];
one.push(55);
// 目前这里就会出现问题了
println!("first = {}", first);
}
// first = 11