Rust学习笔记(1) - 常见编程概念
推进Rust的学习,之前有零散学习过,现在系统做些笔记,主要是看Rust程序设计语言
常见编程概念
变量与可变性
不可变(immutable)变量,可变(mutable)变量,常量(constants)
Rust中的变量默认是不可变的(immutable),要声明可变变量,可以在其之前加mut
来声明,可以用const
关键字声明常量,常量可以在任何作用域,包括全局作用域中声明
1
2
3
4
5
6
7
8
9
// 定义一个常量,命名规范通常为用下划线分隔的大写字母单词,且必须注值的类型
const MAX_POINTS:u32 = 100_000;
fn main() {
// 简单定义一个变量,定义后,其值不可改变
let immutable_int = 500;
// 定义一个可变变量,这里在编译时会有警告,提示没有必要让其可变
let mut mutable_int = 6;
println!("定义了一个可变变量{},一个不可变变量{},一个常量{}",mutable_int,immutable_int,MAX_POINTS);
}
变量隐藏(Shadowing)
可以定义一个与之前变量同名的新变量来隐藏之前的变量,这意味着使用这个变量时会看到第二个值
1
2
3
4
5
6
7
8
9
fn main() {
// 简单定义一个变量,定义后,其值不可改变
let immutable_int = 500;
// 使用相同变量名,隐藏之前定义的变量
let immutable_int = 501;
let immutable_int = 502;
// 编译时,会弹出警告,提示上方两个变量从未使用过
println!("一个变量{}",immutable_int);
}
当再次使用let
时,实际上创建了一个新变量,只是复用了之前变量的名字,这个变量可以是不同类型的变量,使用mut
,则不能用不同类型的变量赋值
1
2
3
4
5
6
7
8
fn main() {
// 定义一个字符串变量
let spaces = " ";
// 用同样的变量名,得到这个字符串的长度
let spaces = spaces.len();
// 因为这个字符串有两个空格,最后这个变量的值是2
println!("最后变量的值是{}",spaces);
}
数据类型
Rust是静态类型(statically typed)语言,在编译时就必须知道所有变量的类型,当可能会是多种类型时,必须增加类型注解,例如:
1
2
let num = "2";
let num = num.parse().expect("这不是一个数字!");
这里在parse
时,必须添加类型注解,说明编译器需要更多信息,写成:
1
2
let num = "2";
let num: u32 = num.parse().expect("这不是一个数字!");
就能编译通过了
标量(scalar)类型
标量(scalar)类型代表一个单独的值,Rust有四种基本的标量类型:整型、浮点型、布尔型和字符类型
整型
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
有无符号代表数字能否为负值,有符号数以补码形式(two’s complement representatin)存储,如果变体使用的位数为n,则变体可以用的值包含从2的n-1次方的相反数到2的n-1次方-1在内的数,所以i8
可以储存从-128到-127在内的数。无符号的变体可以存储从0到2的n次方-1的数,所以u8
可以储存从0到255在内的数
另外,isize
和usize
类型依赖运行程序的计算机架构:64位架构上它们是64位,32位架构上它们是32位
除byte以外的所有数字字面量允许使用类型后缀,也允许使用_
作为分隔符以方便读数,这里有一个表格
数字字面值 | 例子 |
---|---|
Decimal (十进制) | 98_222 |
Hex (十六进制) | 0xff |
Octal (八进制) | 0o77 |
Binary (二进制) | 0b1111_0000 |
Byte (单字节字符)(仅限于u8 ) | b'A' |
例子:
1
2
3
4
5
6
7
8
9
10
fn main() {
let num = 2u8;
let decimal_num = 9_8222;
let hex_num = 0xff;
let octal_num = 0o77;
let binary_num = 0b1111_0000;
let byte = b'A';
// 将输出2,98222,255,63,240,65
println!("{},{},{},{},{},{}",num,decimal_num,hex_num,octal_num,binary_num,byte);
}
Rust的默认数字类型是i32
,它通常是最快的,甚至在64位系统上也是,当使用一个整型变量存放超过其容量的值,会发生整型溢出(integer overflow)
浮点型
Rust有两个原生的浮点数(floating-point numbers)类型,它们是带小数点的数字,分别是f32
和f64
,占32位和64位。默认类型是f64
,因为在现代CPU中,它与f32
速度几乎一样,不过精度更高(f32
是单精度浮点数,f64
是双精度浮点数)。
数值运算
Rust中的所有数字类型都支持基本数学运算:加减乘除和取余,注意当整数和浮点数一起运算时,运算符两边的数值的类型要保持一致,需要均为浮点型或均为整型
1
2
3
4
5
6
7
8
fn main() {
let num = 2;
let num = num + 6;
let num = num - 1;
let num = num * 10;
let num = num as f32 / 2.5 ;
let num = num % 5.0;
}
布尔型
Rust中有两个布尔型:true
和false
,布尔类型用bool
表示
字符类型
Rust中的char
类型大小为四字节,并代表了一个Unicode标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。在 Rust 中,拼音字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。
1
2
3
4
5
fn main() {
let c = '1';
let c = '数';
let c = '😀';
}
复合类型
复合类型(Compound types)可以将多个值组合成一个类型。Rust有两个原生的复合类型:元组(tuple)和数组(array)
元组类型
元组是将多个其他类型的值组合进一个复合类型的主要方式,一旦声明,其长度固定,元组的定义:
1
2
3
let tup = (1.2, 3, 4);
// 添加了类型注解的元组
let tup:(f64,u32,u32) = (1.2,3,4);
可以使用模式匹配(pattern matching)来解构(destructure)元组值,或者用.
来直接访问元组内的值:
1
2
3
4
5
6
7
fn main() {
let tup = (1.2, 3, 4);
// 解构元组,不需要的值可以用_来充当占位符
let(_,y,_) = tup;
println!("The value of y is {}",y);
println!("元组中的第一个值是{}",tup.0);
}
数组类型
与元组不同,数组(array)中的每个元素的类型必须相同,且Rust中的数组是固定长度的,一旦声明,其不能增长或缩小,几种声明数组的方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
let months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
// 在定义时规定数组元素的类型和元素个数
let nums: [u32; 5] = [1, 2, 3, 4, 5];
// 创建一个每个元素都相同的数组,分号前面是数组中元素的值,后面是元素的个数
let repeat_nums = [3; 5];
}
访问数组元素:
1
2
3
4
fn main() {
let nums = [1, 2, 3, 4, 5];
println!("数组的第一个元素是{}", nums[0]);
}
函数如何工作
Rust中的函数和变量名使用snake case规范风格,所有字母都是小写并使用下划线分隔单词
定义函数
Rust中函数定义是以fn
开头的,并且必须声明每个参数的的类型
1
2
3
fn print_one_number(number_be_printed: i32) {
println!("{}", number_be_printed);
}
包含语句和表达式的函数体
在了解这个概念之前,要先理解Rust是一门基于表达式(expression-based)的语言,具体来说,语句(Statements)是执行一些操作但不返回值的命令,表达式(Expressions)计算并产生一个值,直接来看一些例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
// 这是一个语句,我们不能将其赋值给另一个变量,比如写成let x = (let num=6)
// 这里5是一个表达式,其计算出的值是6
let num = 6;
// 5 + 6是一个表达式,其计算出的值是11
let num = 5 + 6;
// 大括号{}也是一个表达式,其包含的代码块被绑定到y上
// 表达式的结尾没有分号,如果在表达式的结尾加上分号,它就变成了语句
let y = {
let x = 3;
x + 1
};
}
虽然在例子中已经写了,但是还是要强调,表达式的结尾是没有分号的,其表示一个计算的过程,如果加上分号,就变成了语句,不会返回值
具有返回值的函数
在Rust中,函数的返回值等同于函数体最后一个表达式的值,并且要在箭头(->
)后声明它的类型
1
2
3
4
5
6
7
8
fn main() {
let num = plus_one(5);
println!("The value of num is:{}", num);
}
fn plus_one(num: i32) -> i32 {
num + 1
}
注释
关于注释,没有特别的地方,在前面加上//
即可,放在行前,会持续到那一行结束,也可以放在代码行的行尾
控制流
if与else if
1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
// 计算if表达式的值并返回给变量
let num = if 1 + 1 > 2 { 5 } else { 6 };
// else if的使用
if num % 2 == 0 {
print!("0")
} else if num % 2 == 1 {
print!("1")
} else {
print!("no match condition");
}
}
循环
使用loops重复执行代码
loop
关键字会让Rust重复执行代码到明确要求停止
1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let mut nums = 0;
// 循环直到break表达式被触发,或程序结束
loop {
println!("continue");
nums += 1;
if nums > 5 {
break;
}
}
}
从循环返回结果
通过break
表达式,可以从循环中返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn main() {
let mut num = 0;
// 循环直到break语句被触发,或程序结束
loop {
println!("continue");
num += 1;
if num > 5 {
break;
}
}
let result = loop {
println!("not yet");
num += 1;
if num > 10 {
break num
}
};
print!("the result is {}", result);
}