Rust语言关键字
- let
- mut
- fn
- const
- if
- else
变量和常量
变量和常量的区别
不允许改变值的变量,可能会使你想起另一个大部分编程语言都有的概念:常量(constants)。类似于不可变变量,常量是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
首先,不允许对常量使用mut
。常量不光默认不能变,它总是不能变。
声明常量使用const
关键字而不是let
,并且 必须 注明值的类型。在下一部分,“数据类型” 中会介绍类型和类型注解,现在无需关心这些细节,记住总是标注类型即可。
常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
最后一个区别是,常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。隐藏
我们可以定义一个与之前变量同名的新变量,而新变量会 隐藏 之前的变量。Rustacean 们称之为第一个变量被第二个 隐藏 了,这意味着使用这个变量时会看到第二个值。可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏
隐藏与将变量标记为 mut 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 let 关键字,就会导致编译时错误。通过使用 let,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。
mut 与隐藏的另一个区别是,当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,但复用这个名字。
数据类型
标量(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 |
Rust 中的整型字面值
数字字面值 | 例子 |
---|---|
Decimal (十进制) | 98_222 |
Hex (十六进制) | 0xff |
Octal (八进制) | 0o77 |
Binary (二进制) | 0b1111_0000 |
Byte (单字节字符)(仅限于u8) | b'A' |
整型溢出
比方说有一个 u8 ,它可以存放从零到 255 的值。那么当你将其修改为 256 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时,Rust 检查这类问题并使程序 panic,这个术语被 Rust 用来表明程序因错误而退出。第九章 “panic! 与不可恢复的错误” 部分会详细介绍 panic。
在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码包装(two’s complement wrapping)的操作。简而言之,256 变成 0,257 变成 1,依此类推。依赖整型溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping。
浮点型
Rust 也有两个原生的 浮点数(floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。
数值运算
#![allow(unused)]
fn main() {
// 加法
let sum = 5 + 10;
// 减法
let difference = 95.5 - 4.3;
// 乘法
let product = 4 * 30;
// 除法
let quotient = 56.7 / 32.2;
// 取余
let remainder = 43 % 5;
}
布尔型
true / false
字符类型
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
}
复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
元组类型
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
#![allow(unused)]
// tup 变量绑定到整个元组上,因为元组是一个单独的复合元素。
// 为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
// 程序首先创建了一个元组并绑定到 tup 变量上。接着使用了 let 和一个模式将 tup 分成了三个不同的变量,x、y 和 z。
// 这叫做 解构(destructuring),因为它将一个元组拆成了三个部分。最后,程序打印出了 y 的值,也就是 6.4。
#![allow(unused)]
// 除了使用模式匹配解构外,也可以使用点号(.)后跟值的索引来直接访问它们。
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;
}
数组类型
另一个包含多个值的方式是 数组(array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。
当你想要在栈(stack)而不是在堆(heap)上为数据分配空间(第四章将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector。
#![allow(unused)]
fn main() {
let a = [1, 2, 3, 4, 5];
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // 变量名为 a 的数组将包含 5 个元素,这些元素的值最初都将被设置为 3。
// 这种写法与 let a = [3, 3, 3, 3, 3]; 效果相同,但更简洁。
// 数组是一整块分配在栈上的内存。可以使用索引来访问数组的元素
let first = a[0];
let second = a[1];
}
函数
函数遍布于 Rust 代码中。你已经见过语言中最重要的函数之一:main 函数,它是很多程序的入口点。你也见过 fn 关键字,它用来声明新函数。
Rust 代码中的函数和变量名使用 snake case 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
函数参数
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {}", x);
}
在函数签名中,必须 声明每个参数的类型。这是 Rust 设计中一个经过慎重考虑的决定:要求在函数定义中提供类型注解,意味着编译器不需要你在代码的其他地方注明类型来指出你的意图。
fn main() {
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);
}
fn main() {
let x = 5;
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {}, x is {}", y, x);
}
具有返回值的函数
fn five() -> i32 {
5
}
fn six() -> i32 {
return 6;
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
let x = six();
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 {
println!("The value of x is: {}", x);
let y = x + 2;
println!("The value of y is: {}", y);
x + 1
}
注释
fn main() {
let x = plus_one(5);
// 行内注释是这样吧
println!("The value of x is: {}", x); // 或者这样
}
/**
* 文档注释是这样吧
*/
fn plus_one(x: i32) -> i32 {
println!("The value of x is: {}", x);
let y = x + 2;
println!("The value of y is: {}", y);
x + 1
}
控制流
if 表达式
/**
* 所有的 if 表达式都以 if 关键字开头,其后跟一个条件。
* 在这个例子中,条件检查变量 number 的值是否小于 5。
* 在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。
* if 表达式中与条件关联的代码块有时被叫做 arms,
* 就像 “比较猜测的数字和秘密数字” 部分中讨论到的 match 表达式中的分支一样。
*
* 也可以包含一个可选的 else 表达式来提供一个在条件为假时应当执行的代码块,这里我们就这么做了。
* 如果不提供 else 表达式并且条件为假时,程序会直接忽略 if 代码块并继续执行下面的代码。
*/
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
代码中的条件 必须 是 bool 值。如果条件不是 bool 值,我们将得到一个错误。
在 let 语句中使用 if
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 if 表达式的值取决于哪个代码块被执行。这意味着 if 的每个分支的可能的返回值都必须是相同类型
if 代码块中的表达式返回一个整数,而 else 代码块中的表达式返回一个字符串。这不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 number 变量的类型,这样它就可以在编译时验证在每处使用的 number 变量的类型是有效的。Rust 并不能够在 number 的类型只能在运行时确定的情况下工作;这样会使编译器变得更复杂而且只能为代码提供更少的保障,因为它不得不记录所有变量的多种可能的类型。
错误示例:
fn main() { let condition = true; let number = if condition { 5 } else { "six" }; println!("The value of number is: {}", number); }
使用循环重复执行
多次执行同一段代码是很常用的,Rust 为此提供了多种 循环(loops)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。为了实验一下循环,让我们新建一个叫做 loops 的项目。
Rust 有三种循环:loop、while 和 for。我们每一个都试试。
使用loop重复执行代码
loop 关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。
fn main() {
loop {
println!("again!");
}
}
使用 break 关键字来告诉程序何时停止循环。
loop 的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的 break 表达式,它会被停止的循环返回:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
while 条件循环
在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 break 停止循环。这个循环类型可以通过组合 loop、if、else 和 break 来实现;如果你喜欢的话,现在就可以在程序中试试。
然而,这个模式太常用了,Rust 为此内置了一个语言结构,它被称为 while 循环。
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number = number - 1;
}
println!("LIFTOFF!!!");
}
使用 for 遍历集合
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!!!");
}
- 综合例子
fn main() {
loop {
println!("请输入工具编号:");
println!("1: 华氏温度转为摄氏度");
println!("2: 摄氏度转为华氏温度");
println!("3: 生成 n 阶斐波那契数列");
println!("4: 打印圣诞颂歌“The Twelve Days of Christmas” 的歌词");
println!("0: 退出");
let tn = loop {
let mut tn = String::new();
std::io::stdin().read_line(&mut tn).expect("Failed to read line");
let tn: usize = match tn.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("输入错误,请重新输入工具编号:");
continue;
},
};
if tn == 0 || tn == 1 || tn == 2 || tn == 3 || tn == 4 {
break tn;
}
println!("输入错误,请重新输入工具编号:");
};
println!("你输入了: {}", tn);
println!();
if tn == 0 {
println!("你选择退出, 下次再见咯 ^_^");
return;
}
let tn = tn - 1;
let tools_arr = ["华氏温度转为摄氏度", "摄氏度转为华氏温度", "生成 n 阶斐波那契数列", "打印圣诞颂歌“The Twelve Days of Christmas” 的歌词"];
println!("你选择的工具为: {}", tools_arr[tn]);
println!();
if tn == 0 {
translate_to_c();
} else if tn == 1 {
translate_to_h();
} else if tn == 2 {
nb();
} else if tn == 3 {
pc();
}
println!("\n\n");
}
}
/**
* 相互转换摄氏与华氏温度。
* 1 摄氏度=33.8 华氏度
* 1 华氏度=-17.2222222 摄氏度
* */
fn translate_to_c() {
println!("请输入华氏温度:");
let h;
let tn = loop {
let mut tn = String::new();
std::io::stdin().read_line(&mut tn).expect("Failed to read line");
let tn: f32 = match tn.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("输入错误,请重新输入华氏温度:");
continue;
},
};
h = tn;
break 5.0 * (tn - 32.0 ) / 9.0;
};
println!("{}℉ = {}℃", h, tn);
}
/**
* 相互转换摄氏与华氏温度。
* 1 摄氏度=33.8 华氏度
* 1 华氏度=-17.2222222 摄氏度
* */
fn translate_to_h() {
println!("请输入摄氏温度:");
let c;
let tn = loop {
let mut tn = String::new();
std::io::stdin().read_line(&mut tn).expect("Failed to read line");
let tn: f32 = match tn.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("输入错误,请重新输入摄氏温度:");
continue;
},
};
c = tn;
break 9.0 * tn / 5.0 + 32.0;
};
println!("{}℃ = {}℉", c, tn);
}
/** 打印 n 阶斐波那契数列。*/
fn nb() {
println!("请输入阶数 n:");
let n = loop {
let mut tn = String::new();
std::io::stdin().read_line(&mut tn).expect("Failed to read line");
let tn: usize = match tn.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("输入错误,请重新输入阶数 n:");
continue;
},
};
break tn;
};
println!("即将打印{}阶斐波那契数列:", n);
for elem in 0..n+1 {
print!("{} ", fbnq(elem));
}
println!();
}
/** 生成 n 阶斐波那契数列 */
fn fbnq(n: usize) -> usize {
if n == 0 || n == 1 {
return n;
}
fbnq(n - 1) + fbnq(n-2)
}
/** 打印圣诞颂歌 “The Twelve Days of Christmas” 的歌词,并利用歌曲中的重复部分(编写循环)。*/
fn pc() {
// 略
}