Rust程序语言设计(5-8)

五、使用结构体来组织相关联的数据

struct ,结构体

  • 自定义的数据类型
  • 命名相关联的值,打包 -> 有意义的组合
(1)定义和实例化 struct
定义 struct
  • 使用 struct 关键字,并为整个 struct 命名
  • 花括号内,为所有 字段 定义名称和类型
    例:
rust 复制代码
struct User {
        username:String,
        email:String,
        age:i32,
        active:bool,
    }
实例化 struct

创建 struct 实例

  • 为每个字段指定具体值
  • 无需按照声明顺序
  • 无 mut 不可更改
rust 复制代码
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,
    };
}

取出一个值:

rust 复制代码
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 作为函数返回值

例:

rust 复制代码
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 实例来创建一个新的实例时使用

rust 复制代码
//如,仅更换几个参数
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

概念:定义类似 tuplestruct ,叫做 tuple struct

  • Tuple struct 整体有名,内部元素没有名
  • 适用于给 tuple 起名,并使它不同于其他 tuple
    定义 tuple structstruct [名字] (元素类型,元素类型,元素类型,...)
    例如:
rust 复制代码
struct Color(i32,i32,i32);
struct Point(i32,i32,i32);
let black = Color(0,0,0);
let origin = Point(0,0,0);

注: blackorigin 是不同的类型,是不同的 tuple struct 的实例。

Unit-Like Struct

概念:可以定义无字段的 struct ,叫做 Unit-Like struct

适用于需要在某个类型上实现某个 trait,但在里面没有想要存储的数据

struct 数据的所有权
rust 复制代码
struct User {
	username: String,
	email: String,
	age: i32,
	active: bool,
}

这里字段使用了 String 而不是 &str

  • struct 实例拥有其所有的数据
  • 只要 struct 实例是有效的,那么里面的字段数据也是有效的
    struct 里也可以存放引用,但这需要使用生命周
  • 生命周期保证只要 struct 实例是有效的,那么里面的引用也是有效的
  • 如果 struct 里面存储引用,而不使用生命周期,就会报错
(2)struct 示例
计算长方形面积

基本实现:

rust 复制代码
fn main() {
    let w = 30;
    let l = 50;
  
    println!("长方形的面积为:{}",area(w,l));
}
  
fn area(width: u32, length: u32) -> u32 {
    width * length
}

元组:

rust 复制代码
fn main() {
    let rect = (30,50);
    println!("长方形的面积为:{}",area(rect));
}
  
fn area(dim:(u32, u32)) -> u32 {  //元素没有名字,可读性差
    dim.0 * dim.1
}

结合 struct 增强代码可读性

rust 复制代码
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
}

验证所有权:

rust 复制代码
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:

rust 复制代码
#[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 (或 enumtrait 对象)的上下文中定义
  • 第一个参数是 self ,表示方法被调用的 struct 实例
    定义方法:
rust 复制代码
impl Rectangle {
    fn area(&self) -> u32 {
    self.width * self.length
    }
}

主函数中调用:

rust 复制代码
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 可以匹配方法的签名
    例如:
rust 复制代码
p1.distance(&p2);
(&p1).distance(&p2);
//两者表达效果相同

判断长方形能否包含另一个长方形

rust 复制代码
#[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 能否包含 rect2
    println!("{}",rect1.can_hold(&rect3));
    //判断rect1 能否包含 rect2
}
关联函数

概念:可以在 ipml 块里定义不把 self 作为第一参数的函数,叫做关联函数

  • 例如:String::from()
    关联函数通常用于构造器
    例:正方形
rust 复制代码
fn square(size: u32) -> Rectangle {
	Rectangle {
		width: size,
		length: size,
	}
}

输出正方形面积:

rust 复制代码
let s = Rectangle::square(50);
println!("正方形的面积是:{}",s.area());

:: 符号:

  • 关联函数
  • 模块创建的命名空间
多个 impl 块

每个 struct 允许拥有多个 impl

六、枚举与模式匹配

(1)定义枚举
定义枚举

IP地址:IPv4、IPv6

rust 复制代码
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
  • 每个变体可以拥有不同的类型以及关联的数据量
    例:
rust 复制代码
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"),
    };
}

更改为:

rust 复制代码
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)
rust 复制代码
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>

标准库中的定义:

rust 复制代码
enum Option<T> {
	Some(T),
	None
}

包含于 预导入模块 中:

  • Option<T>
  • Some(T)
  • None
    例:
rust 复制代码
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
  • 允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码
  • 模式可以是字面值、变量名、通配符等
rust 复制代码
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 中提取值
rust 复制代码
#[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>
rust 复制代码
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 可能性

rust 复制代码
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),
    }
}

运行结果

如,没有全部列出的必要的情况,可用 _ 代替其余值,必须在最后

rust 复制代码
1 => println!(1),
2 => println!(2),
3 => println!(3),
_ => (),
(4)简单控制流 if let
  • 处理只关心一种匹配而忽略其它匹配的情况
  • 更少的代码、缩进、以及模板
  • 放弃了穷举
    例:对比
rust 复制代码
fn main() {
	let v = Some(0u8);
	match v {
		Some(3) => println!("three"),
		_ => println!("others"),
	}
	
	if let Some(3) = v {
		println!("three");
	}
}
if let 搭配 else
rust 复制代码
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(配置文件)

rust 复制代码
[package]
name = "project"
version = "0.1.0"
edition = "2024"

[dependencies]

main.rc(源代码文件、入口文件)

Cargo 的惯例

src/main.rs

  • binary cratecrate root
  • crate 名与 package 名相同
    src.lib.rs
  • package 包含一个 library crate
  • library cratecrate root
  • crate 名与 package 名相同
    一个 Package 可以同时包含 src/main.rssrc/lib.rs
  • 一个 binary crate ,一个 library crate
  • 名称与 package 名相同
    一个 Package 可以有多个 binary crate
  • 文件放在 src/bin
  • 每个文件是单独的 binary crate
crate 的作用

将相关功能组合到一个作用域内,便于项目间进行共享

  • 防止冲突
    如:rand crate,访问它的功能需要通过它的名字:rand
定义 module 来控制作用域和私有性

Module

  • 在一个 crate 内,将代码进行分组
  • 增加可读性,易于复用
  • 控制项目(item )的私有性
    建立 module
  • mod 关键字
  • 可嵌套
  • 可包含其他项(structenum常量trait函数 )的定义
    例:
rust 复制代码
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.rssrc/lib.rs 叫做 crate roots

  • 这两个文件(任意一个)的内容形成了名为 crate 的模块,位于整个模块树的根部
tree 复制代码
 crate
    └── front_of_house
        ├── hosting
        │   ├── add_to_waitlist
        │   └── seat_at_table
        └── serving
            ├── serve_order
            ├── take_order
            └── take_payment
(2)路径

为了在 Rust 的模块中找到某个条目,需要使用路径

路径的两种形式:

  • 绝对路径:从 crate root 开始,使用 crate 名 或 字面值 crate
  • 相对路径:从当前模块开始,使用 selfsuper 或当前模块标识符
    路径至少由一个标识符组成,标识符之间使用 ::
    例:
rust 复制代码
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 关键字

将某些条目标记为公共的

rust 复制代码
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:用于访问父级目录,类似文件系统中的 ...

rust 复制代码
fn serve_order() {}

mod back_of_house {
	fn fix_incorrect_order() {
		cook_order();
		super::serve_order();
	}
	
	fn cook_order() {}
}
pub struct

声明 struct 为公共的

例:

rust 复制代码
mod back_of_house {
	pub struct Breakfast{
		pub xxx: String,  //公有
		yyy: String,    //私有
	}
}

pub struct:

  • struct 是公共的
  • struct 的字段默认是私有的
    struct 的字段需要单独设置 pub 来变为公有
pub enum

pub 放在 enum 前:

  • enum 是公共的
  • enum 的变体也是公共的
    例:
rust 复制代码
mod back_of_house {
	pub enum Appetizer {
		Soup,
		Salad,
	}
}
(4)use 关键字

可以使用 use 关键字将路径导入到作用域内

  • 仍遵循私有性规则
rust 复制代码
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,其他:指定完整路径(指定到本身)
    例:(哈希表)
rust 复制代码
use std::collections::HashMap;

fn main() {
	let mut map = HashMap::new();
	map.insert(1,2);
}
  • 同名条目,指定到父级
rust 复制代码
use std::fmt;
use std::io;

fn f1() -> fmt::Result {}
fn f2() -> io::Result {}

fn main() {}
as 关键字

as 关键字可以为引入的路径指定本地的别名

rust 复制代码
use std::fmt::Result;
use std::io::Result as IoResult;

fn f1() -> Result {}
fn f2() -> IoResult {}

fn main() {}
使用 pub use 重新导出名称
  • 使用 use 将路径导入到作用域后,该名称在此作用域是 私有 的
    例:
rust 复制代码
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
rust 复制代码
[package]
name = "project"
version = "0.1.0"
edition = "2024"

[dependencies]
rand = "0.5.5"

函数中引用

rust 复制代码
use rand::Rng;

标准库(std)也被当做外部包

  • 不需要修改 Cargo.toml 来包含 std
  • 需要使用 use 将 std 中的特定条目引入当前作用域
使用嵌套路径清理大量的 use 语句

如果使用同一个包或模块下的多个条目

可使用嵌套路径在同一行内进行多条引入

  • 路径相同部分::{差异部分}
    例:
rust 复制代码
情况一:
// use std::cmp::Ordering;
// use std::io;

use std::{cmp::Ordering, io};

情况二:
// use std::io;
// use std::io::Write;

use std::io{self, Write};
通配符*

使用 * 可以把路径中所有的公共条目都引入到作用域

rust 复制代码
use std::collections::*;
(5)模块拆分
将模块内容移动至其他文件

模块定义时。如果模块名后边是";" ,而不是代码块:

  • Rust 会从与模块同名的文件中加载内容
  • 模块树的结构不会变化

在同目录下找,再次拆分则需要创建文件夹

随着模块逐渐变大,该技术可以将模块的内容移动到其他文件中

八、通用集合类型

(1)Vector
使用 Vector 存储多个值

Vec<T> ,叫做 vector

  • 由标准库提供
  • 可存储多个值
  • 只能存储相同类型的数据
  • 值在内存中连续存放
创建 Vector

Vec::new 函数

例:

rust 复制代码
fn main() {
	let v: Vec<i32> = Vec::new();
}

使用初始值创建 Vec<T> ,使用 vec!

rust 复制代码
fn main() {
	let v = vec![1, 2, 3];
}
更新 Vector

Vector 添加元素,使用 push 方法

例:

rust 复制代码
fn main() {
	let mut v = Vec::new();
	
	v.push(1);
	v.push(2);
	v.push(3);
	v.push(4);
}
删除 Vector

与其他 struct 一致,离开作用域后自动清理

例:

rust 复制代码
fn main() {
	let v = vec![1, 2, 3, 4];
}
读取 Vector 的元素

两种方式可以引用 Vector 里的元素

  • 索引
  • get 方法
    例:
rust 复制代码
fn main() {
	let v = vec![1, 2, 3, 4, 5];
	//索引
	let third: &i32 = &v[2];
	println!("The third element is {}", third);
	
	//get
	match 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 循环:

例:

rust 复制代码
// 不可变
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 类型下
    例:
rust 复制代码
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() 函数

例:

rust 复制代码
fn main() {
	let mut s = String::new();
}

2、使用初始值来创建 String

  • to_string() 方法,可用于实现了 Display trait 的类型,包括字符串字面值
    例:
rust 复制代码
fn main() {
	let data = "initial contents";
	let s = data.to_string();
	
	let s1 = "initial contents".to_string();
}
  • String::from() 函数,从字面值创建 String
    例:
rust 复制代码
fn main() {
	let s = String::from("initial contents");
}
更新 String
  • push_str() 方法:把一个字符串切片附加到 String
    例:
rust 复制代码
fn main() {
	let mut s = String::from("foo");
	s.push_str("bar");
	
	println!("{}", s);  //&str
}
  • push() 方法:把单个字符附加到 String
    例:
rust 复制代码
fn main() {
	let mut s = String::from("lo");
	s.push('l');
}
  • + 拼接字符串
    例:
rust 复制代码
fn main() {
	let s1 = String::from("Hello,");
	let s2 = String::from("World!");
	
	let s3 = s1 + &s2;
	
	println!("{}",s3);
	//拼接后 s1 无法使用,s2 可以正常使用
}
  • format! :连接多个字符串
    例:
rust 复制代码
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()方法(长度)
    例:
rust 复制代码
fn main() {
	let len = String::from("Hola").len();
	
	println!("{}", len);
}
Bytes,Scalar Values,Grapheme Clusters
  • 字节
  • 标量值
  • 字符簇
    例:
rust 复制代码
let i = "xxxxxx";
i.bytes   //字节
i.chars   //标量值

Rust 不允许对 String 进行索引的最后一个原因:

  • 索引操作应消耗一个常量时间(O(1))
  • String 无法保证:需要遍历所有内容,确定有多少个合法字符
切割 String

使用 []一个范围 来创建字符串的切片

例:

rust 复制代码
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 函数:决定如何在内存中存放 KV

使用场景:通过 K(任何类型)来寻找数据,而不是通过索引

创建 HashMap

创建空 HashMap: new() 函数

添加数据:insert() 方法

例:

rust 复制代码
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 方法

在元素类型为 TupleVector 上使用 collect 方法,可以组建一个 HashMap

  • 要求 Tuple 的两个值:一个作为 K ,一个作为 V
  • collect 方法可以把数据整合成很多种集合类型,包括 HashMap
    • 返回值需要显示指明类型
      例:
rust 复制代码
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

例:

rust 复制代码
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>
    例:
rust 复制代码
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 循环

例:

rust 复制代码
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 不存在
  • 添加一对 KV
替换现有的 V

HashMap 插入一对 KV ,再插入同样的 K ,不同的 V ,就会替换掉原来的 V

例:

rust 复制代码
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:代表值是否存在
    例:
rust 复制代码
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);
}

Entryor_insert() 方法 :

  • 如果 K 存在,返回到对应的 V 的一个可变引用
  • 如果 K 不存在,将方法参数作为 K 的新值插入,返回该值的可变引用
基于现有 V 更新 V

例:

rust 复制代码
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 的类型
相关推荐
道可到3 小时前
35 岁程序员的绝地求生计划:你准备好了吗?
前端·后端·面试
90后的晨仔3 小时前
redis 警告 WARNING: The TCP backlog xxxx
后端
道可到3 小时前
国内最难入职的 IT 公司排行:你敢挑战哪一家?
前端·后端·面试
缓存征服者4 小时前
使用周期性线程池实现流量平滑,我将Redis并发从300+降到1
后端
深圳蔓延科技4 小时前
单点登录到底是什么?
java·后端
道可到4 小时前
程序员养生十大违章:你中了几条?
前端·后端·面试
SimonKing4 小时前
除了 ${},Thymeleaf 的这些用法让你直呼内行
java·后端·程序员
间彧4 小时前
Java拦截器与过滤器的区别及生命周期分析
后端
XXX-X-XXJ4 小时前
二:RAG 的 “语义密码”:向量、嵌入模型与 Milvus 向量数据库实操
人工智能·git·后端·python·django·milvus