03 - 通用编程概念
通用编程概念:变量、基本类型、函数、注释和控制流等。
一、变量与可变性
在之前也提到过,Rust 中的变量默认是不可变的,单从变量这个名称来讲,变量按理说量是可变的,但在 Rust 中却不可变,原因是 Rust 的核心是要保证安全,不过也提供让你可以使用的可变变量方法。在变量声明时使用 mut
关键字
二、变量与常量之间的不同
常量,记住一点就是常量总是需要标注类型。下面为声明一个常量的例子:
const MAX_POINTS: u32 = 100_000;
在 Rust 程序中,约定俗成地使用以下划线分隔的全大写字母来命名一个常量,并在数值中插入下划线来提高可读性。
三、隐藏
让我们来看一个隐藏例子
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x);
}
通过重复 let x =
语句将前一个值进行了隐藏,隐藏机制不同于将一个变量声明为 mut,因为如果不是在使用 let 关键字的情况下重新为这个变量赋值,则会导致编译错误。隐藏机制与 mut 的另一个区别在于,由于重复使用 let 关键字会创建出新变量,所以可以在复用变量名称的同时改变它的类型。
let spaces = " ";
let spaces = spaces.len();
以下却会报错
let mut spaces = " ";
spaces = spaces.len();
四、数据类型
数据类型的子集:标量类型和复合类型。
4.1 标量类型
Rust 中内建了 4 种基础的标题类型:整数、浮点数、布尔值及字符。
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
arch | isize | usize |
以上类型只说明最后一栏,isize 和 usize 两个特殊类型,它们的长度取决于程序运行的目标平台,在 64 位架构上,它们就是 64 位的,在 32 位架构上,它们就是 32 位的,两者主要用作于某些集合的索引。
在 Rust 中如果以 debug 模式编译,那么就会在程序中包含整数溢出的运行时检测代码,并在整数溢出发生时触发程序的 panic,Rust 用 panic 描述程序因为错误而退的情形;如果使用的 release 模式编译,那么就不会包含那些可能会触发 panic 的检查代码,作为替代,Rust 会在溢出发生时执行二进制补码环绕,简而言之,任何超出类型最大值的数值都会被 "环绕" 为类型的最小值。以 u8 为例,256 会变为 0,251 会变成为 1,以此类推。如果想显式地进行环绕行为,那么可以使用标准库中的 Wrapping 类型。
浮点类型 f32 和 f64,在 Rust 中,默认会将浮点数字字面量的类型推导为 f64。
let x = 2.0; // f64
let y: f32 = 3.0; // f32
布尔类型为 true 和 false 两类值
let t = true;
let f: bool = false; // 附带了显式类型标注的语句
字符类型 char 用单引号来表示,字符串用双引号
let c = 'r';
let z = 'R';
char 类型占 4 个字节,是一个 Unicode 标量值。
4.2 复合类型
Rust 提供了两种内置的基础复合类型:元组和数组。
// 声明一个元组的例子
let tup: (i32, f64, u8) = (500, 6.4, 1);
元组有一个固定的长度,无法在声明结束后增加或减少其中的元素数量。
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
将 tup 拆分为 3 个不同的部分:x、y 和 z,这个操作被称为解构,除了解构,还可以通过索引并使用点号来访问元组中的值。
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
println!("The value of fivee_handred is: {}", five_hundred);
println!("The value of six_point_four is: {}", six_point_four);
println!("The value of one is: {}", one);
}
数组与元组不同,数组中的每一个元素都必须是相同的类型,Rust 中的数组长度也是固定的,一旦声明后就也不能随意更改其大小,这与其他某些语言有所不同。
let a = [1, 2, 3, 4, 5];
当你想在栈上而不是堆上为数据分配空间时,或者想要确保总有固定数量的元素时,数组是一个非常有用的工具,Rust 标准库也提供了一个更加灵活的动态数组 (vector) 类型,动态数组可由用户自由调节数组长度,如果不确定什么时候使用普通数组还是动态数组,那就先使用动态数组就好了。
为了写出数组的类型,需要使用一对方括号,并在方括号中填写数组内所有元素的类型,一个分号及数组内元素的数量
let a: [i32; 5] = [1, 2, 3, 4, 5];
如果想创建一个含有相同元素的数组,可以在方括号中指定元素的值,并接着填入一个分号及数组的长度
let a = [3; 5]; // => let a = [3, 3, 3, 3, 3]
访问数组方式同 C 语言一样,非法访问索引外的数组中的值会出错。
五、函数
函数用 fn 关键字来声明,Rust 代码使用蛇形命名法 (只使用小写字母进行命名) 来作为规范函数和变量名称的风格。
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
函数可放在 main 函数前也可放在其后面,不像 C 语言,放在 main 函数后面还需要对函数进行声明。
5.1 函数参数
fn main() {
println!("Hello, world!");
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {}", x);
}
必须显式地声明每个参数的类型,这是在 Rust 设计中设计者们经过慎重考虑做出的决定,函数参数列表使用逗号分隔多个参数
fn main() {
println!("Hello, world!");
another_function(5, 6);
}
fn another_function(x: i32, y: i32) {
println!("The value of x is: {}", x);
println!("The value of y is: {}", y);
}
5.2 函数体中的语句和表达式
语句没有返回值,一个表达式有返回值,比如 x = y = 5 可能在别的语言成立,但在 Rust 中并不成立。
fn main() {
let x = 5;
// 以下为一个代码块,计算出 4 作为结果,赋给 y
// x + 1 后并没有分号
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {}", y);
}
5.3 函数返回值
可不用为返回值命名,但需要在箭头符号 (->) 的后面声明它的类型
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}
再来一个例子
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1
}
六、注释
Rust 中用 // 来表示注释。
七、控制流
Rust 中用来控制程序执行流的结构主要是 if 表达式与循环表达式。
7.1 if 表达式
if 后的表达式必须为一个 bool 类型的值
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
Rust 不会自觉尝试将非布尔类型的值转换为布尔类型,必须显式地在 if 表达式中提供一个布尔类型作为条件。
7.2 使用循环重复执行代码
Rust 提供了 loop、while、和 for 循环工具。loop 关键字来指示 Rust 反复执行某一块代码,直到显式地声明退出为止。
fn main() {
loop {
println!("again!");
}
}
以上代码会无限输出 again!,直到按 Ctrl + C 强制退出。另外可使用 break 关键字来通知程序退出
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
while 循环例 1
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number = number - 1;
}
println!("LIFTOFF!!!");
}
while 循环例 2
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index = index + 1;
}
}
以上代码和下面这段代码等效,效率更高
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("The value is: {}", element);
}
}
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
八、练习
婓波那契数列
use std::io;
fn main() {
println!("请输入婓波那契数列阶数(n > 2):");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
let number: i32 = match input.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入整数,请勿输入无关符号!");
return;
}
};
println!("婓波那契数列:");
let mut i = 1;
while i <= number {
print!("{} ", fibonacci(i));
i = i + 1;
}
}
fn fibonacci(x: i32) -> i32 {
if x < 3 {
1
} else {
fibonacci(x - 2) + fibonacci(x - 1)
}
}