通用编程概念
前言
又开新坑了,笔记系列主要学习了 Rust 官方的文档,配合 B 站的视频效果更佳。
本章节主要介绍了一些 Rust 通用的基础概念,其实这部分对于已经有编程经验的人来说都很了解,可以快速浏览下了解 Rust 与其他语言的一些细节不同之处。
话不多说,Let’s Go.
变量与可变性
- 声明变量使用 let 关键字。
- 默认情况下,变量是不可变的。
- 使用 mut 标记可变变量。
let x = 5;
println!("The value of x is {}", x);
x = 6;
error[E0384]: cannot assign twice to immutable varia
ble `x`
--> src/main.rs:6:1
|
4 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutab
le: `mut x`
5 | println!("The value of x is {}", x);
6 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
变量与常量
- 常量(constant),常量在绑定值以后也是不可变的,但是它与不可变的变量有些区别:
- 不可以使用 mut, 常量永远都是不可变的。
- 声明常量使用 const 关键字,它的类型必须显式的标注。
- 常量可以在任何作用域内声明,包括全局作用域。
- 常量只可以绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值。
- 在程序运行期间,常量在其声明的作用域内一直有效。
- 命名规范:Rust 里常量使用全大写字母,每个单词之间用下划线分隔开,例如:
const MAX_POINTS: u32 = 100_000;
。
Shadowing 隐藏
可以使用相同的名字声明新的变量,新的变量就会 shadow 之前声明的同名变量。
- 在后续的代码中这个变量名代码的就是新的变量。
let x = 5; println!("The value of x is {}", x); // 5 //x = 6; error let x = 6; //shadowing println!("The value of x is {}", x); // 6
shadow 和把变量标记为 mut 是不一样的:
- 如果不使用 let 关键字,那么重新给非 mut 变量进行赋值时会导致编译错误。
- 而使用 let 声明的同名新变量,也是不可变的。
- 使用 let 声明的同名新变量,它的类型可以与之前不同。
let space = " "; // str let space = space.len(); // usize println!("now space value is {}", space);
数据类型
标量和复合类型。
Rust 是静态编译语言,在编译时必须知道所有变量的类型:
- 基于使用的值,编译器通常能够推断出它的具体类型。
- 但如果可能的类型比较多(例如把 String 转为整数 parse
方法),就必须添加类型的显式标注,否则编译会报错。
let guess = "42".parse().expect("Not a number"); error[E0282]: type annotations needed --> src/main.rs:16:9 | 16 | let guess = "42".parse().expect("Not a numb er"); | ^^^^^ consider giving `guess` a type // 需要指定类型 let guess: u32 = "42".parse().expect("Not a number");
标量类型
整数类型
整数类型没有小数部分。
无符号整数类型以 u 开头。例如 u32 就是一个无符号的整数类型,占据 32 位的空间。
有符号整数类型以 i 开头。
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 isize 与 usize 类型的位数由程序运行的计算机架构所决定:
- 如果是 64 位计算机,那就是 64 位。
- 32 位同理。
整数的字面值,除了 byte 类型外,所有的数值字面类型都允许使用类型后缀:
- 不清楚应该使用那种类型,可以使用 Rust 默认类型:
- 整数默认 i32 :速度很快针对性优化。
Number literals Example Decimal 98_222 Hex 0xff Octal 0077 Binary 0b1111_0000 Byte(u8 only) b’A’ 整数溢出,在不同的编译模式下有不同效果:
- 例如在 u8 的范围是 0-255,如果设置为 256:
- 在 debug 模式下,Rust 会检测整数溢出,发生溢出后程序在运行时就会 panic。
- 在 release 模式下,Rust 不会检测整数溢出,如果溢出则会进行
环绕
操作,即 256 变为 0。
- 例如在 u8 的范围是 0-255,如果设置为 256:
浮点类型
Rust 有两种基础的浮点类型,也就是含有小数部分的类型:
- f32,32 位,单精度。
- f64,64 位,双精度。
Rust 的浮点类型使用 IEEE-754 标准来进行描述。
f64 是默认类型,因为在现代 CPU 上 f64 和 f32 的速度出啊不多,而且精度更高。
let x = 2.0; // f64 let y: f32 = 3.0; // f32
数值操作:
+ - * / %
。
布尔类型
- Rust 的布尔类型也只有两个值:true 和 false。
- 占用一个字节大小,符号是 bool。
字符类型
- Rust 语言中 char 类型被用来描述语言中最基础的单个字符。
- 字符类型的字面值使用单引号,占用 4 字节大小。
- 是 Unicode 标量值,可以表示比 ASCII 多得多的字符内容:拼音、中日韩文、零长度空白字符、emoji
表情等。
复合类型
- 复合类型可以将多个值放在一个类型里。
- Rust 提供里两种基础的复合类型:元组(Tuple)、数组。
Tuple
Tuple 可以将多个类型的多个值放在一个类型里。
Tuple 的长度是固定的:一旦声明就无法改变。
let tup: (i32, f64, u8) = (500, 6.4, 1); println!("{},{},{}", tup.0, tup.1, tup.2);
获取 tuple 的元素值:
- 可以使用模式匹配来解构(destructure)一个 tuple 进而获取元素的值。
- 也可以像上边例子中使用元素索引号进行获取。
let (x, y, z) = tup;
println!("x={},y={},z={}", x, y, z); // 500 6.4 1
数组
数组也可以将多个值放在一个类型里。
数组中每个元素的类型必须相同。
数组的长度也是固定的。
let a = [1, 2, 3, 4, 5];
数组的用处。
- 如果想要让数据存储在 stack 上而不是 heap,或者想保证固定的元素,这时使用数组更有好处。
- 数组没有 Vector 灵活:Vector 与数组类似,由标准库提供而数组由预导入模块提供。
- Vector 长度可变,当不确定使用哪种时,优先使用 Vector。
数组的类型:
- 数组的类型表示格式:
[类型;长度]
==>let a: [i32; 5] = [1,2,3,4,5];
- 如果数组中每个元素值都相同的,那么可以使用如下初始化方法:
let a = [3; 5];
等同let a = [3,3,3,3,3];
。
- 数组的类型表示格式:
访问数组的元素:
- 数组是 stack 上分配的单个块的内存。
- 可以使用索引来访问数组的元素。
let a = [1, 2, 3, 4, 5]; println!("a[0] = {}, a[2] = {}", a[0], a[2]); // 1、3
- 如果访问的索引超出来数组的范围,那么:
- 编译会通过。
- 运行时会报错(runtime 时会 panic),Rust 不允许其继续访问相应地址的内存。
函数
- 声明函数使用
fn
关键字。 - 依照惯例,针对函数和变量名,Rust 使用 snake case 命名规范:
- 所有的字母都是小写的,单词之间使用下划线分隔开。
函数的参数
parameters、arguments,即形参与实参。
在函数签名里,必须声明每个参数的类型。
fn main() { another_function(5, 6); // argument } // 参数需要指明类型 // parameter fn another_function(x: i32, y: i32) { println!("the value of x is: {}", x); println!("the value of y is: {}", y); }
函数体中的语句与表达式
- 函数体由一系列语句组成,可选的由一个表达式结束。
- Rust 是一个基于表达式的语言。
- 语句是执行一些动作的命令。
- 表达式会计算产生一个值。
- 函数定义也是语句。
- 语句不返回值,所以不可以使用 let 将一个语句赋给一个变量。
//函数声明语句
fn function() {
let y = 5 + 6; //y = 绑定语句 , 5 + 6 不包括分号是表达式
//let x = (let z = 7); err: 语句不能被赋值
let x = 5;
let y = {
// {} 其中包含 x + 3 是块中的最后一个表达式,相当于返回值
let x = 1;
x + 3 // 如果加上分号使其变为语句 x+3; 则返回的知识一个空的 tuple ()
};
println!("The value of y is : {}", y)
}
函数的返回值
- 在符号 -> 后边声明函数返回值的类型,但是不可以为返回值命名。
- 在 Rust 里面,返回值就是函数体里面最后一个表达式的值。
- 若想提前返回,需要使用 return 关键字,并指定一个值。
- 大多数函数都是默认使用最后一个表达式为返回值。
fn five() -> i32 {
5 // 返回最后一个表达式 5
}
fn plus_five(x: i32) -> i32 {
x + 5 // 返回表达式 x+5 的值,加上分号就变成语句了
}
控制流
if 表达式
if 表达式允许根据条件来执行不同的代码分支。
- 条件必须是 bool 类型。
if 表达式中,与条件相关联的代码块叫做分支。
可选的,在后边可以加上一个 else 表达式。
使用 else if 处理多重条件。
- 如果使用了多余一个 else if,那么最好使用 match 来重构代码。
fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3 or 2"); } }
在 let 语句中使用 if:因为 if 是一个表达式,所以可以将它放在 let
语句中等号右边。fn ifvar() { let condition = true; let number = if condition { 5 } else { 6 }; // 两侧的类型需要一致 println!("The value of number is : {}", number); }
循环
- Rust 提供了三种循环:loop、while 和 for。
loop
loop 关键字告诉 Rust 反复执行一块代码,直到喊停为止。或者使用 break
来进行中止。let mut count = 0; let result = loop { count = count + 1; println!("loop count : {}", count); if count == 10 { break count * 2; } };
while 条件循环
每次执行循环体前都要判断一次条件。
let mut number = 3; while number != 0 { println!("{}!", number); number = number - 1; } println!("LIFTOFF!!!!");
for 循环遍历集合
- 可以使用 while 或 loop 遍历集合,但是易错且低效。
- 使用 for 循环更加简洁紧凑,它可以针对集合中的每个元素来执行一些代码。
let a = [10, 20, 30, 40, 50];
// iter 迭代器
for element in a.iter() {
println!("the value is : {}", element);
}
Range
- 标准库提供。
- 指定一个开始数字和一个结束数字,Range 可以生成它们之间的数字(不包含结束)。
- rev 方法可以反转 Range。
fn for_range() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}