本文作为对Rust的基础学习,建议每一位对Rust感兴趣并且想要接触Rust的同学,将本文中所有例子都自己运行一遍,不要求你手动重写,至少将例子复制下来运行查看效果。
本文基于:
rust: 1.67.0 (fc594f156 2023-01-24)
cargo: 1.67.0 (8ecd4f20a 2023-01-10)
参考文档:Rust 程序设计语言 简体中文版
Rust 数据结构 之
集合vector
在Rust中,vector使用采用的前三个单词缩写,例如:
fn main() { let v: Vec<i32> = Vec::new(); }数据类型必须指定,因为
泛型缘故,这里的Vec代表的一个32位的整型集合。而上述示例则是制定了一个空的
vector,v的变量指向,问:变量v是存在堆中还是栈中?哈哈,请思考一下,文后给出答案。
值得注意的是,这里的
v是无法进行写入的因为它是let而不是let mut(不可变的变量并非常量,因为文档中详细介绍了常量和不可变变量的区别,系列文章便没提到)。我们可以通过直接指定
v变量列表,对此Rust专门提供了一个宏,名为:vec!()举例:fn main() { let v = vec![1, 2, 3, 4]; }上述示例便是对
vec!()宏的使用,与Rust数组数据结构类似,vector也只能存储同类型的数据值;不过它区别于数组的主要优点是:可变长,而数组则是长度固定的。Tips:
vec!()只是简便了Vec::new()的操作,并且vec!()会自动根据列表值推断变量v的数据类型;vector的存取
正如:
vector其实是一个长度可变的数组(下标从0开始),对于它的值存储,它提供了一个push方法,代表为其添加值,下面定义一个可变vec:fn main() { let mut v = vec![1, 2, 3, 4]; v.push(100); println!("{:?}", v) }上例通过
v.push()将一个i32(默认)类型的100添加到v的列表中,然后输出结果:[1, 2, 3, 4, 100]vector的读取则提供了两个方式:&值引用get函数
举个例子:
fn main() { let v = vec![1, 2, 3, 4]; //生成一个不可变的 vector let item1 = &v[0]; println!("item1 = {:?}", item1); let item2 = v.get(0); match item2 { Some(item2) => println!("item2 = {item2}"), None => println!("没有该值!"), } }输出:
item1 = 1 item2 = Some(1)因为
vector的下标索引和数组相同,因此它的访问基本与数组相同;至于
get()函数返回的``Some(1),前文未提到过,这里说一嘴,它是作为enum Option`中的一个枚举字段;Option`中共用两个枚举字段:
- Some:包含了某个值的枚举字段
- None:上述值没有
至于为什么使用
Option,看看以下代码:fn main() { let v = vec![1, 2, 3, 4]; //生成一个不可变的 vector let item1 = &v[0]; println!("item1 = {:?}", item1); let item2 = v.get(100); //修改一下 match item2 { Some(item2) => println!("item2 = {item2}"), None => println!("没有该值!"), } }输出:
item1 = 1 没有该值!这样的好处不言而喻了吧,直接避免了类似
Java中NullException。vector的删除
对于
Vector的删除,Rust提供了一个remve(index)函数和一个类似于栈弹出的pop()函数,看例子:fn main() { let mut v = vec![1, 2, 3, 4]; v.remove(2); println!("{:?}", v); }输出:
[1, 2, 4]fn main() { let mut v = vec![1, 2, 3, 4]; v.pop(); println!("{:?}", v); }输出:
[1, 2, 3]vector的遍历
可以通过for进行迭代,也可以通过 while 进行下标变量,这里就举例
forfn main() { let mut v = vec![1, 2, 3, 4]; for ele in v { println!("{:?}", ele); } }输出:
1 2 3 4也可以批量更新,
fn main() { let mut v = vec![1, 2, 3, 4]; for ele in &mut v { *ele += 100; println!("{:?}", ele); } }输出:
101 102 103 104如果你学习过
C语言,对于&和*应该并不陌生。vector中的枚举
上文所说,vector中只允许存相同类型的值,但是对于枚举
vector是将它看做为一种类型,就因为它是枚举,以下示例来自文档:#[derive(Debug)] enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } fn main() { let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ]; println!("row={:?}", row); }输出:
row=[Int(3), Text("blue"), Float(10.12)]以下是来自文档的原话:
Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。
而由于Rust的安全性,vector在离开它所在的作用域之后会自定释放销毁。
回答前面的问题:变量v是存在堆中还是栈中?
回答:按照前文的逻辑,
v作为变量是被存放在栈当中,然而它有一个地址指向,指向了堆中的一块内存区域,该内存中存放的则是变量的值。String
在Rust中字符串都是已
UTF-8编码的,前文提到过字符串,下面的例子都应该不陌生了:fn main() { let s = String::from("Hello World!"); println!("{}", s); }输出:
Hello World!字符串还提供了一个
new()函数,允许创建一个空的字符串,前文的猜数字就已经使用过://伪代码 fn main(){ let mut s = String::new(); ... //输入操作 }String的更新
对于String来说,它其实与
vector的数据结构类似,只需要对一个String结构增加一个mut关键字,即可对字符串进行更新修改。fn main() { let mut s = String::new(); s.push_str("Hello"); //附加一个字符串切片 s.push('!'); //附加一个字符 println!("{}", s); }输出:
Hello!Tips:字符串切片
&str是一个引用类型,对于以下示例:fn main() { let s: &str = "Hello"; let s1: &str = &String::from("Hello")[..]; println!("{}", s); println!("{}", s1); }程序之初给定了一个不可变变量
s,它的类型则是&str;而s1则是由String切片出来的。输出:
Hello Hello另一种则是通过
+和format!()宏,举例:fn main() { let mut s = String::new(); s += "Hello"; println!("{}", s); }输出:
Hellofn main() { let s1 = "Hello"; let s2 = String::from(" World!"); let s = format!("{s1}{s2}"); println!("{}", s); }输出:
Hello World!String <–> &str
从
String到&str,完整切片后即可实现:let s1: &str = &String::from("Hello")[..];从
&str到String,Rust提供了一个创建方法:to_string();let s: String = "Hello".to_string();
值得注意的是,他们虽然能够得到相同的内容,但是在堆栈中可不一样哦。
String的索引
这是一个不太值得深究的事情,Rust中的String不支持索引访问,也就是:
fn main() { let s = String::from("Hello World!"); let c = s[0]; //不支持 let c = &s[0]; //不支持 println!("{}", c); }上述代码无法通过编译。
String切片(又谈slice)
前面的文章说过
slice,这里跟着文档再说一遍:String的切片在于它能够直接获取某个字符串的一部分(切出一部分)基本使用方式:
fn main() { let s = String::from("Hello World!"); let c1 = &s[..5]; //切出: Hello! let c2 = &s[0..5]; //切出: Hello! let c3 = &s[0..]; //切出: Hello World! let c4 = &s[..]; //切出: Hello World! println!("{}", c1); println!("{}", c2); println!("{}", c3); println!("{}", c4); }输出:
Hello Hello Hello World! Hello World!String的遍历
String提供了一个
chars()函数:fn main() { let s = String::from("Hello World!"); for it in s.chars() { println!("{it}"); } }输出:
H e l l o W o r l d !或者你想要获取每个
char的字节码:fn main() { let s = String::from("Hello World!"); for it in s.bytes() { println!("{it}"); } }输出:
72 101 108 108 111 32 87 111 114 108 100 33HashMap
终于到了
HashMap了,这是一个典型的K:V集合,也称为:键值对。在Rust中,
HashMap结构的创建与字符串类似,可以创建一个空的HashMap集合。use std::collections::HashMap; fn main() { let mut map: HashMap<String, String> = HashMap::new(); map.insert("Key1".to_string(), "Value1".to_string()); println!("{:?}", map); }输出:
{"Key1": "Value1"}例子通过
HashMap::new();创建了一个空的可变的map变量,通过insert()函数完成map增加。HashMap更新
因为HashMap中
键只允许出现一次的缘故,以下代码,再次对Key1进行设置则会将原本的Value1替换为123456:use std::collections::HashMap; fn main() { let mut map: HashMap<String, String> = HashMap::new(); map.insert("Key1".to_string(), "Value1".to_string()); map.insert("Key1".to_string(), "123456".to_string()); println!("{:?}", map); }输出:
{"Key1": "123456"}HashMap中的entry()函数
查看文档之后,发现
entry()非常好用,接下来介绍它的几个方法:use std::collections::HashMap; fn main() { let mut map: HashMap<String, String> = HashMap::new(); let key1 = String::from("Key1"); let value1 = String::from("Value1"); map.insert(key1, value1); let e = map.entry("Key1".to_string()); //e.or_insert("New Value".to_string()); //如果`Key1`不存在, 为`Key1`设置值`New Value` //e.or_default(); //如果`Key1`不存在,则默认给定一个空的字符串 //e.key(); //返回当前的-key即`Key1` }HashMap中的所有权
由于Rust中变量
移动(move)的特性,某个变量一旦被添加到HashMap中,所有权也会跟着移动到HashMap中,修改一下上面的例子:use std::collections::HashMap; fn main() { let mut map: HashMap<String, String> = HashMap::new(); let key1 = String::from("Key1"); let value1 = String::from("Value1"); map.insert(key1, value1); println!("{}", key1); //因为所有权已经被转移, 故它无法通过编译 println!("{}", value1); //因为所有权已经被转移, 故它无法通过编译 println!("{:?}", map); }至此,本篇完!
时间:2023年2月6日 第5天