Rust 学习
Rust 官网:https://www.rust-lang.org/zh-CN/
模块 库:https://crates.io/
1、Rust 简介
Rust 语言的主要目标之一是解决传统 系统级编程语言(如 C 和 C++)中常见的安全性问题,例如空指针引用、数据竞争等。为了实现这个目标,Rust 引入了一种称为 "所有权" 的概念,通过静态检查来确保内存安全和线程安全。此外,Rust 还具有其他一些特性,如模式匹配、代数数据类型、函数式编程风格的特性(如闭包和高阶函数)等。它还提供了丰富的标准库和包管理器 Cargo,使得开发者可以轻松构建和管理他们的项目。
Rust 是一门注重安全(safety)、速度(speed)和并发(concurrency)的现代系统编程语言。Rust 通过内存安全来实现以上目标,但不使用垃圾回收机制(garbage collection, GC)。
Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。
Rust 特点
- 高性能:Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。
- 可靠性:Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。
- 生产力:Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, 还集成了一流的工具——包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持, 以及自动格式化代码等等。
Rust 相关概念
- channel:Rust 会发布3个不同版本:stable、beta、nightly。
stable:Rust 的稳定版本,每 6 周发布一次。
beta:Rust 的公开测试版本,将是下一个 stable 版本。
nightly:每天更新,包含以一些实验性的新特性。 - toolchain:一套 Rust 组件,包括编译器及其相关工具,并且包含 channel,版本及支持的平台信息。
- target:指编译的目标平台,即:编译后的程序在哪种操作系统上运行。
- component (组件):toolchain 是由 component 组成的。查看所有可用和已经安装的组件命令如下:rustup component list。rustup 默认安装的组件:
rustc:Rust 编译器。
rust-std:Rust 标准库。
cargo:包管理和构建工具。
rust-docs:Rust 文档。
rustfmt:用来格式化 Rust 源代码。
clippy:Rust 的代码检查工具。 - profile:为了方便对 component 进行管理,使用 profile 定义一组 component。不同的 profile 包含不同的组件,安装 rustup 时有三种 profile 可选,修改 profile 命令如下:rustup set profile minimal
- Rustup 是什么:Rustup 是 Rust安装器和版本管理工具。安装 Rust 的主要方式是通过 Rustup 这一工具,它既是一个 Rust 安装器又是一个版本管理工具。Rust 的升级非常频繁。运行
rustup update
获取最新版本的 Rust。文档:https://rust-lang.github.io/rustup/ - Cargo 是什么:Cargo 是 Rust 的 构建工具 和 包管理器。安装 Rustup 时会自动安装。Cargo 可以做很多事情:
cargo build 可以构建项目
cargo run 可以运行项目
cargo test 可以测试项目
cargo doc 可以为项目构建文档
cargo publish 可以将库发布到 crates.io。
检查是否安装了 Rust 和 Cargo,可以在终端中运行:cargo --version
下载、安装
下载:https://www.rust-lang.org/tools/install
安装:https://www.rust-lang.org/zh-CN/learn/get-started
默认情况,Rust 依赖 C++ build tools,没有安装也关系。安装过程需要保证网络正常。
在 Rust 开发环境中,所有工具都安装在 ~/.cargo/bin 目录中,可以在这里找到包括 rustc、cargo 和 rustup 在内的 Rust 工具链。在安装过程中,rustup 会尝试配置 PATH,如果 rustup 对 PATH 的修改不生效,可以手动添加路径到 PATH
~/.cargo/bin
~/.rustup/bin
以下是一些常用的命令:
rustup 相关
rustup -h # 查看帮助
rustup show # 显示当前安装的工具链信息
rustup update # 检查安装更新
rustup self uninstall # 卸载
rustup default stable-x86_64-pc-windows-gnu # 设置当前默认工具链rustup toolchain list # 查看工具链
rustup toolchain install stable-x86_64-pc-windows-gnu # 安装工具链
rustup toolchain uninstall stable-x86_64-pc-windows-gnu # 卸载工具链
rustup toolchain link <toolchain-name> "<toolchain-path>" # 设置自定义工具链rustup override list # 查看已设置的默认工具链
rustup override set <toolchain> --path <path> # 设置该目录以及其子目录的默认工具链
rustup override unset --path <path> # 取消目录以及其子目录的默认工具链rustup target list # 查看目标列表
rustup target add <target> # 安装目标
rustup target remove <target> # 卸载目标
rustup target add --toolchain <toolchain> <target> # 为特定工具链安装目标rustup component list # 查看可用组件
rustup component add <component> # 安装组件
rustup component remove <component> # 卸载组件
rustc 相关
rustc --version # 查看rustc版本
cargo 相关
cargo --version # 查看cargo版本
cargo new <project_name> # 新建项目
cargo build # 构建项目
cargo run # 运行项目
cargo check # 检查项目
cargo -h # 查看帮助
配置工具链安装位置
在系统环境变量中添加如下变量:
CARGO_HOME 指定 cargo 的安装目录
RUSTUP_HOME 指定 rustup 的安装目录
默认分别安装到用户目录下的.cargo 和.rustup 目录
配置国内镜像
配置 rustup 国内镜像。在系统环境变量中添加如下变量(选一个就可以,可以组合):
# 清华大学
RUSTUP_DIST_SERVER:https://mirrors.tuna.tsinghua.edu.cn/rustup
RUSTUP_UPDATE_ROOT:https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup# 中国科学技术大学
RUSTUP_DIST_SERVER:https://mirrors.ustc.edu.cn/rust-static
RUSTUP_UPDATE_ROOT:https://mirrors.ustc.edu.cn/rust-static/rustup
配置 cargo 国内镜像。在 cargo 安装目录下新建 config 文件(注意 config 没有任何后缀),文件内容如下:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'tuna'# 清华大学
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"# 中国科学技术大学
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
# 设置代理
[http]
proxy = "127.0.0.1:8889"
[https]
proxy = "127.0.0.1:8889"
Windows 交叉编译 Linux 程序。目标服务器是 Linux(CentOS 7) 64bit
, 所以我们添加的 target 应该是x86_64-unknown-linux-gnu
(动态依赖) 或者x86_64-unknown-linux-musl
(静态依赖)
- 动态依赖:目标服务器需要包含动态依赖的相关库(用户共享库)
- 静态依赖,目标服务器不需要包含相应的库,但是打包文件会更大些
1). 添加需要的 target
rustup target add x86_64-unknown-linux-musl
2). 在 cargo 安装目录下新建 config 文件(注意 config 没有任何后缀),添加的文件内容如下:
[target.x86_64-unknown-linux-musl]
linker = "rust-lld"
3). 构建
cargo build --target x86_64-unknown-linux-musl
示例:
创建新项目
用 Cargo 创建一个新项目。在终端中执行:cargo new hello-rust,会生成一个名为 hello-rust
的新目录,其中包含以下文件:
Cargo.toml
为 Rust 的清单文件。其中包含了项目的元数据和依赖库。src/main.rs
为编写应用代码的地方。
进入新创建的目录中,执行命令运行此程序:cargo run
添加 依赖
现在来为程序添加依赖。可以在 crates.io,即 Rust 包的仓库中找到所有类别的库。在 Rust 中通常把 "包" 称作 "crates"。在本项目中,使用了名为 ferris-says 的库。
在 Cargo.toml
文件中添加以下信息(从 crate 页面上获取):
[dependencies]
ferris-says = "0.3.1"
下载 依赖
运行:cargo build , Cargo 就会安装该依赖。运行 build 会创建一个新文件
Cargo.lock
,该文件记录了本地所用依赖库的精确版本。
使用 依赖
使用该依赖库:可以打开
main.rs
,删除其中所有的内容(它不过是个示例而已),然后在其中添加下面这行代码:use ferris_says::say;这样就可以使用
ferris-says
crate 中导出的say
函数了。
完整 Rust 示例
现在用上面的依赖库编写一个小应用。在 main.rs
中添加以下代码:
use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};fn main() {let stdout = stdout();let message = String::from("Hello fellow Rustaceans!");let width = message.chars().count();let mut writer = BufWriter::new(stdout.lock());say(&message, width, &mut writer).unwrap();
}
保存完毕后,执行命令运行程序:cargo run
成功执行后,会打印一个字符形式的螃蟹图案。
Ferris ( 费理斯 ) 是 Rust 社区的非官方吉祥物。
2、Rust 相关文档
:https://www.rust-lang.org/zh-CN/learn
核心文档
以下所有文档都可以用 rustup doc
命令在本地阅读,它会在浏览器中离线打开这些资源!
标准库
详尽的 Rust 标准库 API 手册。:https://doc.rust-lang.org/std/index.html
版本指南
Rust 版本指南。:https://doc.rust-lang.org/edition-guide/index.html
CARGO 手册
Rust 的包管理器和构建系统。:https://doc.rust-lang.org/cargo/index.html
RUSTDOC 手册
学习如何为 crate 编写完美的文档。:https://doc.rust-lang.org/rustdoc/index.html
RUSTC 手册
熟悉 Rust 编译器中可用的选项。:https://doc.rust-lang.org/rustc/index.html
编译错误索引表
深入解释遇到的编译错误。:https://doc.rust-lang.org/error_codes/error-index.html
Rust 程序
命令行 程序
用 Rust 构建高效的命令行应用。:https://rust-cli.github.io/book/index.html
WEBASSEMBLY 手册
通过 WebAssembly 用 Rust 构建浏览器原生的库。:https://rustwasm.github.io/docs/book/
嵌入式手册
Rust 编写嵌入式程序。:https://doc.rust-lang.org/stable/embedded-book/
Learn X in Y
// 这是注释,单行注释...
/* ...这是多行注释 *////
// 1. 基础 //
///// 函数 (Functions)
// `i32` 是有符号 32 位整数类型(32-bit signed integers)
fn add2(x: i32, y: i32) -> i32 {// 隐式返回 (不要分号)x + y
}// 主函数(Main function)
fn main() {// 数字 (Numbers) //// 不可变绑定let x: i32 = 1;// 整形/浮点型数 后缀let y: i32 = 13i32;let f: f64 = 1.3f64;// 类型推导// 大部分时间,Rust 编译器会推导变量类型,所以不必把类型显式写出来。// 这个教程里面很多地方都显式写了类型,但是只是为了示范。// 绝大部分时间可以交给类型推导。let implicit_x = 1;let implicit_f = 1.3;// 算术运算let sum = x + y + 13;// 可变变量let mut mutable = 1;mutable = 4;mutable += 2;// 字符串 (Strings) //// 字符串字面量let x: &str = "hello world!";// 输出println!("{} {}", f, x); // 1.3 hello world// 一个 `String` – 在堆上分配空间的字符串let s: String = "hello world".to_string();// 字符串分片(slice) - 另一个字符串的不可变视图// 基本上就是指向一个字符串的不可变指针,它不包含字符串里任何内容,只是一个指向某个东西的指针// 比如这里就是 `s`let s_slice: &str = &s;println!("{} {}", s, s_slice); // hello world hello world// 数组 (Vectors/arrays) //// 长度固定的数组 (array)let four_ints: [i32; 4] = [1, 2, 3, 4];// 变长数组 (vector)let mut vector: Vec<i32> = vec![1, 2, 3, 4];vector.push(5);// 分片 - 某个数组(vector/array)的不可变视图// 和字符串分片基本一样,只不过是针对数组的let slice: &[i32] = &vector;// 使用 `{:?}` 按调试样式输出println!("{:?} {:?}", vector, slice); // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]// 元组 (Tuples) //// 元组是固定大小的一组值,可以是不同类型let x: (i32, &str, f64) = (1, "hello", 3.4);// 解构 `let`let (a, b, c) = x;println!("{} {} {}", a, b, c); // 1 hello 3.4// 索引println!("{}", x.1); // hello//// 2. 类型 (Type) ////// 结构体(Sturct)struct Point {x: i32,y: i32,}let origin: Point = Point { x: 0, y: 0 };// 匿名成员结构体,又叫“元组结构体”(‘tuple struct’)struct Point2(i32, i32);let origin2 = Point2(0, 0);// 基础的 C 风格枚举类型(enum)enum Direction {Left,Right,Up,Down,}let up = Direction::Up;// 有成员的枚举类型enum OptionalI32 {AnI32(i32),Nothing,}let two: OptionalI32 = OptionalI32::AnI32(2);let nothing = OptionalI32::Nothing;// 泛型 (Generics) //struct Foo<T> { bar: T }// 这个在标准库里面有实现,叫 `Option`enum Optional<T> {SomeVal(T),NoVal,}// 方法 (Methods) //impl<T> Foo<T> {// 方法需要一个显式的 `self` 参数fn get_bar(self) -> T {self.bar}}let a_foo = Foo { bar: 1 };println!("{}", a_foo.get_bar()); // 1// 接口(Traits) (其他语言里叫 interfaces 或 typeclasses) //trait Frobnicate<T> {fn frobnicate(self) -> Option<T>;}impl<T> Frobnicate<T> for Foo<T> {fn frobnicate(self) -> Option<T> {Some(self.bar)}}let another_foo = Foo { bar: 1 };println!("{:?}", another_foo.frobnicate()); // Some(1)///// 3. 模式匹配 (Pattern matching) /////let foo = OptionalI32::AnI32(1);match foo {OptionalI32::AnI32(n) => println!("it’s an i32: {}", n),OptionalI32::Nothing => println!("it’s nothing!"),}// 高级模式匹配struct FooBar { x: i32, y: OptionalI32 }let bar = FooBar { x: 15, y: OptionalI32::AnI32(32) };match bar {FooBar { x: 0, y: OptionalI32::AnI32(0) } =>println!("The numbers are zero!"),FooBar { x: n, y: OptionalI32::AnI32(m) } if n == m =>println!("The numbers are the same"),FooBar { x: n, y: OptionalI32::AnI32(m) } =>println!("Different numbers: {} {}", n, m),FooBar { x: _, y: OptionalI32::Nothing } =>println!("The second number is Nothing!"),}///// 4. 流程控制 (Control flow) /////// `for` 循环let array = [1, 2, 3];for i in array {println!("{}", i);}// 区间 (Ranges)for i in 0u32..10 {print!("{} ", i);}println!("");// 输出 `0 1 2 3 4 5 6 7 8 9 `// `if`if 1 == 1 {println!("Maths is working!");} else {println!("Oh no...");}// `if` 可以当表达式let value = if true {"good"} else {"bad"};// `while` 循环while 1 == 1 {println!("The universe is operating normally.");}// 无限循环loop {println!("Hello!");}// 5. 内存安全和指针 (Memory safety & pointers) //// 独占指针 (Owned pointer) - 同一时刻只能有一个对象能“拥有”这个指针// 意味着 `Box` 离开他的作用域后,会被安全地释放let mut mine: Box<i32> = Box::new(3);*mine = 5; // 解引用// `now_its_mine` 获取了 `mine` 的所有权。换句话说,`mine` 移动 (move) 了let mut now_its_mine = mine;*now_its_mine += 2;println!("{}", now_its_mine); // 7// println!("{}", mine); // 编译报错,因为现在 `now_its_mine` 独占那个指针// 引用 (Reference) – 引用其他数据的不可变指针// 当引用指向某个值,我们称为“借用”这个值,因为是被不可变的借用,所以不能被修改,也不能移动// 借用一直持续到生命周期结束,即离开作用域let mut var = 4;var = 3;let ref_var: &i32 = &var;println!("{}", var); //不像 `mine`, `var` 还可以继续使用println!("{}", *ref_var);// var = 5; // 编译报错,因为 `var` 被借用了// *ref_var = 6; // 编译报错,因为 `ref_var` 是不可变引用// 可变引用 (Mutable reference)// 当一个变量被可变地借用时,也不可使用let mut var2 = 4;let ref_var2: &mut i32 = &mut var2;*ref_var2 += 2;println!("{}", *ref_var2); // 6// var2 = 2; // 编译报错,因为 `var2` 被借用了
}
rust 打印占位符
在 Rust 中,打印的占位符由格式化宏提供,最常用的是 println!
和 format!
。下面是一些常见的占位符及其用法:
-
{}
:默认占位符,根据值的类型自动选择合适的显示方式。 -
{:?}
:调试占位符,用于打印调试信息。通常用于Debug
trait 的实现。 -
{:#?}
:类似于{:?}
,但打印出更具可读性的格式化调试信息,可以嵌套显示结构体和枚举的字段。 -
{x}
:将变量x
的值插入到占位符的位置。 -
{x:format}
:将变量x
按照指定的格式进行格式化输出。例如,{x:?}",
{x:b},
{x:e}` 等。
这只是一小部分常见的占位符用法,你还可以根据需要使用其他格式化选项。Rust 的格式化宏提供了非常灵活和强大的格式化功能,可以满足大多数打印需求。
fn main() {let name = "Alice";let age = 25;let height = 1.65;println!("Name: {}", name);println!("Age: {}", age);println!("Height: {:.2}", height); // 格式化为小数点后两位let point = (3, 5);println!("Point: {:?}", point);
}
打印 "枚举、结构体"
#[derive(Debug)]
enum MyEnum {Variant1,Variant2(u32),Variant3 { name: String, age: u32 },
}#[derive(Debug)]
struct MyStruct{field_1: String,field_2: usize,
}impl MyStruct {fn init_field(&self){let name = &self.field_1;let age = self.field_2;println!("{name} ---> {age}")}
}fn main() {let my_enum = MyEnum::Variant2(42);println!("{:?}", my_enum);let my_struct = MyStruct{field_1: String::from("king"),field_2: 100};my_struct.init_field();println!("{:?}", my_struct);
}
3、Rust 程序设计语言
英文文档:https://doc.rust-lang.org/book/
中文文档:https://kaisery.github.io/trpl-zh-cn/
《Rust 程序设计语言》被亲切地称为“圣经”。给出了 Rust 语言的概览。在阅读的过程中构建几个项目,读完后,就能扎实地掌握 Rust 语言。
- 1. 入门指南
-
- 1.1. 安装
- 1.2. Hello, World!
- 1.3. Hello, Cargo!
- 2. 写个猜数字游戏
- 3. 常见编程概念
-
- 3.1. 变量与可变性
- 3.2. 数据类型
- 3.3. 函数
- 3.4. 注释
- 3.5. 控制流
- 4. 认识所有权
-
- 4.1. 什么是所有权?
- 4.2. 引用与借用
- 4.3. Slice 类型
- 5. 使用结构体组织相关联的数据
-
- 5.1. 结构体的定义和实例化
- 5.2. 结构体示例程序
- 5.3. 方法语法
- 6. 枚举和模式匹配
-
- 6.1. 枚举的定义
- 6.2. match 控制流结构
- 6.3. if let 简洁控制流
- 7. 使用包、Crate 和模块管理不断增长的项目
-
- 7.1. 包和 Crate
- 7.2. 定义模块来控制作用域与私有性
- 7.3. 引用模块项目的路径
- 7.4. 使用 use 关键字将路径引入作用域
- 7.5. 将模块拆分成多个文件
- 8. 常见集合
-
- 8.1. 使用 Vector 储存列表
- 8.2. 使用字符串储存 UTF-8 编码的文本
- 8.3. 使用 Hash Map 储存键值对
- 9. 错误处理
-
- 9.1. 用 panic! 处理不可恢复的错误
- 9.2. 用 Result 处理可恢复的错误
- 9.3. 要不要 panic!
- 10. 泛型、Trait 和生命周期
-
- 10.1. 泛型数据类型
- 10.2. Trait:定义共同行为
- 10.3. 生命周期确保引用有效
- 11. 编写自动化测试
-
- 11.1. 如何编写测试
- 11.2. 控制测试如何运行
- 11.3. 测试的组织结构
- 12. 一个 I/O 项目:构建命令行程序
-
- 12.1. 接受命令行参数
- 12.2. 读取文件
- 12.3. 重构以改进模块化与错误处理
- 12.4. 采用测试驱动开发完善库的功能
- 12.5. 处理环境变量
- 12.6. 将错误信息输出到标准错误而不是标准输出
- 13. Rust 中的函数式语言功能:迭代器与闭包
-
- 13.1. 闭包:可以捕获其环境的匿名函数
- 13.2. 使用迭代器处理元素序列
- 13.3. 改进之前的 I/O 项目
- 13.4. 性能比较:循环对迭代器
- 14. 更多关于 Cargo 和 Crates.io 的内容
-
- 14.1. 采用发布配置自定义构建
- 14.2. 将 crate 发布到 Crates.io
- 14.3. Cargo 工作空间
- 14.4. 使用 cargo install 安装二进制文件
- 14.5. Cargo 自定义扩展命令
- 15. 智能指针
-
- 15.1. 使用Box<T> 指向堆上数据
- 15.2. 使用Deref Trait 将智能指针当作常规引用处理
- 15.3. 使用Drop Trait 运行清理代码
- 15.4. Rc<T> 引用计数智能指针
- 15.5. RefCell<T> 与内部可变性模式
- 15.6. 引用循环会导致内存泄漏
- 16. 无畏并发
-
- 16.1. 使用线程同时地运行代码
- 16.2. 使用消息传递在线程间通信
- 16.3. 共享状态并发
- 16.4. 使用Sync 与 Send Traits 的可扩展并发
- 17. Rust 的面向对象编程特性
-
- 17.1. 面向对象语言的特点
- 17.2. 为使用不同类型的值而设计的 trait 对象
- 17.3. 面向对象设计模式的实现
- 18. 模式与模式匹配
-
- 18.1. 所有可能会用到模式的位置
- 18.2. Refutability(可反驳性): 模式是否会匹配失效
- 18.3. 模式语法
- 19. 高级特征
-
- 19.1. 不安全的 Rust
- 19.2. 高级 trait
- 19.3. 高级类型
- 19.4. 高级函数与闭包
- 19.5. 宏
- 20. 最后的项目:构建多线程 web server
-
- 20.1. 建立单线程 web server
- 20.2. 将单线程 server 变为多线程 server
- 20.3. 优雅停机与清理
- 21. 附录
-
- 21.1. A - 关键字
- 21.2. B - 运算符与符号
- 21.3. C - 可派生的 trait
- 21.4. D - 实用开发工具
- 21.5. E - 版本
- 21.6. F - 本书译本
- 21.7. G - Rust 是如何开发的与 “Nightly Rust”
Rust 数据类型
Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。
标量、复合
Rust 2大类数据类型:
- 标量(scalar):代表一个单独的值。4种基本的标量:整型、浮点型、布尔类型、字符。字符串(
String
)类型由 Rust 标准库提供,而不是编入核心语言。在 Rust 中,"字符串字面值" 使用双引号括起来,例如:"Hello, World!"。这是一种字符串类型的常量表示方法。而普通的字符串类型则是指动态可变的字符串,即 String 类型。字符串字面值是静态不可变的,不能修改其中的内容。你可以直接使用字符串字面值进行一些简单的操作,如拼接、切割等,但无法修改它们的值。字符串字面值就是 String 的 slice - 复合(compound):Rust 有两个原生的复合类型:元组(tuple)、数组(array)。
元组 是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
另一个包含多个值的方式是 数组(array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust 中的数组长度是固定的。
Rust 标准库中包含一系列被称为 集合(collections)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。
三个在 Rust 程序中被广泛使用的集合:
- vector 一个挨着一个地储存一系列数量可变的值。为了创建一个新的空 vector,可以调用 Vec::new 函数,会用初始值来创建一个 Vec<T> 而 Rust 会推断出储存值的类型,所以很少会需要这些类型注解。为了方便 Rust 提供了 vec! 宏,这个宏会根据我们提供的值来创建一个新的 vector。
- 字符串(string)是字符的集合。我们之前见过
String
类型,不过在本章我们将深入了解。 - 哈希 map(hash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做 map 的更通用的数据结构的特定实现。
对于标准库提供的其他类型的集合,请查看文档。
vec 示例:
fn main() {let mut v = Vec::new();v.push(5);v.push(6);v.push(7);v.push(8);println!("{:?}", v);let v = vec![1, 2, 3, 4, 5];let third: &i32 = &v[2];println!("The third element is {third}");let third: Option<&i32> = v.get(2);match third {Some(third) => println!("The third element is {third}"),None => println!("There is no third element."),}
}
fn main() {let mut v = vec![100, 32, 57];for i in &mut v {// 为了修改可变引用所指向的值,在使用 += 运算符之前// 必须使用解引用运算符(*)获取 i 中的值。*i += 100;}for i in &v{println!("{i}")}
}
示例:
fn main() {let mut v = Vec::new();v.push(5);v.push(6);v.push(7);v.push(8);println!("{:?}", v);let v = vec![1, 2, 3, 4, 5];let third: &i32 = &v[2];println!("The third element is {third}");let third: Option<&i32> = v.get(2);match third {Some(third) => println!("The third element is {third}"),None => println!("There is no third element."),}let mut v = vec![100, 32, 57];for i in &mut v {*i += 100;}for i in &v{println!("{i}")}#[derive(Debug)]enum SpreadsheetCell {Int(i32),Float(f64),Text(String),}// 使用枚举来存储多个类型,类比 Python 的 listlet row = vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from("blue")),SpreadsheetCell::Float(10.12),];for cell in &row {match cell {SpreadsheetCell::Int(value) => println!("整数值: {}", value),SpreadsheetCell::Text(value) => println!("文本值: {}", value),SpreadsheetCell::Float(value) => println!("浮点数值: {}", value),}}
}
Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加 match
意味着 Rust 能在编译时就保证总是会处理所有可能的情况,
如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,
字符串
Rust 的核心语言中只有一种字符串类型:字符串 slice str
,它通常以被借用的形式出现,&str
。第四章讲到了 字符串 slices:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。
fn main() {let s1 = String::from("Hello, ");let s2 = String::from("world!");let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用println!("s2 ---> {s2}");println!("s3 ---> {s3}");let s1 = String::from("tic");let s2 = String::from("tac");let s3 = String::from("toe");// 宏 format! 生成的代码使用引用所以不会获取任何参数的所有权。let s = format!("{s1}-{s2}-{s3}"); 索引字符串println!("s ---> {s}");println!("s1 ---> {s1}");println!("s2 ---> {s2}");println!("s3 ---> {s3}");
}
哈希 map:HashMap<K, V>
HashMap<K, V>
类型储存了一个键类型 K
对应一个值类型 V
的映射。它通过一个 哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表、关联数组、Python的字典(Dict) 等。
可以使用 new
创建一个空的 HashMap
,并使用 insert
增加元素。
fn main() {use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);
}
必须首先 use
标准库中集合部分的 HashMap
。在这三个常用集合中,HashMap
是最不常用的,所以并没有被 prelude 自动引用。标准库中对 HashMap
的支持也相对较少,例如,并没有内建的构建宏。类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
Rust 的 所有权
:https://kaisery.github.io/trpl-zh-cn/ch04-01-what-is-ownership.html
所有权(系统)是 Rust 最为与众不同的特性,对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全,因此理解 Rust 中所有权如何工作是十分重要的。
所有权的规则
- Rust 中的每一个值都有一个 所有者(owner)。
- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被 丢弃(释放内存空间)。
变量、作用域
变量是否有效与作用域的关系跟其他编程语言是类似的。
fn main() {
{ // s 在这里无效,它尚未声明
let s = "hello"; // 从此处起,s 是有效的
// 使用 s
} // 此作用域已结束,s 不再有效,清理并drop(释放掉)内存空间
}这里有两个重要的时间点:
- 当
s
进入作用域 时,它就是有效的。- 这一直持续到它 离开作用域 为止。
变量与数据交互:移动 (浅拷贝)、克隆 (深拷贝)
- 浅拷贝(shallow copy)和 深拷贝(deep copy)
浅拷贝:拷贝指针、长度和容量,而不拷贝指针所指向内存空间的数据。
深拷贝:拷贝指针、长度和容量,同时也拷贝指针所指向内存空间的数据。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 在 C++ 中,这里会发生浅拷贝,不过在 Rust 中会使第一个变量无效,这个操作被称为 移动(move),而不是叫做浅拷贝。为了确保内存安全,在let s2 = s1;
之后,Rust 认为s1
不再有效,因此 Rust 不需要在s1
离开作用域后清理任何东西。println!("{}, world!", s1); // 看看在
s2
被创建之后尝试使用s1
会发生什么,这里会报错, // 因为只有s2
是有效的,当其离开作用域,它就释放自己的内存
}这里隐含了 Rust 的一个设计选择:Rust 永远也不会自动创建数据的 “深拷贝”。
变量的 "移动(转移)"
只要进行 "赋值(=)、函数传参" 都会有 移动
- 移动 ( 转移 ):变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。
变量的 克隆
确实 需要深度复制
String
中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做clone
的通用函数。fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();println!("s1 = {}, s2 = {}", s1, s2);
}
只在栈上的数据:拷贝
fn main() {
let x = 5;
let y = x;println!("x = {}, y = {}", x, y);
}一个通用的规则,任何一组简单标量值的组合都可以实现 Copy,任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型:
- 所有整数类型,比如
u32
。- 布尔类型,
bool
,它的值是true
和false
。- 所有浮点数类型,比如
f64
。- 字符类型,
char
。- 元组,当且仅当其包含的类型也都实现
Copy
的时候。比如,(i32, i32)
实现了Copy
,但(i32, String)
就没有。
所有权与函数
将值传递给函数与给变量赋值的原理相似。向函数传递值可能会移动或者复制,就像赋值语句一样。
fn main() {
let s = String::from("hello"); // s 进入作用域takes_ownership(s); // s 的值移动到函数里 ...
// ... 所以到这里不再有效let x = 5; // x 进入作用域
makes_copy(x); // x 应该移动函数里,
// 但 i32 是 Copy 的,
// 所以在后面可继续使用 x} // 这里,x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
// 没有特殊之处fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。
// 占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // 这里,some_integer 移出作用域。没有特殊之处
返回值与作用域
返回值也可以转移所有权。
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值
// 转移给 s1let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里,s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 离开作用域并被丢弃fn gives_ownership() -> String { // gives_ownership 会将
// 返回值移动给
// 调用它的函数let some_string = String::from("yours"); // some_string 进入作用域。
some_string // 返回 some_string
// 并移出给调用的函数
//
}// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
//a_string // 返回 a_string 并移出给调用的函数
}
引用 ( 借用 )
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。同时出现时 "可变引用、不可变引用 作用域 不能重叠"
- 引用不传递所有权。
- 引用必须总是有效的。
- 引用 ( 借用 ):& 符号就是 引用,它们允许你使用值,但不获取其所有权,因为没有所有权,所以在离开作用域时,不会进行清理释放内存。引用(reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同,引用确保指向某个特定类型的有效值。
fn main() {
let s1 = String::from("hello");let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}fn calculate_length(s: &String) -> usize { // s 是 String 的引用
s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
// 所以什么也不会发生变量
s
有效的作用域与函数参数的作用域一样,不过当s
停止使用时并不丢弃引用指向的数据,因为s
并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。
- 总结:将创建一个引用的行为称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。因为并不拥有它。
正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。
如果想要修改引用的值,就需要用到 可变引用(mutable reference)。可变引用有一个很大的限制:如果创建了一个变量的可变引用,就不能再创建对该变量的引用。不可变引用的值本身就不希望被改变,一个变量可以有多个不可变引用。
fn main() {
let mut s = String::from("hello");let r1 = &mut s;
let r2 = &mut s;println!("{}, {}", r1, r2);
}
这个报错说这段代码是无效的,因为我们不能在同一时间多次将s
作为可变变量借用。第一个可变的借入在r1
中,并且必须持续到在println!
中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在r2
中创建另一个可变引用,该引用借用与r1
相同的数据。这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争(data race)类似于竞态条件,它可由这三个行为造成:
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。
数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!
可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能 同时 拥有:
fn main() {
let mut s = String::from("hello");{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用let r2 = &mut s;
}
Rust 在同时使用可变与不可变引用时也采用的类似的规则。这些代码会导致一个错误:fn main() {
let mut s = String::from("hello");let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题println!("{}, {}, and {}", r1, r2, r3);
} // r1、r2、r3 作用域都是到这里结束,但是上面打印时,r1、r2 生效时 r3 也生效,所以报错。因为 rust 会自动判断 变量引用的作用域是否重叠,所以可以调整 println 的顺序即可。fn main() {
let mut s = String::from("hello");let r1 = &s; // 没问题
let r2 = &s; // 没问题println!("{}, {}", r1, r2);
let r3 = &mut s; // 大问题
println!("{}", r3);
}不可变引用
r1
和r2
的作用域在println!
最后一次使用之后结束,这也是创建可变引用r3
的地方。它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。记住这是 Rust 编译器在提前指出一个潜在的 bug 的规定。
悬垂引用(Dangling References)
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
fn main() {
let reference_to_nothing = dangle();
}fn dangle() -> &String { // dangle 返回一个字符串的引用
let s = String::from("hello"); // s 是一个新字符串
&s // 返回字符串 s 的引用,但是引用不转移所有权,所以函数结束时,s 被销毁释放内存
} // 这里 s 离开作用域并被丢弃。其内存被释放。
// 危险!
正确的做法:不返回引用。fn main() {
let string = no_dangle();
}fn no_dangle() -> String {
let s = String::from("hello");
s // 这样就没有任何错误了。所有权被移动出去,所以没有值被释放。
}
Slice 类型:slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它同样没有所有权。
枚举、结构体
- 枚举(enumerations),也被称作 enums。枚举允许你通过列举可能的 成员(variants)来定义一个类型。
- struct,或者 structure,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。结构体可以定义方法。结构体作用就是将字段和数据聚合在一块,形成新的数据类型。
fn main() {enum IpAddr {V4(String),V6(String),}let home = IpAddr::V4(String::from("127.0.0.1"));let loopback = IpAddr::V6(String::from("::1"));match home {IpAddr::V4(ip) => println!("Home IPv4 地址是: {}", ip),IpAddr::V6(ip) => println!("Home IPv6 地址是: {}", ip),}match loopback {IpAddr::V4(ip) => println!("Loopback IPv4 地址是: {}", ip),IpAddr::V6(ip) => println!("Loopback IPv6 地址是: {}", ip),}
}
Rust 的 Result
在 Rust 中,Result
是一个枚举类型,它代表了可能产生错误的操作的结果。Result
枚举有两个变体:Ok
和 Err
。
Ok
变体表示操作成功,并包含操作返回的值。Err
变体表示操作失败,并包含一个错误值,用于描述错误的原因。
通常,Result
类型被用于表示可能会发生错误的函数的返回类型。这样,调用者可以通过检查 Result
来处理操作的成功或失败。简单的示例,演示如何使用 Result
:
fn divide(a: i32, b: i32) -> Result<i32, String> {if b == 0 {return Err(String::from("除数不能为零"));}Ok(a / b)
}fn main() {let result = divide(10, 2);match result {Ok(value) => println!("结果是: {}", value),Err(error) => println!("出现错误: {}", error),}
}
示例 2:
use std::io;
use std::io::stdin;fn main() {let mut input_str = String::from("");stdin().read_line(&mut input_str).expect("获取输入失败");let input_int:usize = match input_str.trim().parse() {Ok(n) => n,Err(_) => {println!("无效的输入");return;}};let result = input_int * 100;println!("{result}")
}
match 控制流结构、匹配 Option<T>
- Rust 的
match
是极为强大的控制流运算符,它允许将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成。 -
Option<T>
时,是为了从Some
中取出其内部的T
值;
如果其中含有一个值,则执行有值得流程。
如果其中没有值,则执行没有值得流程。
fn main() {fn plus_one(x: Option<i32>) -> Option<i32> {match x {None => None,Some(i) => Some(i + 1),}}let five = Some(5);let six = plus_one(five);let none = plus_one(None);// 使用模式匹配match six {Some(value) => println!("Some 值是: {}", value),None => println!("None"),}// 使用 unwrap() 方法if let Some(value) = five {println!("Some 值是: {}", value);} else {println!("None");}if let Some(value) = six {println!("Some 值是: {}", value);} else {println!("None");}
}
使用包、Crate、模块管理
Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能,有时被统称为 “模块系统(the module system)”,包括:
- crate :是 Rust 在编译时最小的代码单位。如果用
rustc
而不是cargo
来编译一个文件时,编译器会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译。crate 有两种形式:二进制的可执行程序、lib库。 - Crates :一个模块的树形结构,它形成了库或二进制项目。
- 包(Packages):Cargo 的一个功能,它允许你构建、测试和分享 crate。
包(package)是提供一系列功能的一个或者多个 crate。一个包会包含一个 Cargo.toml 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。
- 从 crate 根节点开始:当编译一个 crate,编译器首先在 crate 根文件(通常,对于一个库 crate 而言是 src/lib.rs,对于一个二进制 crate 而言是 src/main.rs)中寻找需要被编译的代码。
- 声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用
mod garden
声明了一个叫做garden
的模块。编译器会在下列路径中寻找模块代码:- 内联,在大括号中,当
mod garden
后方不是一个分号而是一个大括号 - 在文件 src/garden.rs
- 在文件 src/garden/mod.rs
- 内联,在大括号中,当
- 声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了
mod vegetables;
。编译器会在以父模块命名的目录中寻找子模块代码:- 内联,在大括号中,当
mod vegetables
后方不是一个分号而是一个大括号 - 在文件 src/garden/vegetables.rs
- 在文件 src/garden/vegetables/mod.rs
- 内联,在大括号中,当
- 模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的
Asparagus
类型可以在crate::garden::vegetables::Asparagus
被找到。 - 私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用
pub mod
替代mod
。为了使一个公用模块内部的成员公用,应当在声明前使用pub
。 use
关键字: 在一个作用域内,use
关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus
的作用域,你可以通过use crate::garden::vegetables::Asparagus;
创建一个快捷方式,然后你就可以在作用域中只写Asparagus
来使用该类型。
示例:创建一个名为backyard
的二进制 crate 来说明这些规则。该 crate 的路径同样命名为backyard
,该路径包含了这些文件和目录:
这个例子中的 crate 根文件是src/main.rs,该文件包括了:
文件名:src/main.rs
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {:?}!", plant);
}
pub mod garden;
行告诉编译器应该包含在 src/garden.rs 文件中发现的代码:
文件名:src/garden.rs
pub mod vegetables;
在此处, pub mod vegetables;
意味着在src/garden/vegetables.rs中的代码也应该被包括。这些代码是:
#[derive(Debug)]
pub struct Asparagus {}
模块的路径有两种形式:
- 绝对路径(absolute path)是以 crate 根(root)开头的全路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值
crate
开头。 - 相对路径(relative path)从当前模块开始,以
self
、super
或当前模块的标识符开头。
绝对路径和相对路径都后跟一个或多个由双冒号(::
)分割的标识符。
4、通过例子学 Rust
英文文档:https://doc.rust-lang.org/rust-by-example/
中文文档:https://rustwiki.org/zh-CN/rust-by-example/
查看更多 Rust 官方文档中英文双语教程,包括双语版《Rust 程序设计语言》(出版书名为《Rust 权威指南》), Rust 标准库中文版。
《通过例子学 Rust》(Rust By Example 中文版)翻译自 Rust By Example,中文版最后更新时间:2022-1-26。查看此书的 Github 翻译项目和源码。
开始学习吧!
-
Hello World - 从经典的 “Hello World” 程序开始学习。
-
原生类型 - 学习有符号整型,无符号整型和其他原生类型。
-
自定义类型 - 结构体
struct
和 枚举enum
。 -
变量绑定 - 变量绑定,作用域,变量遮蔽。
-
类型系统 - 学习改变和定义类型。
-
类型转换
-
表达式
-
流程控制 -
if
/else
,for
,以及其他流程控制有关内容。 -
函数 - 学习方法、闭包和高阶函数。
-
模块 - 使用模块来组织代码。
-
Crate - crate 是 Rust 中的编译单元。学习创建一个库。
-
Cargo - 学习官方的 Rust 包管理工具的一些基本功能。
-
属性 - 属性是应用于某些模块、crate 或项的元数据(metadata)。
-
泛型 - 学习编写能够适用于多种类型参数的函数或数据类型。
-
作用域规则 - 作用域在所有权(ownership)、借用(borrowing)和生命周期(lifetime)中起着重要作用。
-
特性 trait - trait 是对未知类型(
Self
)定义的方法集。 -
宏
-
错误处理 - 学习 Rust 语言处理失败的方式。
-
标准库类型 - 学习
std
标准库提供的一些自定义类型。 -
标准库更多介绍 - 更多关于文件处理、线程的自定义类型。
-
测试 - Rust 语言的各种测试手段。
-
不安全操作
-
兼容性
-
补充 - 文档和基准测试
相关文章:
Rust 学习
Rust 官网:https://www.rust-lang.org/zh-CN/ 模块 库:https://crates.io/ 1、Rust 简介 Rust 语言的主要目标之一是解决传统 系统级编程语言(如 C 和 C)中常见的安全性问题,例如空指针引用、数据竞争等。为了实现这个…...
1.1 【应用开发】应用开发简介
写在前面 Screen图形子系统基于客户端/服务器模型,其中应用程序是请求图形服务的客户端(Screen)。它包括一个合成窗口系统作为这些服务之一,这意味着所有应用程序渲染都是在离屏缓冲区上执行的,然后可以在稍后用于更新…...
在windows系统搭建LVGL模拟器(codeblock工程)
1.codeblock准备 下载codeblock(mingw),安装。可参考网上教程。 2.pc_simulator_win_codeblocks 工程获取 仓库地址:lvgl/lv_port_win_codeblocks: Windows PC simulator project for LVGL embedded GUI Library (github.com) 拉取代码到本地硬盘&…...
2023第十四届蓝桥杯国赛 C/C++ 大学 B 组
文章目录 前言试题 A: 子 2023作者思考题解答案 试题 B: 双子数作者思考题解 试题 C: 班级活动作者思考题解 试题 D: 合并数列作者思考题解 试题 E: 数三角作者思考题解 试题 F: 删边问题作者思考题解 试题 G: AB 路线作者思考题解 试题 H: 抓娃娃作者思考题解 试题 I: 拼数字试…...
如何在页面中加入百度地图
官方文档:jspopularGL | 百度地图API SDK (baidu.com) 添加一下代码就可以实现 <!DOCTYPE html> <html> <head><meta name"viewport" content"initial-scale1.0, user-scalableno"/><meta http-equiv"Conten…...
Windows VC++提升当前进程权限到管理员权限
Windows VC提升当前进程权限 Windows VC提升当前进程权限到管理员权限 Windows VC提升当前进程权限到管理员权限 有时候Windows下我们需要提升当前进程的权限到管理员权限,相关VC代码如下: #ifndef SAFE_CLOSE_HANDLE #define SAFE_CLOSE_HANDLE(handl…...
算法leetcode|92. 反转链表 II(rust重拳出击)
文章目录 92. 反转链表 II:样例 1:样例 2:提示:进阶: 分析:题解:rust:go:c:python:java: 92. 反转链表 II: 给你单链表的…...
Chapter 7 - 3. Congestion Management in Ethernet Storage Networks以太网存储网络的拥塞管理
Pause Threshold for Long Distance Links长途链路的暂停阈值 This section uses the following basic concepts: 本节使用以下基本概念: Bit Time (BT): It is the time taken to transmit one bit. It is the reciprocal of the bit rate. For example, BT of a 10 GbE po…...
优雅玩转实验室服务器(二)传输文件
使用服务器最重要的肯定是传输文件了,我们不仅需要本地的一些资源上传到服务器,好进行实验,也需要将服务器计算得到的实验结果传输到本地,来进行预览或者报告撰写。 首先,由于涉及到服务器操作,我强烈推荐…...
动态面板简介以及ERP原型图案列
动态面板简介以及ERP原型图案列 1.Axure动态面板简介2.使用Axure制作ERP登录界面3.使用Asure完成左侧菜单栏4.使用Axuer完成公告栏5.使用Axuer完成左边侧边栏 1.Axure动态面板简介 在Axure RP中,动态面板是一种强大的交互设计工具,它允许你创建可交互的…...
漏刻有时百度地图API实战开发(12)(切片工具的使用、添加自定义图层TileLayer)
TileLayer向地图中添加自定义图层 var tileLayer new BMap.TileLayer();tileLayer.getTilesUrl function (tileCoord, zoom) {var x tileCoord.x;var y tileCoord.y;return images/tiles/ zoom /tile- x _ y .png;}var lockMap new BMap.MapType(lock_map, tileLaye…...
python 爬虫 m3u8 视频文件 加密解密 整合mp4
文章目录 一、完整代码二、视频分析1. 认识m3u8文件2. 获取密钥,构建解密器3. 下载ts文件4. 合并ts文件为mp4 三、总结 一、完整代码 完整代码如下: import requests from multiprocessing import Pool import re import os from tqdm import tqdm fro…...
mybatis中xml文件容易搞混的属性
目录 第一章、1.1)MyBatis中resultMap标签1.2)MyBatis的resultType1.3)MyBatis的parameterType1.4)type属性1.5)jdbcType属性1.6)javaType属性1.7)ofType属性 友情提醒: 先看文章目录ÿ…...
android Retrofit2.0请求 延长超时操作
import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory;public class MyApiClient {private static final String BASE_URL "https://api.example.com/";// 创建 OkHttpClient,并设置超时时间…...
Axure之动态面板轮播图
目录 一.介绍 二.好处 三.动态面板轮播图 四.动态面板多方式登录 五.ERP登录 六.ERP的左侧菜单栏 七.ERP的公告栏 今天就到这了哦!!!希望能帮到你了哦!!! 一.介绍 Axure中的动态面板是一个非常有用的组…...
一文读懂算法中的时间复杂度和空间复杂度,O(1)、O(logn)、O(n)、O(n^2)、O(2^n) 附举例说明,常见的时间复杂度,空间复杂度
时间复杂度和空间复杂度是什么 时间复杂度(Time Complexity)是描述算法运行时间长短的一个度量。空间复杂度(Space Complexity)是描述算法在运行过程中所需要的存储空间大小的一个度量。 时间复杂度和空间复杂度是衡量算法性能…...
LWIP热插拔功能实现
0 工具准备 1.lwip 1.4.1 2.RTOS(本文使用rt-thread)1 使能连接变化回调功能 打开lwipopts.h,将宏定义LWIP_NETIF_LINK_CALLBACK的值设为1,如下: #define LWIP_NETIF_LINK_CALLBACK 1这个宏定义被使能后会将…...
android下的app性能测试应主要针对那些方面,如何开展?
如何开展安卓手机下的App性能测试,对于优秀的测试人员而言,除了要懂得性能测试的步骤流程外,还应该懂的性能测试的一些其他知识,比如性能测试指标、各指标的意义,常用的性能测试工具、如何查看结果分析等等知识。所以本…...
【深度学习】注意力机制(二)
本文介绍一些注意力机制的实现,包括EA/MHSA/SK/DA/EPSA。 【深度学习】注意力机制(一) 【深度学习】注意力机制(三) 目录 一、EA(External Attention) 二、Multi Head Self Attention 三、…...
学习黑马vue
项目分析 项目下载地址:vue-admin-template-master: 学习黑马vue 项目下载后没有环境可参考我的篇文章,算是比较详细:vue安装与配置-CSDN博客 安装这两个插件可格式化代码,vscode这个软件是免费的,官网:…...
gdb本地调试版本移植至ARM-Linux系统
移植ncurses库 本文使用的ncurses版本为ncurses-5.9.tar.gz 下载地址:https://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz 1. 将ncurses压缩包拷贝至Linux主机或使用wget命令下载并解压 tar-zxvf ncurses-5.9.tar.gz 2. 解压后进入到ncurses-5.9目录…...
《Linux C编程实战》笔记:实现自己的ls命令
关键函数的功能及说明 1.void display_attribute(struct stat buf,char *name) 函数功能:打印文件名为name的文件信息,如 含义分别为:文件的类型和访问权限,文件的链接数,文件的所有者,文件所有者所属的组…...
Python个人代码随笔(观看无益,请跳过)
异常抛错:一般来说,在程序中,遇到异常时,会从这一层逐层往外抛错,一直抛到最外层,由最外层把错误显示在用户终端。 try:raise ValueError("A value error...") except ValueError:print("V…...
Unity中实现ShaderToy卡通火(总结篇)
文章目录 前言一、把卡通火修改为后处理效果1、在Shader属性面板定义属性接收帧缓存纹理2、在片元着色器对其纹理采样后,与卡通火相加输出请添加图片描述 二、我们自定义卡通火1、修改 _CUTOFF 使卡通火显示在屏幕两侧2、使火附近屏幕偏红色 前言 在之前的文章中&a…...
等保2.0的变化
1法律地位得到确认 《中华人民共和国网络安全法》第21条规定“国家实行网络安全等级保护制度”,要求“网络运营者应当按照网络安全等级保护制度要求,履行安全保护义务”;第31条规定“对于国家关键信息基础设施,在网络安全等级保护…...
漏洞复现-网神SecGate3600防火墙敏感信息泄露漏洞(附漏洞检测脚本)
免责声明 文章中涉及的漏洞均已修复,敏感信息均已做打码处理,文章仅做经验分享用途,切勿当真,未授权的攻击属于非法行为!文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…...
ArkTS入门
代码结构分析 struct Index{ } 「自定义组件:可复用的UI单元」 xxx 「装饰器:用来装饰类结构、方法、变量」 Entry 标记当前组件是入口组件(该组件可被独立访问,通俗来讲:它自己就是一个页面)Component 用…...
JS中for循环之退出循环
我为大家介绍一下退出循环的两种方法 1.continue 退出本次循环,一般用于排除或者跳过某一个选项的时候,可以使用continue for(let i 0;i<5;i){if(i 3){continue}// 跳过了3console.log(i) //0 1 2 4}2.break 退出整个for循环,一般用于…...
《Global illumination with radiance regression functions》
总结一下最近看的这篇结合神经网络的全局光照论文。 论文的主要思想是利用了神经网络的非线性特性去拟合全局光照中的间接光照部分,采用了基础的2层MLP去训练,最终能实现一些点光源、glossy材质的光照渲染。为了更好的理解、其输入输出表示如下。 首先…...
华南理工C++试卷
诚信应考 , 考试作弊将带来严重后果! 《C程序设计试卷》 注意事项:1. 考前请将密封线内填写清楚; 2. 所有答案请答在试卷的答案栏上; 3.考试形式:闭卷 4. 本试卷共 五 大题,满分100分ÿ…...
做公司网站需要什么材料/我想找一个营销团队
来自:三顿(ID:wordpptdream)作者:三顿华为出品的PPT长什么样?很多小伙伴第一反应想到的一定是高大上的发布会PPT:然而这次我在华为的官网上却发现了不少一言难尽的案例,一眼望去密密麻麻铺满了各种图示图表…...
自定义字段wordpress/免费推广的网站有哪些
// 队列的单链表实现 // 头节点:哨兵作用,不存放数据,用来初始化队列时使队头队尾指向的地方 // 首节点:头节点后第一个节点,存放数据#include<stdio.h> #include<malloc.h> #include<stdlib.…...
公安局松江分局网站/线上营销方式主要有哪些
目录Linux操作系统1.Linux操作命令2.在Linux中find和grep的区别?3.绝对路径用什么符号表示?4.当前目录、上层目录用什么表示?5.主目录用什么表示?6.怎么查看进程信息?7.保存文件并退出vi 编辑?8.怎么查看当前用户id&a…...
添加到wordpress视频库中/什么是电商平台推广
0 结论 避免在整型和指针类型间重载,尽量使用nullptr代替空指针。 1 nullptr、NULL和0 一般情况下,人们经常习惯于使用0或NULL来代指空指针,这容易导致歧义或者程序编译错误。例如: 下面的代码中,f(NULL)会导致歧义…...
如何提升做网站的效率/杭州网站推广与优化
Object.freezed() 冻结 检查函数 Object.isFrozen(obj) Object.seal() 密封 检查函数 Object.isSealed(obj) Object.preventExtensions()扩展 检查函数 Object.isExtensible(obj) 共同点: 都不能添加新的属性(有一个例外就是属性是对象的时候&…...
爱网恋的男生/重庆网络seo
先说一下什么是Ucenter,顾名思义它是“用户中心”。UCenter是Com服务器enz旗下各个产品之间信息直接传递的一个桥梁,通过UCenter可以无缝整合Com服务器enz系列产品,实现用户的一站式登录以及社区其他数据的交互。Ucenter 通信基本过程如下:1.从用户xxx在某一应用程序…...