rust language learning part1


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以及该项目的配置文件

image-20240107193700042

.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]部分中,如下:

image-20240109141907653

然后我们重新编译一下:

image-20240109142205738

可以看到,这里已经成功将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_lineparse 都返回 Result 类型,但它们代表的不同操作,因此包含的信息也不同。

  1. io::Resultread_line的返回类型):

    • io::Resultstd::io 模块中的一个特定 Result 类型,用于表示 I/O 操作可能的成功或失败。
    • io::ResultOk 分支包含成功时的结果,通常是读取的字节数或其他 I/O 相关信息。
    • io::ResultErr 分支包含错误信息,它可能是由于文件不存在、权限问题、或其他 I/O 错误而导致的失败。

    例如,在你的代码中,io::stdin().read_line(&mut guess).expect("Error") 尝试从标准输入读取一行,并返回 io::Result,其中 Ok 包含读取的字节数,而 Err 包含可能的 I/O 错误信息。

  2. 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有两种浮点类型,也就是含有小数部分的类型

分别是:

  • f32,32位,单精度
  • f64,64位,双精度

Rust使用IEEE-754(计组:规格化)标准来描述,并使用f64为默认类型。

数值操作

与其他语言一致,不细说。

布尔类型

两种值:

false 和true

均占一字节大小

image-20240109173413254

字符类型
  • 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循环

最为重要的循环方法

image-20240110144354390

通过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(),逆序输出。

  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值