【百例RUST - 009】容器 Vector

【百例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
相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
doiito9 小时前
【AI 应用】从“外国人味”到地道中文:kokoroi-rs v0.1.2 架构升级深度解析
ai·rust·系统设计
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜10 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript