这期我们将会介绍一些通用的编程概念(也就是通常编程语言都有的东西),如数据类型、函数、控制流等
首先我们先介绍一下Rust的关键字,在变量命名时以免冲撞~
The following keywords currently have the functionality described.
as
- perform primitive casting, disambiguate the specific trait containing an item, or rename items in use
and extern crate
statementsasync
- return a Future
instead of blocking the current threadawait
- suspend execution until the result of a Future
is readybreak
- exit a loop immediatelyconst
- define constant items or constant raw pointerscontinue
- continue to the next loop iterationcrate
- link an external crate or a macro variable representing the crate in which the macro is defineddyn
- dynamic dispatch to a trait objectelse
- fallback for if
and if let
control flow constructsenum
- define an enumerationextern
- link an external crate, function, or variablefalse
- Boolean false literalfn
- define a function or the function pointer typefor
- loop over items from an iterator, implement a trait, or specify a higher-ranked lifetimeif
- branch based on the result of a conditional expressionimpl
- implement inherent or trait functionalityin
- part of for
loop syntaxlet
- bind a variableloop
- loop unconditionallymatch
- match a value to patternsmod
- define a modulemove
- make a closure take ownership of all its capturesmut
- denote mutability in references, raw pointers, or pattern bindingspub
- denote public visibility in struct fields, impl
blocks, or modulesref
- bind by referencereturn
- return from functionSelf
- a type alias for the type implementing a traitself
- method subject or current modulestatic
- global variable or lifetime lasting the entire program executionstruct
- define a structuresuper
- parent module of the current moduletrait
- define a traittrue
- Boolean true literaltype
- define a type alias or associated typeunsafe
- denote unsafe code, functions, traits, or implementationsuse
- bring symbols into scopewhere
- denote clauses that constrain a typewhile
- loop conditionally based on the result of an expressionThe following keywords do not have any functionality but are reserved by Rust for potential future use.
abstract
become
box
do
final
macro
override
priv
try
typeof
unsized
virtual
yield
上期我们看到有一个mut变量的声明,那么没有mut关键字呢
fn main() {
let a = 21;
println!("a: {} ", a);
}
能够正确打印a的值,而下面这段代码
fn main() {
let a = 21;
println!("a: {} ", a);
a = 33;
println!("a: {} ", a);
}
出现错误提示:
3 | println!("a: {} ", a);
4 | a = 33;
| ^^^^^^ cannot assign twice to immutable variable
在Rust中,没有加mut的变量,是不可变的哟,不能再次赋值。我们可以称之为不可变变量,将有mut关键字的变量称为可变变量。
那大家就会有疑问了,那这和常量又有什么区别呢?
fn main() {
const THIS_YEAR : u32 = 2020;
println!("{} ",THIS_YEAR);
}
在Rust中使用const关键字定义常量。看下面代码
fn main() {
let a : u32;
a = 33;
}
非可变变量可以在定义时不初始化,而后再赋值。那常量呢?
fn main() {
const THIS_YEAR : u32;
THIS_YEAR = 2000;
println!("{} ",THIS_YEAR);
}
常量必须初始化,且右边必须是编译时就确定的结果,而不能时运行时。
什么是变量遮蔽呢,前面我们提到不可变变量是无法重新赋值的,请看下面代码。
fn main() {
let var = 59;
println!("{}", var);
let var = var + 1;
let mut var = var + 1;
println!("{}", var);
let var = "白纸";
println!("{}", var);
let var = var.len();
println!("{}", var);
}
运行结果:
59
60
61
白纸
6
咦,这里var怎么一直在变呢?其实不然,这里我们其实是使用let关键字将var变量重新定义了,而不是重新赋值,相当于直接覆盖了。
那么这样做到底有什么作用呢,其实还是很有用的。比如,我们需要在同一个函数内部把一个变量转换为另一个类型的变量,但又不想给它们起不同的名字。再比如,在同一个函数内部,需要修改一个变量绑定的可变性。例如,我们对一个可变数组执行初始化,希望此时它是可读写的,但是初始化完成后,我们希望它是只读的。
use std::vec;
fn main() {
let mut vec = Vec::new(); //此时我们需要vec是可读的,因为我们还需要插入数据
vec.push(1);
vec.push(2);
vec.push(3);
let vec = vec; //此时前面的vec已经被遮蔽,无法再访问
for i in &vec {
println!("{}", i);
}
}
运行结果:
1
2
3
在上面程序中,我们希望vec再初始化以后就不再是可写的了,因此采用变量遮蔽的办法实现。
还有一些别的例子,就先举着一个例子吧,应该还是很好理解的,总之就是“严谨!”,能够节省开销。
bool
和其他语言差不多,就不赘述了
char
字符类型,可以描述任何一个符合unicode标准的字符值,通常用单引号包围
let c1 = '\n';
let love = '❥';
由于char类型设计的目的是描述unicode字符,因此它所占据的内存空间是4个字节。
但是Rust同样提供了单字节字符字面量来表示ASCII字符,我们可以使用一个字母b 在字符或者字符串前面,代表这个字面量存储在u8 类型数组中,这样占用空间比char 型数组要小一些。
fn main() {
let c1 = b's';
let s1 = b"shuaishuai";
}
这里顺便介绍一下Rust的类型推导,IDE帮我们标识了出来,但是直接复制代码是看不出来的。截图一下
这里呢,我们并没有敲变量类型,而是输入右边的值以后自动显示了,非常滴方便!当然我们也可以自己显示地定义数据的类型。
整数类型
Rust中的整数分很多类型,具体见下表,
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 |
u8 |
16-bit | i16 |
u16 |
32-bit | i32 |
u32 |
64-bit | i64 |
u64 |
128-bit | i128 |
u128 |
arch | isize |
usize |
注:有符号和无符号不仅仅表现在含义上,还表现在内存空间中bit表达的意义。有符号类型的最高位是符号位,无符号类型的最高位和其他位一样表示数字的大小。例如,对于8位的数据来说,如果存的是无符号数,表示范围则是0255,有符号数则表示-128127,应该是很好理解的。
我们c/c++程序员都知道,整数溢出是一个需要重视的问题,处理不好很有可能会导致未知的bug。那么Rust是怎么处理整数溢出的呢?
默认情况下,在debug 模式下编译器会自动插入整数溢出检查, 一旦发生溢出, 则会引发panic ;在release 模式下,不检查整数溢出,而是采用自动舍弃高位的方式。
那大家知道c是怎么处理整数溢出的吗?后面如果建群的话可以一起探讨探讨。
浮点类型
f32和f64两种类型,默认类型是f64,那大家知道为什么默认是f64而不是f32吗?哈哈
浮点数可能的状态:
enum FpCategory {
Nan,
Infinite,
Zero,
Subnormal,
Normal,
}
Zero:表示0值,正儿八经的0.
Normal:正常的浮点类型。
Subnormal:以后有机会一起讨论吧,不想说这个了。
Nan:不是数字,0 / 0.
Infinite:无穷大,1 / 0.
这里我们先只讲元组类型,至于结构体,枚举和字符串以后再讲。
tuple:元组
废话不多说,直接上代码,用心感受就好了
这里我们采用截图的方式,这样可以清楚的看到自动类型推导。
运行结果:
就不要解释了,相信大家已经深刻体会到了。
array:数组
继续体会:
fn main() {
let array1 = [1, 2, 3, 4, 5, 6];
println!("first: {}", array1[0]);
}
数组也就那么回事。
关于数组下标越界:Rust在编译时不会报错,在运行时会报错。
还有一些别的内容,多维数组、数组切片、大小比较等内容以后再做专门介绍。
Rust的函数需要使用fn关键字开头。
fn main() {
print_input_param(20);
}
fn print_input_param(x : i32) {
println!("input_param: {}", x);
}
运行结果:
input_param: 20
上面是一个简单的单参数无返回值函数,很好理解,下面将演示一个多参数带返回值的函数
fn main() {
println!("result: {}", add1((1, 2)));
}
fn add1((x, y) : (i32, i32)) -> i32 {
return x + y;
}
运行结果:
result: 3
使用return语句返回了函数的返回值,返回值类型是i32。
Rust也可以不通过Rust语句返回返回值。
fn main() {
println!("result: {}", add2((1, 2)));
}
fn add2((x, y) : (i32, i32)) -> i32 {
x + y
}
注意没有分号哟。
上面的输入参数说到底其实就是个元组,因此也可以使用元组作为输入参数
fn main() {
let tup1 = (1, 2);
println!("result: {}", add3(tup1));
}
fn add3(t : (i32, i32)) -> i32 {
t.0 + t.1
}
函数在Rust中是头等公民(first class value),可以被复制到一个值中
fn main(){
let func = add1;
println!("func: {}", func((2, 3)));
}
fn add1((x, y) : (i32, i32)) -> i32 {
return x + y;
}
运行结果:
func: 5
Rust中的每一个函数都有自己的类型
fn main(){
let mut func = add1;
func = add2;
}
fn add1((x, y) : (i32, i32)) -> i32 {
return x + y;
}
fn add2((x, y) : (i32, i32)) -> i32 {
return x + y + 2;
}
编译报错:
error[E0308]: mismatched types
--> src\main.rs:3:12
|
3 | func = add2;
| ^^^^ expected fn item, found a different fn item
|
= note: expected type `fn((i32, i32)) -> i32 {add1}`
found type `fn((i32, i32)) -> i32 {add2}`
尽管参数类型一样,返回值类型一样,但这两个函数仍然是不同类型的函数。
fn main(){
let mut func = add1;
let mut func1 = add1;
func = func1;
println!("func: {}", func((3, 9)));
}
fn add1((x, y) : (i32, i32)) -> i32 {
return x + y;
}
正常运行,你可能觉得这个程序多余,但是我不觉得
上上个程序中,怎么才能正常运行呢,方案是让func的类型不是add1而是通用的fn类型就可以了。
方法一:as
fn main(){
let mut func = add1 as fn((i32, i32)) -> i32;
println!("add1: {}", func((1, 2)));
func = add2;
println!("add2: {}", func((1, 2)));
}
fn add1((x, y) : (i32, i32)) -> i32 {
return x + y;
}
fn add2((x, y) : (i32, i32)) -> i32 {
return x + y + 2;
}
运行结果:
add1: 3
add2: 5
方法二:通过显示类型标记
fn main(){
let mut func : fn((i32, i32)) ->i32 = add1;
println!("add1: {}", func((1, 2)));
func = add2;
println!("add2: {}", func((1, 2)));
}
fn add1((x, y) : (i32, i32)) -> i32 {
return x + y;
}
fn add2((x, y) : (i32, i32)) -> i32 {
return x + y + 2;
}
运行结果:
add1: 3
add2: 5
当然,这仅仅适用于能够转换为同一个fn类型的函数,至于为什么我要解释这一下,因为我觉得不多余。
statement and expression
运算表达式
和其他语言相差不大,没啥好说的
赋值表达式
这里涉及到所有权的问题,后面会专门讲一章
语句块表达式
就是{}包起来
和其他语言大同小异吧
死循环
continue break
和其他语言差不多
涉及到迭代器,后面再讲哈