观看B站软件工艺师杨旭的rust教程学习记录,有删减有补充
hello word
文件按名为main.rs
,main是函数入口,rust默认推断为void main
,!
代表println是一个宏,函数没有!
rust
fn main(){
println!("{}"," Hello world!");
}
编译(仅适合小项目,一般用Cargo)
rust
rustc main.rs
执行
rust
#win
.\main.exe
#Linux
./main
Cargo
查看cargo版本cargo --version
创建rust项目
rust
cargo new hello_rust
运行项目
rust
cargo run
检查代码但不生成可执行文件,速度比构建快,能干过编译器就是好代码
rust
cargo check
构建项目,生成的可执行文件在/target/debug/build
rust
cargo build
为发布构建,编译时进行优化但编译时间更长,生成的可执行文件在/target/release/build
rust
cargo build --release
自定义发布配置
cargo build
:采用dev配置
cargo build --release
:采用release配置
在Cargo.toml中添加profile可以覆盖配置
toml
[prifile.dev]
opt-level = 0 #开发时使用最低优化级别
[profile.release]
opt-level = 3 #发布时使用最高优化级别
生成更小的可执行文件
toml
[profile.release]
opt-level = "z" # 优化代码尺寸
lto = true # 启用链接时优化
codegen-units = 1 # 降低代码生成单元数,增加优化时间但减少二进制大小
strip = "debuginfo" # 移除调试信息
生成更快的可执行文件
toml
[profile.release]
opt-level = 3 # 最大程度优化代码速度
lto = "fat" # 启用最大程度的链接时优化
codegen-units = 1 # 降低代码生成单元数,增加优化时间但提升性能
Rust依赖库
在Cargo.toml
添加需要的库,^
代表与指定版本兼容的版本,如
rust
[dependencies]
rand = "^0.8.5"
更新cargo.lock
版本
rust
cargo update
猜数游戏
rust
use rand::Rng;
use std::cmp::Ordering;
use std::io; //trait
fn main() {
println!("猜数!");
//生成1~100的随机数
let secret_number = rand::thread_rng().gen_range(1..101);
println!("神秘数值是{}", secret_number);
loop {
println!("猜一个数");
let mut guess = String::new();
//等价于std::io
io::stdin().read_line(&mut guess).expect("无法读取行");
//去掉两边的空白并转换为整数,变量遮蔽shadow,match处理错误
let guess: u32 = match guess.trim().parse() {
Ok(num) => num, //成功就返回num
Err(_) => continue, //_代表不关心它的值是什么
};
println!("你猜的数字是:{}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("太小"),
Ordering::Greater => println!("太大"),
Ordering::Equal => {
println!("猜对了");
break;
}
}
}
}
变量与可变性
- 使用
let
声明变量 - 变量赋值在Rust中被称为变量绑定
- 变量默认是不可变的
- 使用
mut
关键字使变量可变
rust
fn main() {
let a = 5;
let mut b = 4;
b = 6;
println!("a:{},b:{}", a, b);//a:5,b:6
}
变量与常量
常量声明后不可变
- 常量使用
const
关键字声明,它的类型必须标注 - 不可以使用
mut
,常量永远不可变 - 常量可以在任何作用域声明,包括全局作用域
- 常量只可以绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时计算出的值
命名规范 :Rust常量使用全大写字母,每个单词之间使用_
分开,如MAX_VALUE
rust
const MAX_VALUE: u32 = 1_000;
fn main() {
const MIN_VALUE: u32 = 1_000;
println!("最大值:{}\n最小值{}", MAX_VALUE, MIN_VALUE);
}
shadowing(变量遮蔽)
后面声明的变量会遮蔽前面声明的
rust
fn main(){
let a=5;
let a=a+3;
println!("{}",a);//8
}
用法
rust
fn main() {
let str = "hello world";
let str = str.len();
println!("{}",str);
}
字面量
字面量是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数、布尔值以及字符串(String类型不是字符串!)
它们的共同特点就是值固定,存储在stack(栈内存)上
let a = 4;//4是字面量,a存储实际字面值
let s = "Hello";//Hello是字面量,s存储实际字面值
let b = true;//true是字面量,b存储实际字面值
let f = 3.14;//3.14是字面量,f存储实际字面值
String类型是可变的,hello被转换为String类型,hello可变,数据存储在heap(堆内存)上,指针(长度、容量)存储在stack(栈内存)上
-
字面值是不可变的,可变就不叫字面值
let s1 = "hello";//hello是字面量,s1存储的是对hello的引用&str,s1本质是一个字符串切片
let s2 = String::from("hello");//hello是字面值,s2存储hello字面量的所有权,也就是hello的指针
数据类型
Rust是静态编译语言,在编译时必须知道所有变量的类型
- 基于使用的值,编译器通常能推断出它的具体类型
- 可能的类型较多时必须标注类型(如需要将string转换为integer)
rust
fn main() {
let string_to_integer: u32 = "2024".parse().expect("不是数字");
println!("{}", string_to_integer); //2024
}
标量类型
- 整数类型
- 浮点类型
- 布尔类型
- 字符类型
integer 整型
- 有符号范围:
-2^n-1~2^n-1 -1
- 无符号范围:
0~2^n -1
长度 | 有符号类型integer | 数值范围 | 无符号类型unsigned | 数值范围 |
---|---|---|---|---|
8位 | i8 | -128~127(-2^7~2^7-1) | u8 | 0~255(2^8-1) |
16位 | i16 | -32768~32767(-2^15~2^15 -1) | u16 | 0~65535(2^16 -1) |
32位 | i32 | -2^31~2^31 -1 | u32 | 0~2^32 -1 |
64位 | i64 | -2^63~2^63 -1 | u64 | 0~2^64 -1 |
128位 | i128 | -2^127~2^127 -1 | u128 | 0~2^128 |
视架构而定 | isize | cpu是n位就是-2^n-1~2^n-1 -1 | usize | cpu是n位就是0~2^n -1 |
rust整型默认使用i32
rust
use std::any::type_name_of_val;
fn main() {
let num1 = 18;
let num2 : i64 = 18;
println!("类型: {}", type_name_of_val(&num1));//类型: i32
println!("类型: {}", type_name_of_val(&num2));//类型: i64
}
整数字面值
字面量 | 例子 |
---|---|
Decimal 十进制 |
12_000 |
Hex 十六进制 |
0xff |
Octal 八进制 |
0077 |
Binary 二进制 |
0b1111_1101 |
Byte(u8 only) 字节 |
b'A' |
整数溢出
u8最大为255,对于超出数据范围的操作,rust会报错将会溢出
rust
fn main(){
let a : u8 = 255;
let b = a+4;//error:attempt to compute `u8::MAX + 4_u8`, which would overflow
println!("{}",b);
}
使用arithmetic_overflow显式处理表明我们需要它按补码循环的方式溢出,"环绕"操作
rust
fn main(){
let a:u8 = 255;
let b= a.wrapping_add(20);//按照补码循环溢出规则处理
println!("{}",b);//19
let b = a.checked_add(20);//发生溢出,则返回 None
println!("{:?}",b);//None
let b = a.overflowing_add(20);//返回该值和一个指示是否存在溢出的布尔值
println!("{:?}",b);//(19,true)
let b = a.saturating_add(20);//限定计算后的结果不超过目标类型的最大值或低于最小值
println!("{}",b);//255
}
在std::num::Wrapping里还有其他用法
浮点类型
精度 | 正浮点数范围 | 负浮点数范围 |
---|---|---|
f32 | 1.4.13e-45~3.4028e38 | -3.4028e38~-1.4013e-45 |
f64 | 2.2251e-3.08~1.7977e3.8 | -1.7977e308~-4.9407e-324 |
- 单精度:符号位:1位 指数部分:8位 尾数部分:23位
- 双精度:符号位:1位 指数部分:11位 尾数部分:52位
现代计算机处理器对两种浮点数计算的速度几乎相同,但 64 位浮点数精度更高,所以rust浮点型默认64位
rust
use std::any::type_name_of_val;
fn main(){
let a = 2.0;
let b:f32 = 2.0;
println!("类型: {}", type_name_of_val(&a));//类型: f64
println!("类型: {}", type_name_of_val(&b));//类型: f32
}
浮点数往往是预期数字的近似表达,0.1+0.2居然不等于0.3!
rust
fn main(){
let a=0.1;
let b=0.2;
println!("{}",a+b);//0.30000000000000004
}
NaN (not a number)
不是一个数,NaN 是一种特殊的浮点数值,用于表示无效或未定义的操作结果
将a的值设置为f64类型的NaN
rust
fn main() {
let a: f64 = f64::NAN;
if a.is_nan() {
println!("a 不是一个数 (NaN)");
}
}
类型转换
- 类型不同的数不允许比较,可以显式转换为两者中更高的类型
- 类型转换必须显式转换
rust
fn main() {
let a : i8 = 4;
let b : i32 = 8;
//将a转换为i32
println!("{}",i32::from(a)<b);//true
}
使用as转换
rust
fn main() {
let a : i8 = 4;
let b : i32 = 8;
let char = 'a';
println!("{}", ((a as i32) < b));//true
println!("{:?}",char as i8);//97
}
数值运算
rust
fn main(){
let a = 2;
let b = 3;
println!("{}",a+b);//5
println!("{}",a-b);//-1
println!("{}",a*b);//6
println!("{}",a/b);//0
println!("{}",a%b);//2
}
位运算
运算符 | 解释 |
---|---|
& 位与 | 相同位置均为1时则为1,否则为0 |
|位或 | 相同位置只要有1时则为1,否则为0 |
^ 异或 | 相同位置不相同则为1,相同则为0 |
! 位非 | 把位中的0和1相互取反,即0置为1,1置为0 |
<< 左移 | 所有位向左移动指定位数,右位补0 |
>> 右移 | 所有位向右移动指定位数,带符号移动(正数补0,负数补1 |
>>>无符号右移 | 所有位向右移动指定位数,但都补0 |
计算机采用补码计算,正数的补码就是原码,负数的补码是原码所有位取反再加1
rust
fn main(){
//补码表示
let a:i8=-2;//1111_1110 负数补码取反再+1:1000_0010->1111_1101+1->1111_1110
let b:i8=3;//0000_0011 正数补码就是本身的2进制数
//正数补码结果就是原码,负数补码结果-1再取反才是原码
println!("{}",a&b);//2 0000_0010
println!("{}",a|b);//-1 1111_1111->1111_1110->1000_0001
println!("{}",a^b);//-3 1111_1101->1111_1100->1000_0011
println!("{}",!a);//1 0000_0001
println!("{}",!b);//-4 1111_1100->1111_1011->1000_0100
println!("{}",a<<2);//-8 1111_1000->1111_0111->1000_1000
println!("{}",a>>2);//-1 1111_1111->1111_1110->1000_0001
}
有理数和复数运算
rust
use num::complex::Complex;
fn main() {
let a = Complex { re: 2.1, im: -1.2 };
let b = Complex::new(11.1, 22.2);
let result = a + b;
println!("{} + {}i", result.re, result.im)
}
布尔类型
- 占用1字节
- 符号为bool
rust
fn main() {
let a = true;
if a {
println!("a是对的!");
}
}
单元类型
单元类型(unit type),没有具体的数值,在没有返回值或不需要传输有意义的数据时使用
以下例子print_hello
返回的数值为(),表示没有返回值,rust函数最后一行默认返回表达式的值,没有值可以返回就是(),main函数返回值也是()
rust
fn main() -> (){
let result = print_hello();
println!("函数返回值: {:?}", result);//函数返回值: ()
}
fn print_hello() {
println!("Hello, world!");//Hello, world!
}
never !类型
rust
fn never_returns() -> ! {
panic!("无返回值");
}
fn main() {
let result: i32 = never_returns();
println!("结果: {}", result);//无返回值
}
字符类型 char
- 字符类型使用
'
- 占用4字节
所有的Unicode值都是字符,例如unicode编码的表情,Unicode都是4字节编码,其他字符
rust
fn main() {
let a = 'A';
let b = '学';
let emo = '🔞';
println!("{},{},{}", a, b, emo);//A,学,🔞
println!("字符占用了{}字节的内存大小",std::mem::size_of_val(&a));//4
println!("字符占用了{}字节的内存大小",std::mem::size_of_val(&b));//4
println!("字符占用了{}字节的内存大小",std::mem::size_of_val(&emo));//4
}
类型别名
- 为现有类型生产另一个名称
- 并不是独立的类型
- 用于减少代码字符重复
rust
type UserId = u32;
fn main() {
let user_id: UserId = 42;
println!("User ID: {}", user_id); //User ID: 42
}
rust
struct Point<T> {
x: T,
y: T,
}
type PointF32 = Point<f32>;
fn main() {
let point: PointF32 = PointF32 { x: 1.5, y: 2.5 };
println!("Point: ({}, {})", point.x, point.y);//Point: (1.5, 2.5)
}
复合类型
Tuple 元组
- Tuple可以将多个类型的多个值放在一个类型里
- Tuple的长度是固定的,一旦声明就无法改变
rust
fn main() {
let tup:(i32, f64, char) = (200, 3.14, '🤣');
println!("{},{},{}", tup.0, tup.1, tup.2);//200,3.14,🤣
//解构赋值
let (x,y,z) = tup;
println!("{},{},{}",x,y,z);//200,3.14,🤣
}
Array 数组
- 数组将多个类型相同的值放在一个类型里
- 数组的长度固定
- 数组的数据存放在stack(栈)上(因为它元素数量固定)
rust
fn main(){
let arry1 = [1,2,3,4,5];
println!("arry1[0]:{}",arry1[0]);//arry1[0]:1
let arry2=[4;5];//5个4
println!("{}",arry2.len());//5
let arry3:[i32;10] = [1;10];//10个1
println!("arry2[0]:{},arry2的长度:{}",arry3[0],arry3.len());//arry2[0]:1,arry2的长度:10
println!("在栈中占用字节数:{}",std::mem::size_of_val(&arry3));//40
//数组可以自动被借用为slice切片
println!("{:?}",&arry3[0 .. 4]);//[1, 1, 1, 1]
}
函数
fn
关键字声明函数- 函数参数必须声明类型
- Rust使用snake_case命名规范
- 所有字母小写,单词之间用
_
隔开
- 所有字母小写,单词之间用
函数无论定义在main的前面后面都没有区别
rust
fn main(){
println!("main函数");//main函数
another_function();
}
fn another_function(){
println!("其他函数");//其他函数
}
语句和表达式
- 语句是执行动作的指令,不返回值,一般以
;
结尾 - 函数的定义也是语句
- 表达式会计算产生一个值,没有
;
rust
fn main() {
let result = add(1, 2);//语句
println!("{}", result);//3,语句
}
fn add(a: i32, b: i32) -> i32 {
a + b//表达式
}
函数返回值
->
后边声明返回值的类型,不可以为返回值命名- Rust返回值是函数体最后一个表达式的值
- 提前返回使用
return
关键字
前面说到最后一行没有表达式的值默认返回()
单元类型,也可以使用return
关键字提前返回
rust
fn main() {
let result = add(1, 2);
println!("{}", result);//3
}
fn add(a: i32, b: i32) -> i32 {//->后面代表函数的返回值类型
return a + b;//语句,虽然计算了值也有返回值,但这个返回值是a+b的结果,不是return a+b的返回值
}