title:rust learning
RUST 学习
第一章
安装rust,我选择在wsl中安装:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
查看版本:
rustc --version
查看文档:
rustup doc
hello world
源码:
fn main(){
println!("Hello World!");
}
编译:
rustc main.rs
注意事项:
- 和c语言一样,main函数也是rust语言第一个被执行的函数。
- fn (function)用于创建函数
- main没有参数,没有返回
- rust中的缩进是4个空格,不是tab
- println!是rust的宏(rust macro),所以这里如果将!删去,println就是一个函数
- 每块语句以分号结尾;
rust语言特点:ahead-of-time预编译,可以将编译完成的rust程序直接发送给未安装rust的电脑运行。
注意:
rustc只适用于简单的,小型的rust程序。复杂的则需要cargo
Cargo
cargo是rust构建系统和包管理工具
~/rust_learning 19:21:10 ─╮
❯ cargo --version ─╯
cargo 1.75.0 (1d8b05cdd 2023-11-20)
可以通过cargo new *** .rs创建项目:
他会直接在当前目录下创建一个新的目录:
里面包含:
src以及该项目的配置文件
.gitgnore是仓库初始文件,Cargo.toml是该项目的配置文件:
里面包含文件依赖,版本信息等
运行:
cargo run//类似docker run
第二章
牛刀小试:1.简单的猜数游戏
题目信息:
-
第一步,创建项目:
❯ cargo new guessing ─╯ Created binary (application) `guessing` package
-
第二步,编写源码:
use std::io;//因为我们下面 fn main() { println!("let us play a game!"); println!("guess what is next number!"); let mut guess = String::new(); io::stdin().read_line(&mut guess).expect("error"); println!("this is your guess: {}", guess); }
代码详解:
use std::io;//因为我们下面引用了read_line函数,他是IO库中的函数,而IO库又是STD库中的函数,所以我们再调用read_line函数的时候需要用 USE 导入该库,因为这一个库没有被preclude fn main() { println!("let us play a game!"); println!("guess what is next number!");//注意println!不是函数,是一个宏 let mut guess = String::new(); //let 用于声明变量 //mut关键字表明该变量是可修改的,而在rust中所有变量在申明时默认都是immuteable,不可修改的 //举个例子:如果let foo = 1;声明foo为1,那么我们不可以再foo = 2;对其赋值。 //::表明new是String类型的关联函数,关联函数就是指,他是针对这一类型来实现的,而不是针对某一个实例实现的。String是标准库中的,采用utf-8格式。 io::stdin().read_line(&mut guess).expect("error"); //如果未申明use std::io,可以换一种写法:std::IO::read_line...这样子可以达到同样的效果。注意read_line返回的值为有两种:OK err,这里.expect就是指当readline返回值为err时就输出“error”;然后就是这里关于引用&,这样子引用的是同一块数据区域,注意read_line函数第二个参数规定了是String类型,只能用字符串类型的参数,引用&在rust里面也是默认不可更改,这也是加mut的原因。 println!("this is your guess: {}", guess); //{}为占位符,类似C语言中的%d等等。 }
牛刀小试:2.生成神秘数字
在rust中,我们为了生成随机数,需要外部导入一个lib类型的库 rand,这个库要添加到cargo.toml的[dependencies]部分中,如下:
然后我们重新编译一下:
可以看到,这里已经成功将rand依赖导入
导入库的过程大致如下:
先将crates.io这个网址最新的索引下载下来,就是一个注册表,然后更新注册表,然后cargo就会检查dependieces下有哪些库未下载,然后再下载,同样的因为rand库也有一个依赖,所以我们可看到上图中第一个位置是libc,这就是rand库的依赖。
简单源码:
use std::io;
use rand::Rng;//trait 类似接口
fn main() {
println!("let us play a game!");
println!("guess what is next number!");
println!("this is the number you should know!");
let secretnumber = rand::thread_rng().gen_range(1..101);//范围[1,101),这里最新版的rand库,要求范围这么写,旧版本(1,101);
println!("生成的随机数是 {}",secretnumber);
println!("plz guess the number.");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("error");
println!("this is your guess: {}", guess);
}
注意:RUST严格区分大小写;
牛刀小试:3.比较猜测数字与神秘数字
use std::io;
use rand::Rng;//trait 类似接口
use std::cmp::Ordering;
fn main() {
println!("let us play a game!");
println!("guess what is next number!");
println!("this is the number you should know!");
let secretnumber = rand::thread_rng().gen_range(1..101);//范围[1,101)
println!("生成的随机数是 {}",secretnumber);
println!("plz guess the number.");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("error");
println!("this is your guess: {}", guess);
let guess:u32 = guess.trim().parse().expect("error");//因为之前guess在申明时直接就是String类型,而secretnumber是整数型,无法直接比较,会报错,所以这里我们需要
//将String类型转为整形,trim是为了将字符串两边空格等不显示字符去掉,parse()转整形,如果有xy等字符,转型失败,同read_line的expect用法。
//这样的同名变量会隐藏之前一个变量(shadow),其统属范围以覆盖位置为结尾。let guess:u32显示类型,同样的为了比较,secretnumber也会被转型从i32转为u32
match guess.cmp(&secretnumber){
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("you win!"),//注意match的分支arm,结尾需要以,结尾.
}
}
这节主要就是学习了:
- std::cmp::Ordering枚举的用法,以及简单match的用法。
- 同名函数转型及shadow的用法
- trim(),parse()等用法
牛刀小试:4.连续猜测的实现
use std::io;
use rand::Rng;//trait 类似接口
use std::cmp::Ordering;
fn main() {
println!("let us play a game!");
println!("guess what is next number!");
println!("this is the number you should know!");
let secretnumber = rand::thread_rng().gen_range(1..101);//范围[1,101)
println!("生成的随机数是 {}",secretnumber);
loop{//无限循环
println!("plz guess the number.");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("error");
println!("this is your guess: {}", guess);
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,//将解析成功的数字赋给guess
Err(_) => continue,//_通配符表示忽略报错信息,然后continue
};//为了避免输入无法转型的字符导致的退出,因为parse返回也为result枚举型,可以用match分支实现
match guess.cmp(&secretnumber) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("you win!");
break;//这里利用break实现在猜对后推出loop的效果
}
}
}
}
这里第一时间,我想的是直接在read_line后面match,然后报错了,发现原来是result类型不一致:
以下是chatgpt给出的回答:
在你的代码中,
read_line
和parse
都返回Result
类型,但它们代表的不同操作,因此包含的信息也不同。
io::Result
(read_line
的返回类型):
io::Result
是std::io
模块中的一个特定Result
类型,用于表示 I/O 操作可能的成功或失败。io::Result
的Ok
分支包含成功时的结果,通常是读取的字节数或其他 I/O 相关信息。io::Result
的Err
分支包含错误信息,它可能是由于文件不存在、权限问题、或其他 I/O 错误而导致的失败。例如,在你的代码中,
io::stdin().read_line(&mut guess).expect("Error")
尝试从标准输入读取一行,并返回io::Result
,其中Ok
包含读取的字节数,而Err
包含可能的 I/O 错误信息。
Result<u32, ParseIntError>
(parse
的返回类型):
parse
函数返回的Result
是一个泛型类型,它表示解析字符串为数字可能的成功或失败。Ok
分支包含成功时的解析结果,这里是一个u32
类型的数字。Err
分支包含解析失败时的错误信息,通常是一个实现了std::error::Error
trait 的类型,比如ParseIntError
。在你的代码中,
match guess.trim().parse()
用于处理parse
的返回结果,其中Ok(num)
包含解析成功时得到的数字,而Err(_)
包含解析失败的错误信息。在这里,continue
语句用于跳过当前迭代并提示用户重新输入,表示解析失败。
所以这里只能在parse这里使用match分枝。
第三章:通用编程概念
- 变量与可变性
- 数据类型
- 标量类型’
- 复合类型
- 函数
- 注释
- 控制流
3.1 变量与可变性
- 声明变量使用let关键字
- 变量未使用mut关键字时,默认不可更改(immutable)
常量(constant)
常量在被定义后和默认immut的变量一样不可更改,但依旧有很多区别:
- 不可用mut更改可变性
- 常量的声明需要使用const,并且必须显式的标明常量类型;
- 常量可以在任何作用域内声明,包括全局作用域
- 常量只能绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值。
在程序运行期间,常量在其声明的作用域中一直有效
命名规范:Rust里面常量必须是使用大写字符,并且只能用下划线隔开,例:
- MAX_POINTS
定义例:
const MAX_POINTS:u32 = 100_00;”_“ 在RUST可用于增加可读性。
变量
shadowing(隐藏)
-
可以使用相同的名字声明变量,新的变量就会隐藏之前那个变量、
-
在后续代码中使用的变量就是新变量
-
shadow和mut在某种程度上是不一样的:
- 不使用let给immuttable变量赋值时会编译错误
- 使用let声明同名变量时也不可变
let mut y = 6;
y = y+1;//不会报错
let y = y+8;
y = y+1;//这里会报错,重新声明也需要加mut
- 使用let声明同名新变量时,可以改变类型(第二章用过了)
usize表示与自己电脑相同位数。
3.2 数据类型
- 标量类型
- 复合类型
RUST是静态编译语言,所以在编译时必须知道所有的变量的类型:
-
基于使用的值,编译器能推断出大部分的类型
-
但对于如Parse()转型等方法使用过程中,就需要显式的写出
let mut guess = "42"; let guess:u32 = guess.parse().expect("err");
因为对于42我们可以转为i32和u32等等,所以我们需要显示的写出类型。
标量类型
-
一个标量代表一个单个的值
-
Rust有四个主要的标量类型:
- 整数类型
- 布尔类型
- 浮点类型
- 字符类型
整数类型
- 1.整数类型就是没有小数部分
- 2.例如u32就是一个无符号的整数类型,占据32位空间
- 无符号类型整数以u开头
- 有符号整数以i开头
- Rust的整数类型列表如图:
!
注意一字节8位
整数字面值
- 除了byte类型外,所有数字字面值都允许使用类型后缀,例如:57u8
默认类型时i32,性能速度相当快。
整数溢出
例如:u8的范围是0-255,如果你把一个u8的变量设为256,那么:
- 调试模式下编译就会:Rust会检查整数溢出,如果发生溢出,程序会在运行时panic
- 发布模式下(-release)编译:Rust不会检查可能导致panic的整数溢出。
- 如果溢出发生,那么这时rust就会进行环绕操作:
- 256 -> 0
- 257->1
- …
- 但是在发布模式下并不会发生panic
- 如果溢出发生,那么这时rust就会进行环绕操作:
浮点类型
Rust有两种浮点类型,也就是含有小数部分的类型
分别是:
- f32,32位,单精度
- f64,64位,双精度
Rust使用IEEE-754(计组:规格化)标准来描述,并使用f64为默认类型。
数值操作
与其他语言一致,不细说。
布尔类型
两种值:
false 和true
均占一字节大小。
字符类型
-
Rust中char类型被用来描述语言中最基础的单个字符。
-
字符类的字面值使用单引号
-
占用4字节
-
是unicode标量值,可以表示比ASCII多的字符内容:拼音,中日韩文,零长度空白字符,emoji表情等等。
- 范围:U+0000 - U+D7FF
- U+E000 - U+10FFFF
3.3 复合类型
- 复合类型可以提供多个值放在一个类型里面
- Rust提供了两种基础的复合类型:元组(Tuple),数组
Tuple
- Tuple可以将多个固定的多个值放在一个类型里面
- Tuple的长度是固定的:一旦声明就无法改没变
创建Tuple
- 在小括号里面,将值用逗号分开
- Tuple中的每个位置都对应每一个类型,tuple中个元素类型不必
let hui:(i32,f64,char) = (32,5.4,'a');
println!("{},{},{}",hui.0,hui.1,hui.2);
//输出
32,5.4,a
获取Tuple的元素值
- 可以使用模式匹配来解构(destructure)一个Tuple来获取元素的值
let hui:(i32,f64,char) = (32,5.4,'a');
let (x,y,z) = hui;
println!("{},{},{}",hui.0,hui.1,hui.2);
println!("{},{},{}",x,y,z);
//输出
32,5.4,a
32,5.4,a
访问Tuple元素
- 在Tuple变量使用点标记法,后接元素的索引号;
let hui:(i32,f64,char) = (32,5.4,'a');
println!("{},{},{}",hui.0,hui.1,hui.2);
//输出
32,5.4,a
数组
- 数组也可以将多个值放在一个类型里面
- 数组中每个元素的类型必须相同
- 数组的长度也是固定的
数组的类型
let a[u32;5] = [1,2,3,4,5];
[类型:长度]
或者:
let a[3;5] = [3,3,3,3,3];
[值:长度]
访问数组元素
通过中括号的索引访问,与其它语言中一致;
注意简单的数组越界在编译时,rust会直接报错;当然复杂的越界不会;
最大为12时:
报错
不报错:
3.4 函数
- 声明函数使用fn关键字
- 按照惯例,针对函数和变量名,rust使用snake case命名规范
snake case;
所有字母都是小写,单词之间使用下划线分开。
函数的参数
- paremeters.arguments(形参,实参)
- 在函数的签名中必须声明每个参数的类型。
fn new_function(x:i32,y:u32);//这里的参数z,y也就是形参
这里有一点很重要:
注意这里y语句部分,x+3没有以分号结尾,所以这里其实相当于y = x + 3,只不过这里x的初值为1;只要看这里有没有返回值就可以,如果加了”;“,呢么这块语句就会被判定为没有返回值,y也就会被赋空值;并爆错,无法打印。
并且我们可以看到,这里x没有shadow前一个x,它的作用域只在花括号中。
函数返回值
- 在->符号后面声明函数返回值的类型,但是不可以返回值命名
- 在Rust里面,返回值就是函数体里面最后一个表达式的值
- 若想提前返回,需要return关键字,并指定一个值
- 大多数函数都默认使用最后一个表达式的值为返回值
fn six() -> i32{
6
}
fn plus_six(x:i32) -> i32{
x + 6
}
let yu = six();
let xu = plus_six(6);
println!("the value of yu {}",yu);
println!("the value of yu {}",xu);
//输出
the value of yu 6
the value of yu 12
注释
//
或者/**/
3.5 控制流
if表达式
- if表达式允许您根据条件来执行不同的代码分支
- 这个条件必须是bool类型
- if表达式中,与条件相关联的代码块就叫arm分支;
- 可在后面添加else;
else if 处理多重条件
注意:如果使用了多于一个else if,最好使用match语句重构你的代码。
循环
loop循环
反复执行一段代码,直至手动停止,或者break;
while条件循环
每次执行前都判断一次条件
for循环
最为重要的循环方法
通过Range生成,1,2,3三个数,并用rev(),逆序输出。
_six(6);
println!("the value of yu {}",yu);
println!("the value of yu {}",xu);
//输出
the value of yu 6
the value of yu 12
#### 注释
//
或者/**/
### 3.5 控制流
#### if表达式
* if表达式允许您根据条件来执行不同的代码分支
* **这个条件必须是bool类型**
* if表达式中,与条件相关联的代码块就叫arm分支;
* 可在后面添加else;
#### else if 处理多重条件
注意:**如果使用了多于一个else if,最好使用match语句重构你的代码**。
---
#### 循环
##### loop循环
反复执行一段代码,直至手动停止,或者break;
##### while条件循环
每次执行前都判断一次条件
##### for循环
[外链图片转存中...(img-MzUxkJBu-1709632696996)]
最为重要的循环方法
[外链图片转存中...(img-So5BgRL0-1709632696996)]
通过Range生成,1,2,3三个数,并用rev(),逆序输出。