Rust-base-learning
Rust基础学习–(一)
一、基础知识
1、hello rust–printfn!及其用法
eg:
1 |
|
printfn!()是一个宏,其功能是把格式化文本输出到标准输出(stdout),并且会自动换行。
Rust 使用 {}
来作为格式化输出占位符,其它语言可能使用的是 %s
,%d
,%p
等,由于 println!
会自动推导出具体的类型,因此无需手动指定。
其他相关宏:
print!
:和 println!
类似,但不会自动换行。
eprintln!
:把内容输出到标准错误(stderr)。
format!
:将格式化后的字符串存储到变量中,而不是直接输出。
2、cargo、rustc
cargo new 新文件名(创建复杂工程)
cargo build 文件名(编译)
cargo run 文件名(编译加执行)
编译:rustc 文件名(rs文件)(简单)
执行:./exe文件
二、变量
1、手动设置变量的可变性:
rust需要手动设置变量是否可变,以同时保证安全性和灵活性。
默认下,变量都是不可变的,如果需要可变,需要通过mut关键字来声明。
eg:
1 |
|
如果我们运行这段代码,就会报错,因为没有设置可变,解决方法就是在声明let并进行绑定的时候在let后面加上mut,变为let mut x=5;
(这里没有使用mut关键字的变量并不等同于常量,常量需要使用 const
关键字而不是 let
关键字来声明,并且值的类型必须标注Rust ,以下是一个常量命名示例:
1 |
|
常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性。常量可以在任意作用域内声明,包括全局作用域,在声明的作用域内,常量在程序运行的整个过程中都有效。
)
2、变量的绑定,移动,克隆与拷贝
我们在rust中将变量声明称之为绑定。 这是因为Rust 最核心的原则——所有权,后续会学到。
何为所有权?简单来讲,任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人,同时,该对象之前的主人就会丧失对该对象的所有权。
Rust 中,将一个值赋给另一个变量时,会发生移动(Move),而非复制。移动后,原变量不再有效。若你确实需要复制值,可以使用 clone
方法(深拷贝)。
此外,拷贝(copy)实现了 Copy
trait 的类型(如整数、布尔值等),赋值时会直接复制值,不会转移所有权。
1 |
|
3、忽略未被使用的变量:
rust会给声明却未使用的变量一个警告,如想去掉警告,需要在变量声明的时候在变量名前面加一个_(是在变量名前面直接加,没有空格,变量名也没有被改变)
4、变量解构:
let
表达式不仅仅用于变量的绑定,还能进行复杂变量的解构:从一个相对复杂的变量中,匹配出该变量的一部分内容:
1 |
|
(assert_eq!:
主要用于测试代码时验证两个值是否相等。要是这两个值不相等,程序就会触发 panic(崩溃,Rust 使用这个术语来表明程序因错误而退出),测试也就失败了。
具体用法:
1 |
|
相关地,assert_ne!
用于验证两个值是否不相等)
解构式赋值:
eg:
1 |
|
(需要注意的是,使用 +=
的赋值语句还不支持解构式赋值)
5、变量遮蔽
Rust 允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的(在被遮蔽后,无法再访问到之前的同名变量)。
eg:
1 |
|
这和 mut
变量的使用是不同的,第二个 let
生成了完全不同的新变量,两个变量只是恰好拥有同样的名称,涉及一次内存对象的再分配 ,而 mut
声明的变量,可以修改同一个内存地址上的值,并不会发生内存对象的再分配,性能要更好。
6、变量的类型
(1)整数类型
长度 | 有符号类型 | 无符号类型 |
---|---|---|
8 位 | i8 |
u8 |
16 位 | i16 |
u16 |
32 位 | i32 |
u32 |
64 位 | i64 |
u64 |
128 位 | i128 |
u128 |
视架构而定 | isize |
usize |
整型字面量可以用下表的形式书写:
数字字面量 | 示例 |
---|---|
十进制 | 98_222 |
十六进制 | 0xff |
八进制 | 0o77 |
二进制 | 0b1111_0000 |
字节 (仅限于 u8 ) |
b'A' |
整型溢出
当在 debug 模式编译时,Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 panic
在当使用 --release
参数进行 release 模式构建时,Rust 不检测溢出。相反,当检测到整型溢出时,Rust 会按照补码循环溢出(two’s complement wrapping)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 u8
的情况下,256 变成 0,257 变成 1,依此类推。程序不会 panic,但是该变量的值可能不是期望值。依赖这种默认行为的代码都应该被认为是错误的代码。
(2)浮点数类型与运算
浮点数根据 IEEE-754
标准实现。f32
类型是单精度浮点型,f64
为双精度。
1 |
|
这个程序会panic,因为二进制精度问题,导致了 0.1 + 0.2 并不严格等于 0.3(在i64类型中),它们可能在小数点 N 位后存在误差。
那如果非要进行比较的话,可以考虑用这种方式 (0.1_f64 + 0.2 - 0.3).abs() < 0.00001
,具体小于多少,取决于对精度的需求。
(NaN:
对于数学上未定义的结果,例如对负数取平方根 -42.1.sqrt()
,会产生一个特殊的结果:Rust 的浮点数类型使用 NaN (not a number) 来处理这些情况
所有跟 NaN
交互的操作,都会返回一个 NaN
,而且 NaN
不能用来比较,代码会崩溃)
运算:
综合示例:
1 |
|
位运算:
Rust 的位运算基本上和其他语言一样
运算符 | 说明 |
---|---|
& 位与 | 相同位置均为1时则为1,否则为0 |
| 位或 | 相同位置只要有1时则为1,否则为0 |
^ 异或 | 相同位置不相同则为1,相同则为0 |
! 位非 | 把位中的0和1相互取反,即0置为1,1置为0 |
<< 左移 | 所有位向左移动指定位数,右位补0 |
>> 右移 | 所有位向右移动指定位数,带符号移动(正数补0,负数补1) |
(序列:Rust 提供了一个非常简洁的方式,用来生成连续的数值,例如 1..5
,生成从 1 到 4 的连续数字,不包含 5 ;1..=5
,生成从 1 到 5 的连续数字,包含 5,它的用途很简单,常常用于循环中。
eg:
1 |
|
序列只允许用于数字或字符类型,原因是:它们可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是 Rust 中仅有的可以用于判断是否为空的类型。
)
(有理数和复数并未包含在标准库中,如需使用,需要引入 num
库:
步骤如下:
- 创建新工程
cargo new complex-num && cd complex-num
- 在
Cargo.toml
中的[dependencies]
下添加一行num = "0.4.0"
- 将
src/main.rs
文件中的main
函数替换为下面的代码 - 运行
cargo run
1 |
|
)
(3)字符类型
在 Rust 语言中这些都是字符,Rust 的字符不仅仅是 ASCII
,所有的 Unicode
值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等,都是合法的字符类型。由于 Unicode
都是 4 个字节编码,因此字符类型也是占用 4 个字节。
(4)布尔类型
Rust 中的布尔类型有两个可能的值:true
和 false
,布尔值占用内存的大小为 1
个字节。
使用布尔类型的场景主要在于流程控制,例如上述代码的中的 if
就是其中之一。
(5)单元类型
单元类型就是 ()
,唯一的值也是 ()
。
main函数就返回单元类型(),常见的 println!()
的返回值也是单元类型 ()。
再比如,可以用 ()
作为 map
的值,表示我们不关注具体的值,只关注 key
。 这种用法 ()
可以作为一个值用来占位,但是完全不占用任何内存。
7、类型转换
Rust 中可以使用 As 来完成一个类型到另一个类型的转换,其最常用于将原始类型转换为其他原始类型,但是它也可以完成诸如将指针转换为地址、地址转换为指针以及将指针转换为其他指针等功能。包括AsRef
trait和AsMut
trait
AsRef
trait 用于不可变引用的转换,定义如下:
1 |
|
AsMut
trait 用于可变引用的转换,定义如下:
1 |
|
下面是一些使用AsRef
和AsMut
的常见场景:
(1)String 转换为 & str:
1 |
|
(2) 使用 AsRef 处理多种类型:
1 |
|
(3)AsMut 示例
1 |
|
三、语句、表达式、函数
1、语句
1 |
|
以上都是语句,它们完成了一个具体的操作,但是并没有返回值,因此是语句。
由于 let
是语句,因此不能将 let
语句赋值给其它值,如下形式是错误的:
1 |
|
2、表达式
表达式会进行求值,然后返回一个值。例如 5 + 6
,在求值后,返回值 11
,因此它就是一条表达式。
表达式可以成为语句的一部分,例如 let y = 6
中,6
就是一个表达式,它在求值后返回一个值 6
(有些反直觉,但是确实是表达式)。
调用一个函数是表达式,因为会返回一个值,调用宏也是表达式,用花括号包裹最终返回一个值的语句块也是表达式,总之,能返回值,它就是表达式。
(表达式不能包含分号!)
最后,表达式如果不返回任何值,会隐式地返回一个() 。
3、函数
Rust 的函数体是由一系列语句组成,最后由一个表达式来返回值,例如:
1 |
|
【注意:函数的位置可以随便放,Rust 不关心我们在哪里定义了函数,只要有定义即可
每个函数参数都需要标注类型】
(发散函数:
当用 !
作函数返回类型的时候,表示该函数永不返回( diverging functions ),特别的,这种语法往往用做会导致程序崩溃的函数:
1 |
|
)
To Be Continue…