Files
Notebook-StarNet/rust/rust1.md
T
2024-12-14 22:20:19 +08:00

547 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Rust 语法学习
## 用例 1:猜数游戏
生成一个随机 1-100 的数并让玩家猜测
```rust
// rust默认隐式引入prelude
// 除了prelude以外都要显式引入
use rand::random;
fn main() {
// 使用常量定义一个偏置量
// 常量在编译时就已经确定,所以不能通过如CONST XX = XX()来初始化
// let声明的不可变变量虽然不可变,但是在编译时默认为未知的。
const BIAS: u8 = 1;
println!("guess number game");
// let: 声明变量,默认不可变
// mut:声明变量可变
// String::new(),冒号表示这是一个String类的关联函数
// 关联函数就是类似于静态方法
let secret_number = random::<u8>() + BIAS;
println!("secret number is {}", secret_number);
// mut使得变量可变
let mut user_guess: u8 = 0;
let mut user_input = String::new();
while user_guess != secret_number {
// 清空之前的输入
user_input.clear();
// io::stdin():标准输入
// read_line:读取一行
// &:取地址符号,表示引用,默认不可变
// &mut:声明可变引用
std::io::stdin().read_line(&mut user_input).expect("read fail");
// 将输入的字符串转换为数字
// user_guess = user_input.trim().parse().expect("parse fail");
// 使用 match 表达式判断用户输入的是否为数字
// 比使用if else更简洁
user_guess = match user_input.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("please input a number");
continue;
}
};
// 判断用户输入的数字与随机数的大小关系
// if user_guess > secret_number {
// println!("user number is too large");
// } else if user_guess < secret_number {
// println!("user number is too small");
// } else {
// println!("user number is correct");
// break;
// }
// 使用 match 表达式判断用户输入的数字与随机数的大小关系
// 比使用if else更简洁
match user_guess.cmp(&secret_number) {
std::cmp::Ordering::Greater => println!("user number is too large"),
std::cmp::Ordering::Less => println!("user number is too small"),
std::cmp::Ordering::Equal => {
println!("user number is correct");
break;
}
}
println!("user number is {}", user_input);
}
}
```
## 特性: shadow
在同一个作用域内,可以重复声明同名的变量,会覆盖之前的变量
```rust
fn main() {
let x = 5;
// 重复声明同名的变量,会覆盖之前的变量
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x); // 12
}
```
## 类型
### 基本类型
#### 整数类型
- 整数:i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
- 整数类型范围:
- 有符号:-2^n+1 ~ 2^(n-1)-1
- 无符号:0 ~ 2^n-1
- isize, usize:取决于运行程序的计算机的架构
- 表示
- hex: 0x12345678
- oct: 0o12345678
- bin: 0b1010_1010
- Byte: b'A'
#### 浮点数类型
- 浮点数:f32, f64, 默认是 f64
- 表示
- 科学计数法:1.23e3
- 小数点:1.23
#### 布尔类型
- 布尔类型:bool
- 表示
- true, false
#### 字符类型
- 字符类型:char
- 表示
- 'a', 'A', '1', '中'
- 单引号表示
- 占用 4 字节
### 复合类型
#### 元组类型(Tuple)
- 可以将多个类型的多个值放在一个类型里
- 长度是固定的,一旦声明就无法改变
- 声明例: let tup: (i32, f64, u8) = (500, 6.4, 1);
- 访问例 1: let (x, y, z) = tup;
- 访问例 2: let x = tup.0;
#### 数组类型(Array)
- 可以将多个类型的多个值放在一个类型里
- 长度也是固定的,一旦声明就无法改变
- 想要使用有固定数量的元素时使用数组更有好处
- 声明例: let arr: [i32; 5] = [1, 2, 3, 4, 5];
- 声明例 2: let arr = [3; 5]; // 表示数组有 5 个 3
- 访问例: let x = arr[0];
- 没有 Vector 灵活
## 函数
函数名小写,单词间使用下划线进行分割
例子:
```rust
fn f(x: i32, y: i32) -> i32 {
// 这里不加分号,因为是表达式,会返回值
// 函数默认使用最后一个表达式作为返回值
x + y
}
fn f2(x: i32, y: i32) {
let z = {
x + y + 1
};
z
}
```
## 控制流
### if 表达式
- 如果使用多于一个的 if-else,最好使用 match 对代码进行重构
- if 表达式是一个表达式,所以可以将其赋值给一个变量
```rust
fn main() {
let x = 5;
if x < 5 {
println!("x is less than 5");
} else if x > 5 {
println!("x is greater than 5");
} else {
println!("x is 5");
}
// 使用 match 对代码进行重构
let x = 5;
match x.cmp(&5) {
std::cmp::Ordering::Less => println!("x is less than 5"),
std::cmp::Ordering::Greater => println!("x is greater than 5"),
std::cmp::Ordering::Equal => println!("x is 5"),
}
let y = if x < 5 {
1
} else {
2
};
}
```
### 循环
```rust
fn main() {
let mut x = 0;
while x < 5 {
println!("x is {}", x);
x += 1;
}
x = 0;
// 使用 .. 表示一个范围,不包含右边的数
// 实际的类型是std::ops::Range<{integer}>
// 可以通过(1..5).rev() 表示一个倒序的范围
for x in 0..5 {
println!("x is {}", x);
}
const A: [i32; 5] = [1, 2, 3, 4, 5];
// iter 方法返回一个迭代器,迭代器是一个可以遍历集合的对象
for x in A.iter() {
println!("x is {}", x);
}
x = 0;
loop {
println!("x is {}", x);
x += 1;
if x > 5 {
break;
}
}
// loop 表达式是一个表达式,所以可以将其赋值给一个变量
let result = loop {
if x > 5 {
break x * 2;
}
x += 1;
};
}
```
## 所有权
- 内存管理分为两个部分:栈和堆
- 所有权是 Rust 最独特的特性之一
- 在 Rust 中,每个值都有一个变量,这个变量被称为所有者
- 每个值在任意时刻有且只有一个所有者
- 当所有者离开 scope(作用域),这个值将被删除
### 作用域
例:
```rust
fn main() {
{
let x = 5;
println!("x is {}", x);
}
// x 已经离开作用域
}
```
### copy trait
- 赋值要么发生移动,要么发生复制
- Rust 中的基本类型都实现了 copy trait
- 如果一个类型实现了 copy trait,则旧的变量在赋值后仍然可以使用
- 否则就像下面的 String 一样,赋值以后旧的变量会失效
- 内容全为已实现 copy 的 tuple 也是可 copy 的
### 所有权和函数
- rust 函数传参与赋值操作类似
- 所以可能发生移动,使得一些变量失效
- 返回值时所有权也很发生移动或复制
- 一个变量的所有权总是遵循同样的模式
- 把一个值赋给其他变量的时候会发生移动
- 当一个包含 heap 数据的变量离开作用域时。这个值就会被 drop 函数删除
- 除非其所有权发生了转移
例:
```rust
fn main() {
let s = String::from("hello");
takes_ownership(s);
// 这里会报错,因为s已经失效了
// println!("{}", s);
let x = 5;
makes_copy(x);
// 这里不会报错,因为x仍然可以使用
println!("{}", x);
let s1 = generates_string();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
println!("{}", s1);
// 这里会报错,因为s2已经失效了
println!("{}", s2);
println!("{}", s3);
}
fn takes_and_gives_back(a_string: String) -> String {
a_string
}
fn generates_string() -> String {
let s = String::from("hello");
s
}
fn makes_copy(x: i32) {
println!("{}", x);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
}
```
## 引用和借用
- 引用是一个指向某个值的指针
- 引用不会获取到值的所有权
- 引用的变量默认是不可变的
- 引用的变量可以通过 mut 关键字声明为可变的
- 特定的作用域内,一个变量只能有一个可变引用
- 不可同时拥有可变引用与不可变引用
- 不可变引用可以有多个
- rust 在编译时会防止悬空指针的出现
例
```rust
fn main() {
let s1 = String::from("hello");
// 这里的s2是一个引用,指向s1
let len = calculate_length(&s1);
change_string(&mut s1);
println!("{}", s2);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn change_string(s: &mut String) {
s.push_str(", world");
}
```
## slice 切片
- slice 是一个引用,指向一个字符串/数组的一部分
- 定义函数时使用字符串切片(&str)来代替字符串引用会使我们的 api 更加通用且不会损失任何功能
```rust
fn main() {
let s = String::from("hello world");
let wordIndex = first_word(&s);
let hello = &s[0..5];
let world = &s[6..11];
// 这里会报错,因为clear会导致可变,但是 s已被不可变引用
s.clear();
let a: [u8; 5] = [1, 2, 3, 4, 5];
let slice = &a[0..2];
}
fn first_word(s: &str) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[i..];
}
}
&s[..]
}
```
## String
- 相比于使用双引号创建的字符串,使用 String 类型可以动态地分配内存
- String 是一个可变的字符串类型
- String 是一个堆上的字符串,而不是栈上的字符串
- 底层实现上:
- String 是一个结构体,包含一个指向堆上的字符串的指针和一个长度,一个容量
- 结构体储存在栈上
- 真实的字符串储存在堆上
```rust
fn main() {
// 在heap上分配内存了
let mut s = String::from("hello");
// 和js类似,s1和s实际上是同一个字符串
// 但是因为一些所有权的问题,所以在s1生效以后,s就失效了
let s1 = s;
// 克隆消耗资源,但是原来的不会失效了
let s2 = s1.clone();
// 这样会报错,因为s已经失效了
s.push_str(", world");
s1.push_str(", world");
println!("s is {}", s1);
}
```
## struct 结构体
- 结构体是一个自定义的类型
- 一旦结构体是可变的,那么结构体的所有字段就是可变的
- 只要 struct 是有效的,则里面的引用也都是有效的
```rust
struct User {
name: String,
age: u8,
id: u32,
}
// tuple struct 元组结构体
struct Color(i32, i32, i32)
// 这样可以将结构体打印出来
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
// impl关键字用于为结构体实现接口或者关联函数或者方法
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// 关联函数,不需要实例化就可以调用,通常用于创建结构体
// 类似于静态方法
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let id: u32 = 1;
let user = User {
name: String::from("hello"),
age: 18,
id, // 如果字段名和变量名相同,可以省略变量名
};
let user2 = User {
name: String::from("world"),
id: 2,
..user //struct更新语法
};
println!("user is {}", user.name);
let color = Color(255, 0, 0);
let rect = Rectangle {
width: 10,
height: 20,
};
let rect2 = Rectangle {
width: 5,
height: 10,
};
println!("rect is {:#?}", rect);
println!("rect area is {}", rect.area());
println!("rect can hold rect2? {}", rect.can_hold(&rect2));
}
```
## 枚举
- 枚举是一个自定义的类型
例
```rust
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6((String::from("::1")));
}
```
## Option
- Option 是一个枚举类型,表示一个值可能不存在
- 包含在预导入库中,可以直接使用
```rust
enum Option<T> {
Some(T),
None,
}
```
- 如 let x: Option<i32> = None;
- 通过unwrap方法可以获取到Some的值
## match
- match可以提供一种匹配
```rust
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 coin = Coin::Quarter(UsState::Alaska);
println!("value_in_cents is {}", value_in_cents(coin));
}
```