Rust程序语言设计(5-8)
五、使用结构体来组织相关联的数据
struct ,结构体
- 自定义的数据类型
- 命名相关联的值,打包 -> 有意义的组合
(1)定义和实例化 struct
定义 struct
- 使用 struct 关键字,并为整个 struct 命名
- 花括号内,为所有 字段 定义名称和类型
例:
struct User {username:String,email:String,age:i32,active:bool,}
实例化 struct
创建 struct 实例
- 为每个字段指定具体值
- 无需按照声明顺序
- 无 mut 不可更改
struct User {username:String,email:String,age:i32,active:bool,}
fn main() {let user1 = User { //无 mut ,不可更改username:String::from("MOON"),email:String::from("example@123.com"),age:19,active:true,};
}
取出一个值:
struct User {username:String,email:String,age:i32,active:bool,}
fn main() {let mut user1 = User {username:String::from("MOON"),email:String::from("example@123.com"),age:19,active:true,};println!("{}",user1.email);
}
==注:==一旦 struct 的实例是可变的,那么实例中所有的字段都是可变的
struct 作为函数返回值
例:
struct User {username:String,email:String,age:i32,active:bool,
}fn main() {let user1 = demo(String::from("admin"),String::from("example@123.com"),19,true);// 传入 &str ,报错,所以转换成 string 类型println!("{}",user1.email);
}fn demo(username:String,email:String,age:i32,active:bool) -> User {User {username,email,age,active, //简写 -> active:active,}
}
struct 更新语法
基于某个 struct 实例来创建一个新的实例时使用
//如,仅更换几个参数
let user2 = User {email: String::from("example@123.com"),username: String::from("admin"),active: user1.active,age: user1.age,
};//使用 struct 更新语法:
let user2 = User {email: String::from("example@123.com"),username: String::from("admin"),..user1
};
Tuple struct
概念:定义类似 tuple 的 struct ,叫做 tuple struct
- Tuple struct 整体有名,内部元素没有名
- 适用于给 tuple 起名,并使它不同于其他 tuple
定义 tuple struct:struct [名字] (元素类型,元素类型,元素类型,…)
例如:
struct Color(i32,i32,i32);
struct Point(i32,i32,i32);
let black = Color(0,0,0);
let origin = Point(0,0,0);
注: black 和 origin 是不同的类型,是不同的 tuple struct 的实例。
Unit-Like Struct
概念:可以定义无字段的 struct,叫做 Unit-Like struct
适用于需要在某个类型上实现某个 trait,但在里面没有想要存储的数据
struct 数据的所有权
struct User {username: String,email: String,age: i32,active: bool,
}
这里字段使用了 String 而不是 &str:
- 该 struct 实例拥有其所有的数据
- 只要 struct 实例是有效的,那么里面的字段数据也是有效的
struct 里也可以存放引用,但这需要使用生命周 - 生命周期保证只要 struct 实例是有效的,那么里面的引用也是有效的
- 如果 struct 里面存储引用,而不使用生命周期,就会报错
(2)struct 示例
计算长方形面积
基本实现:
fn main() {let w = 30;let l = 50;println!("长方形的面积为:{}",area(w,l));
}fn area(width: u32, length: u32) -> u32 {width * length
}
元组:
fn main() {let rect = (30,50);println!("长方形的面积为:{}",area(rect));
}fn area(dim:(u32, u32)) -> u32 { //元素没有名字,可读性差dim.0 * dim.1
}
结合 struct 增强代码可读性
struct Rectangle {width: u32,length: u32,
}fn main() {let rect = &Rectangle {width: (30),length: (50),};println!("长方形的面积为:{}",area(&rect));// 传递 &rect 引用,不会获取到所有权,以便后续使用
}fn area(rect: &Rectangle) -> u32 {rect.width * rect.length
}
验证所有权:
struct Rectangle {width: u32,length: u32,
}fn main() {let rect = &Rectangle {width: (30),length: (50),};println!("长方形的面积为:{}",area(&rect));// 传递 &rect 引用,不会获取到所有权,以便后续使用println!("{}",rect.weigth);// 单个元素调用println!("如想输出全部元素{:?}",rect);// 这种输出需要添加 debug 在开头 // #[derive(Debug)]
}fn area(rect: &Rectangle) -> u32 {rect.width * rect.length
}
添加debug:
#[derive(Debug)]struct Rectangle {width: u32,length: u32,
}fn main() {let rect = &Rectangle {width: (30),length: (50),};println!("长方形的面积为:{}",area(&rect));println!("如想输出全部元素{:?}",rect);// 这种输出需要添加 debug 在开头 // #[derive(Debug)]
}fn area(rect: &Rectangle) -> u32 {rect.width * rect.length
}
运行结果:
{:?} -> {:#?} 后再输出:
(3)struct 方法
方法和函数类似:fn关键字、名称、参数、返回值
方法与函数不同之处:
- 方法是在 struct(或 enum、trait 对象)的上下文中定义
- 第一个参数是 self ,表示方法被调用的 struct 实例
定义方法:
impl Rectangle {fn area(&self) -> u32 {self.width * self.length}
}
主函数中调用:
fn main() {let rect = &Rectangle {width: (30),length: (50),};println!("长方形的面积为:{}",rect.area()); // rect.方法名()println!("{:#?}",rect);
}
定义方法
在 impl 块里定义方法
方法的第一个参数可以是 &self,也可以获得其所有权或可变借用
方法调用的运算符
C/C++:object -> something() 和 (*object).something()
Rust 没有 -> 运算符
Rust 会自动引用或解引用
- 在调用方法时就会发生这种行为
在调用方法时,Rust 根据情况自动添加 &、&mut 或 *,以便 object 可以匹配方法的签名
例如:
p1.distance(&p2);
(&p1).distance(&p2);
//两者表达效果相同
判断长方形能否包含另一个长方形
#[derive(Debug)]struct Rectangle {width: u32,length: u32,
}impl Rectangle {fn area(&self) -> u32 {self.width * self.length}fn can_hold(&self, other: &Rectangle) -> bool {self.width > other.width && self.length > other.length}
}fn main() {let rect1 = &Rectangle {width: (30),length: (50),};let rect2 = &Rectangle {width: (10),length: (40),};let rect3 = &Rectangle {width: (35),length: (55),};println!("{}",rect1.can_hold(&rect2));//判断rect1 能否包含 rect2println!("{}",rect1.can_hold(&rect3));//判断rect1 能否包含 rect2
}
关联函数
概念:可以在 ipml 块里定义不把 self 作为第一参数的函数,叫做关联函数
- 例如:String::from()
关联函数通常用于构造器
例:正方形
fn square(size: u32) -> Rectangle {Rectangle {width: size,length: size,}
}
输出正方形面积:
let s = Rectangle::square(50);
println!("正方形的面积是:{}",s.area());
:: 符号:
- 关联函数
- 模块创建的命名空间
多个 impl 块
每个 struct 允许拥有多个 impl 块
六、枚举与模式匹配
(1)定义枚举
定义枚举
IP地址:IPv4、IPv6
enum IpAddrKind {V4,V6,
}fn main() {let four = IpAddrKind::V4;let six = IpAddrKind::V6;route(four);route(six);route(IpAddrKind::V6);
}fn route(ip_kind: IpAddrKind) {}
将数据附加到枚举的变体中
优点:
- 不需要额外使用 struct
- 每个变体可以拥有不同的类型以及关联的数据量
例:
enum IpAddrKind {V4,V6,
}struct IpAddr {kind: IpAddrKind,address: String,
}fn main() {let home = IpAddr {kind: IpAddrKind::V4,address: String::from("127.0.0.1"),};let loopback = IpAddr {kind: IpAddrKind::V6,address: String::from("::1"),};
}
更改为:
enum IpAddrKind {V4(u8, u8, u8, u8),V6(String),
}fn main() {let home = IpAddrKind::V4((127), (0), (0), (1));let loopback = IpAddrKind::V6((::1));
}
为枚举定义方法 (impl)
enum Message {Quit,Move {x: i32, y: i32},Write(String),ChangeColor(i32, i32, i32),
}impl Message {fn call(&self) {}
}fn main() {let q = Message::Quit;let m = Message::Move { x: (12), y: (24) };let w = Message::Write(("Hello"));let c = Message::ChangeColor((0), (255), (255));m.call();
}
(2)Option枚举
- 定义于标准库中
- 在 Prelude (预导入模型 )
- 描述了:某个值可能存在(某种类型)或不存在的情况
Rust 没有 Null
其他语言:
- Null 是一个值,它表示"没有值"
- 一个变量可以处于两种状态:空值(null)、非空
Null的问题在于:如果使用 Null 值像非 Null 值一样,会引发错误
概念:因某种原因而变为无效或缺失的值
Rust中类似 Null 概念的枚举-Option<T>
标准库中的定义:
enum Option<T> {Some(T),None
}
包含于 预导入模块 中:
- Option<T>
- Some(T)
- None
例:
fn main() {let some_number = Some(5);let some_string = Some("A String");let absent_number: Option<i32> = None;
}
==注:==Option<T> != T
Option<T> 相较于 Null
- Option<T> 和 T 是不同的类型
- 若想使用 Option<T> 中的 T 必须先转换成 T
(3)控制流运算符 match
- 允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码
- 模式可以是字面值、变量名、通配符等
enum Coin {Penny,Nickel,Dime,Quarter,
}fn Value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => 1,Coin::Nickel => 5,Coin::Dime => 10,Coin::Quarter => 25,}
}fn main() {}
绑定值的模式
匹配的分支可以绑定到被匹配对象的部分值
- 因此,可以从 enum 中提取值
#[derive(Debug)]enum UsState {Alabama,Alaska,
}
enum Coin {Penny,Nickel,Dime,Quarter(UsState),
}fn Value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => 1,Coin::Nickel => 5,Coin::Dime => 10,Coin::Quarter(state) => {println!("State quarter from {:?}", state);25}}
}fn main() {let c = Coin::Quarter(UsState::Alaska);println!("{}", Value_in_cents(c));
}
匹配 Option<T>
fn main() {let five = Some(5);let six = plus_one(five);let none = plus_one(None);
}fn plus_one(x: Option<i32>) -> Option<i32> {match x {None => None,Some(i) => Some(i + 1),}
}
match 匹配必须穷举所有可能
假设没有列出 None 可能性
fn main() {let five = Some(5);let six = plus_one(five);let none = plus_one(None);
}fn plus_one(x: Option<i32>) -> Option<i32> {match x {//None => NoneSome(i) => Some(i + 1),}
}
运行结果
如,没有全部列出的必要的情况,可用 _ 代替其余值,必须在最后
1 => println!(1),
2 => println!(2),
3 => println!(3),
_ => (),
(4)简单控制流 if let
- 处理只关心一种匹配而忽略其它匹配的情况
- 更少的代码、缩进、以及模板
- 放弃了穷举
例:对比
fn main() {let v = Some(0u8);match v {Some(3) => println!("three"),_ => println!("others"),}if let Some(3) = v {println!("three");}
}
if let 搭配 else
fn main() {let v = Some(0u8);match v {Some(3) => println!("three"),_ => (),}if let Some(3) = v {println!("three");} else {println!("others");}
}
七、使用包、单元包及模块管理项目
(1)Package、Crate、Module
Rust 的代码组织
代码组织主要包括:
- 哪些细节可以暴露,哪些细节是私有的
- 作用域内哪些名称有效
模块系统: - Package(包):Cargo 的特性,构建、测试、共享 crate
- Crate(单元包):一个模块树,可以产生一个 library 或可执行文件
- Module(模块):控制代码的组织、作用域、私有路径
- Path(路径):为 struct、function或module等项命名的方式
Package 和 Crate
Crate 的类型:
- binary
-library
Crate Root: - 源代码文件
- Rust 编译器从这里开始,组成 Crate 的根 Module
Package: - 包含 1 个 Cargo.toml,描述了如何构建这些 Crates
- 只能包含 0-1 个 library crate
- 可以包含任意数量的 binary crate
- 必须至少包含一个 crate(library 或 binary)
创建
cargo new project
Cargo.toml(配置文件)
[package]
name = "project"
version = "0.1.0"
edition = "2024"[dependencies]
main.rc(源代码文件、入口文件)
Cargo 的惯例
src/main.rs:
- binary crate 的 crate root
- crate 名与 package 名相同
src.lib.rs: - package 包含一个 library crate
- library crate 的 crate root
- crate 名与 package 名相同
一个 Package 可以同时包含 src/main.rs 和 src/lib.rs - 一个 binary crate,一个 library crate
- 名称与 package 名相同
一个 Package 可以有多个 binary crate: - 文件放在 src/bin
- 每个文件是单独的 binary crate
crate 的作用
将相关功能组合到一个作用域内,便于项目间进行共享
- 防止冲突
如:rand crate,访问它的功能需要通过它的名字:rand
定义 module 来控制作用域和私有性
Module:
- 在一个 crate 内,将代码进行分组
- 增加可读性,易于复用
- 控制项目(item)的私有性
建立 module: - mod 关键字
- 可嵌套
- 可包含其他项(struct、enum、常量、trait、函数)的定义
例:
mod front_of_house {mod hosting {fn add_to_eaitlist() {}fn seat_at_table() {}}mod srving {fn take_order() {}fn serve_order() {}fn take_payment() {}}
}
Module
src/main.rs 和 src/lib.rs 叫做 crate roots:
- 这两个文件(任意一个)的内容形成了名为 crate 的模块,位于整个模块树的根部
crate└── front_of_house├── hosting│ ├── add_to_waitlist│ └── seat_at_table└── serving├── serve_order├── take_order└── take_payment
(2)路径
为了在 Rust 的模块中找到某个条目,需要使用路径
路径的两种形式:
- 绝对路径:从 crate root 开始,使用 crate 名 或 字面值 crate
- 相对路径:从当前模块开始,使用 self、super 或当前模块标识符
路径至少由一个标识符组成,标识符之间使用 ::
例:
mod fount_of_house {mod hosting{fn add_to_waitlist() {}}
}pub fn eat_at_restaurant() {crate::front_of_house::hosting::add_to_waitlist();front_of_house::hosting::add_to_waitlist();
}
注:该例子无法正常运行,存在调用私有
私有边界
- 模块不仅可以组织代码,还可以定义私有边界
- 如果想把 函数 或 struct 等设为私有,可以将它放到某个模块中
- Rust 中所有的条目,(函数、方法、struct、enum、模块、常量)默认是私有的
- 父级模块无法访问子模块中的 私有条目
- 子模块里可以使用所有祖先模块中的条目
pub 关键字
将某些条目标记为公共的
mod fount_of_house {pub mod hosting{pub fn add_to_waitlist() {}}
}pub fn eat_at_restaurant() {crate::front_of_house::hosting::add_to_waitlist();//正常运行front_of_house::hosting::add_to_waitlist();
}
(3)super、pub struct、enum
super 关键字
super:用于访问父级目录,类似文件系统中的 …
fn serve_order() {}mod back_of_house {fn fix_incorrect_order() {cook_order();super::serve_order();}fn cook_order() {}
}
pub struct
声明 struct 为公共的
例:
mod back_of_house {pub struct Breakfast{pub xxx: String, //公有yyy: String, //私有}
}
pub struct:
- struct 是公共的
- struct 的字段默认是私有的
struct 的字段需要单独设置 pub 来变为公有
pub enum
pub 放在 enum 前:
- enum 是公共的
- enum 的变体也是公共的
例:
mod back_of_house {pub enum Appetizer {Soup,Salad,}
}
(4)use 关键字
可以使用 use 关键字将路径导入到作用域内
- 仍遵循私有性规则
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::add_to_waitlist();hosting::add_to_waitlist();
}
使用 use 指定相对路径
use 的习惯用法
- 函数:将函数的父级模块引入作用域(指定到父级)
- struct、enum,其他:指定完整路径(指定到本身)
例:(哈希表)
use std::collections::HashMap;fn main() {let mut map = HashMap::new();map.insert(1,2);
}
- 同名条目,指定到父级
use std::fmt;
use std::io;fn f1() -> fmt::Result {}
fn f2() -> io::Result {}fn main() {}
as 关键字
as 关键字可以为引入的路径指定本地的别名
use std::fmt::Result;
use std::io::Result as IoResult;fn f1() -> Result {}
fn f2() -> IoResult {}fn main() {}
使用 pub use 重新导出名称
- 使用 use 将路径导入到作用域后,该名称在此作用域是 私有 的
例:
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::add_to_waitlist();hosting::add_to_waitlist();
}
pub use:重导出
- 将条目引入作用域
- 该条目可以被外部代码引入到它们的作用域
使用外部包(package)
1、Cargo.toml 添加依赖的包
- https://crate.io/
2、use 将特定条目引入作用域
例:Cargo.toml引入 rand
[package]
name = "project"
version = "0.1.0"
edition = "2024"[dependencies]
rand = "0.5.5"
函数中引用
use rand::Rng;
标准库(std)也被当做外部包
- 不需要修改 Cargo.toml 来包含 std
- 需要使用 use 将 std 中的特定条目引入当前作用域
使用嵌套路径清理大量的 use 语句
如果使用同一个包或模块下的多个条目
可使用嵌套路径在同一行内进行多条引入
- 路径相同部分::{差异部分}
例:
情况一:
// use std::cmp::Ordering;
// use std::io;use std::{cmp::Ordering, io};情况二:
// use std::io;
// use std::io::Write;use std::io{self, Write};
通配符*
使用 * 可以把路径中所有的公共条目都引入到作用域
use std::collections::*;
(5)模块拆分
将模块内容移动至其他文件
模块定义时。如果模块名后边是“;” ,而不是代码块:
- Rust 会从与模块同名的文件中加载内容
- 模块树的结构不会变化
在同目录下找,再次拆分则需要创建文件夹
随着模块逐渐变大,该技术可以将模块的内容移动到其他文件中
八、通用集合类型
(1)Vector
使用 Vector 存储多个值
Vec<T>,叫做 vector
- 由标准库提供
- 可存储多个值
- 只能存储相同类型的数据
- 值在内存中连续存放
创建 Vector
Vec::new 函数
例:
fn main() {let v: Vec<i32> = Vec::new();
}
使用初始值创建 Vec<T>,使用 vec! 宏
fn main() {let v = vec![1, 2, 3];
}
更新 Vector
向 Vector 添加元素,使用 push 方法
例:
fn main() {let mut v = Vec::new();v.push(1);v.push(2);v.push(3);v.push(4);
}
删除 Vector
与其他 struct 一致,离开作用域后自动清理
例:
fn main() {let v = vec![1, 2, 3, 4];
}
读取 Vector 的元素
两种方式可以引用 Vector 里的元素
- 索引
- get 方法
例:
fn main() {let v = vec![1, 2, 3, 4, 5];//索引let third: &i32 = &v[2];println!("The third element is {}", third);//getmatch v.get(2) {Some(third) => println!("The third element is {}",third),None => println!("There is no third element"),}
}
索引 vs get 处理访问越界
- 索引:panic
- get:返回 None
所有权和借用规则
不能在同一作用域内同时拥有可变和不可变引用
遍历 Vector 中的值
for 循环:
例:
// 不可变
fn main() {let v = vec![100, 32, 57];for i in &v {println!("{}", i);}
}// 可变
fn main() {let mut v = vec![100, 32, 57];for i in &mut v {*i += 50;// *i 解引用 }for i in &v {println!("{}", i);}
}
使用 enum 来存储多种数据类型
- Enum 的变体可以附加不同类型的数据
- Enum 的变体定义在同一个 enum 类型下
例:
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),];
}
(2)String
概念: Byte 的集合,通过一些方法将 byte 解析为文本
字符串是什么
Rust 的核心语法层面,只有一个字符串类型:字符串切片 str(或 &str)
字符串切片:对存储在其他地方、UTF-8 编码的字符串的引用
- 字符串字面值:存储在二进制文件中,也是字符串切片
String 类型: - 来自 标准库 而不是 核心语言
- 可增长、可修改、可拥有
- UTF-8
通常所说的字符串
String 和 &str
- 标准库里用的多
- UTF-8 编码
Rust提供的其他字符串类型
OsString、OsStr、CString、CStr
- String vs Str 后缀:拥有或借用的变体
- 可存储不同编码的文本或在内存中以不同的形式展现
Library crate (第三方库)针对存储字符串可提供更多的选项
创建字符串(String)
Vec<T> 的操作大部分可用于 String
1、String::new() 函数
例:
fn main() {let mut s = String::new();
}
2、使用初始值来创建 String:
- to_string() 方法,可用于实现了 Display trait 的类型,包括字符串字面值
例:
fn main() {let data = "initial contents";let s = data.to_string();let s1 = "initial contents".to_string();
}
- String::from() 函数,从字面值创建 String
例:
fn main() {let s = String::from("initial contents");
}
更新 String
- push_str() 方法:把一个字符串切片附加到 String
例:
fn main() {let mut s = String::from("foo");s.push_str("bar");println!("{}", s); //&str
}
- push() 方法:把单个字符附加到 String
例:
fn main() {let mut s = String::from("lo");s.push('l');
}
- + 拼接字符串
例:
fn main() {let s1 = String::from("Hello,");let s2 = String::from("World!");let s3 = s1 + &s2;println!("{}",s3);//拼接后 s1 无法使用,s2 可以正常使用
}
- format!:连接多个字符串
例:
fn main() {let s1 = String::from("tic");let s2 = String::from("tac");let s3 = String::from("toe");//let s = s1 + "-" + &s2 + "-" + &s3;let s = format!("{}-{}-{}",s1,s2,s3);println!("{}", s);
}
String
类型不支持索引语法
内部表示
String 是对 Vec<T> 的包装
- len()方法(长度)
例:
fn main() {let len = String::from("Hola").len();println!("{}", len);
}
Bytes,Scalar Values,Grapheme Clusters
- 字节
- 标量值
- 字符簇
例:
let i = "xxxxxx";
i.bytes //字节
i.chars //标量值
Rust 不允许对 String 进行索引的最后一个原因:
- 索引操作应消耗一个常量时间(O(1))
- String 无法保证:需要遍历所有内容,确定有多少个合法字符
切割 String
使用 [] 和 一个范围 来创建字符串的切片
例:
fn main() {let hello = "abcdefghij";let s = &hello[0..4];
}
- 谨慎使用
- 如切割时跨越了字符边界,程序就会 panic
- (b1,b2),(b3,b4),(b5,b6),(b7,b8)
遍历 String 方法
标量值:chars()方法
字节:bytes()方法
注:
Rust 选择将正确处理 String 数据作为所有 Rust 程序的默认行为
- 程序员必须投入精力处理 UTF-8 数据
可防止在开发后期处理设计非 ASCII 字符的错误
(3)HashMap
HashMap<K,V>
键值对的形式存储数据,一个键(Key)对应一个值(Value)
Hash 函数:决定如何在内存中存放 K 和 V
使用场景:通过 K(任何类型)来寻找数据,而不是通过索引
创建 HashMap
创建空 HashMap: new() 函数
添加数据:insert() 方法
例:
use std::collections::HashMap;fn main() {// let mut scores: HashMap<String, i32> = HashMap::new();let mut scores = HashMap::new(); //必须声明类型scores.insert(String::from("Blue"), 10);
}
注:
- HashMap 用的较少,不在 Prelude(预导入) 中,需手动引入
- 标准库支持少,没有内置 宏 来创建 HashMap
- 数据存储在 heap 上
- 同构中,一个 HashMap中:
所有 K 必须同类型
所有 V 必须同类型
另一种创建方式:collect 方法
在元素类型为 Tuple 的 Vector 上使用 collect 方法,可以组建一个 HashMap:
- 要求 Tuple 的两个值:一个作为 K,一个作为 V
- collect 方法可以把数据整合成很多种集合类型,包括 HashMap
- 返回值需要显示指明类型
例:
- 返回值需要显示指明类型
use std::collecctions::HashMap;fn main() {let teams = vec![String::from("Blue"), String::from("Yellow")];let intial_scores = vec![10, 50];let scores: HashMap<_, _> = teams.iter().zip(intial_scores.iter()).collect();// zip() 合成一个元组
}
HashMap 和所有权
对于实现了 Copy trait 的类型(例如 i32),值会被复制到 HashMap 中
对于拥有所有权的值(例如 String),值会被移动,所有权会转移给 HashMap
例:
use std::collections::HashMap;fn main() {let field_name = String::from("Favorite color");let field_value = String::from("Blue");let mut map = HashMap::new();map.insert(field_name, field_value);//map.insert(&field_name, &field_value);// 没有直接获取所有权,后续可正常使用println!("{}: {}", field_name, field_value); //所有权移交,此处报错
}
如果将值的引用插入到 HashMap,值本身不会移动
- 在 HashMap 有效期间,被引用的值必须保持有效
访问 HashMap 中的值
get 方法
- 参数:K
- 返回:Option<&V>
例:
use std::collections::HashMap;fn main() {let main scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);let team_name = String::from("Blue");let score = scores.get(&team_name);match score {Some(s) => println!("{}", s),None => println!("team not exist"),}
}
遍历 HashMap
for 循环
例:
use std::collections::HashMap;fn main() {let main scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);for (k, v) in %scores {println!("{}: {}", k, v);}
}
更新HashMap<K, V>
HashMap 大小可变
每个 K 同时只能对应一个 V
更新 HashMap 中的数据:
1、K 已经存在,对应一个 V
- 替换现有的 V
- 保留现有的 V ,忽略新的 V
- 合并现有的 V 和新的 V
2、K 不存在 - 添加一对 K,V
替换现有的 V
向 HashMap 插入一对 K、V,再插入同样的 K,不同的 V,就会替换掉原来的 V
例:
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Blue"), 20);println!("{:?}", scores);
}
只在 K 不对应任何值的情况下,插入 V
entry 方法:检查指定的 K 是否对应一个 V
- 参数为 K
- 返回 enum Entry:代表值是否存在
例:
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.entry(String::from("Yellow")).or_insert(50);scores.entry(String::from("Blue")).or_insert(50);println!("{:?}", scores);
}
Entry 的 or_insert() 方法 :
- 如果 K 存在,返回到对应的 V 的一个可变引用
- 如果 K 不存在,将方法参数作为 K 的新值插入,返回该值的可变引用
基于现有 V 更新 V
例:
use std::collections::HashMap;fn main() {let text = "Hello world wonderful world";let mut map = HashMap::new();for word in text.split_whitespace() {let count = map.entry(word).or_insert(0);*count += 1;}println!{"{:#?}", map};
}
Hash 函数
默认情况下,HashMap 使用加密功能强大的 Hash 函数,可以抵抗拒绝服务攻击
- 不是最快的Hash算法
- 具有更好的安全性
可以指定不同的 hasher 来切换到另一个函数 - hasher 是实现 BuildHasher trait 的类型