2024-01-27
Rust
0
请注意,本文编写于 516 天前,最后修改于 516 天前,其中某些信息可能已经过时。

目录

变量与可变性
变量
常量
隐藏
数据类型
标量类型
整型
浮点型
数值运算
布尔型
字符类型
复合类型
元组类型
数组类型
函数
参数
语句和表达式
具有返回值的函数
注释
单行注释
文档注释
控制流
if 表达式
基本使用
使用 else if 处理多重条件
在 let 语句中使用 if
使用循环重复执行
使用 loop 重复执行代码
while 条件循环
使用 for 遍历集合
在 for 中使用 Range
尝试
相互转换摄氏与华氏温度
生成第n个斐波那契数

变量与可变性

变量

变量使用 let 定义

  1. 不可变变量 当变量不可变时,一旦值被绑定一个名称上,就不能改变这个值 Rust 鼓励利用不可变性

    Rust 编译器保证,如果声明一个值不会变,它就真的不会变,所以你不必自己跟踪它。这意味着你的代码更易于推导。

  2. 可变变量 在变量名前添加 mut 来使其可变

常量

常量使用 const 定义

  1. 不允许对常量使用 mut
  2. 常量默认不可变且总不可变
  3. 常量定义时必须注明值的类型
  4. 常量可以在任何作用域中声明,包括全局作用域
  5. 常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值
  6. Rust 对常量的命名约定是在单词之间使用全大写加下划线
  7. 编译器能够在编译时计算一组有限的操作,这使我们可以选择以更容易理解和验证的方式写出此值,而不是直接设置为一个数值
rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

隐藏

rust
fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {x}"); } println!("The value of x is: {x}"); }

let 一个同名变量时,编译器将看到第二个变量。

实际上,第二个变量“遮蔽”了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被隐藏或第二个变量的作用域结束。

可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏。

隐藏和 mut 的区别:

  1. 当不小心尝试对变量重新赋值时,如果没有使用 let 关键字,就会导致编译时错误。通过使用 let,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的。

  2. 当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。例如:

    rust
    let spaces = " "; let spaces = spaces.len();

    若使用 mut 修改变量的值,则不能改变变量的类型。

数据类型

在 Rust 中,每一个值都属于某一个 数据类型data type)。

Rust 是 静态类型statically typed)语言,也就是说在编译时就必须知道所有变量的类型。

编译器通常可以推断出我们想要用的类型,但当多种类型均有可能时(比如使用 parseString 转换为数字时),必须增加类型注解。(有点类似ts)

rust
let guess: u32 = "42".parse().expect("Not a number!"); // 不加会报错

标量类型

标量scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。

整型

Rust 数字类型默认是 i32isizeusize 主要作为某些集合的索引。

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

范围:

有符号:(2n1)2n11无符号:02n1有符号:-(2^{n - 1}) 到 2^{n - 1} - 1 \\ 无符号:0 到 2^n - 1

isizeusize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。

数字字面值例子
Decimal (十进制)98_222
Hex (十六进制)0xff
Octal (八进制)0o77
Binary (二进制)0b1111_0000
Byte (单字节字符)(仅限于u8)b'A'

可以以上表任意一种形式编写数字字面值

可以是多种数字类型的数字字面值允许使用类型后缀,例如 57u8 来指定类型,同时也允许使用 _ 做为分隔符以方便读数,例如1_000,它的值与你指定的 1000 相同。

整型溢出

  • --debug:Rust检查这类问题并使程序 panic
  • --release:Rust不会**检测会导致 panic 的整型溢出,而是进行二进制补码wrapping(比此类型能容纳最大值还大的值会回绕到最小值,值 256 变成 0,值 257 变成 1
  • 依赖整型溢出 wrapping 的行为被认为是一种错误。

为了显式地处理溢出的可能性,可以使用这几类标准库提供的原始数字类型方法:

  • 所有模式下都可以使用 wrapping_* 方法进行 wrapping,如 wrapping_add
  • 如果 checked_* 方法出现溢出,则返回 None
  • overflowing_* 方法返回值和一个布尔值,表示是否出现溢出
  • saturating_* 方法在值的最小值或最大值处进行饱和处理

浮点型

Rust 的浮点数类型是 f32f64,分别占 32 位和 64 位。默认类型是 f64

rust
fn main() { let x = 2.0; // f64 let y: f32 = 3.0; // f32 }

数值运算

加法、减法、乘法、除法和取余

整数除法:向零舍入到最接近的整数(正整数向下取整,负整数向上取整)

rust
fn main() { // addition let sum = 5 + 10; // subtraction let difference = 95.5 - 4.3; // multiplication let product = 4 * 30; // division let quotient = 56.7 / 32.2; let truncated = -5 / 3; // 结果为 -1 // remainder let remainder = 43 % 5; }

布尔型

使用 bool 表示,值为 truefalse

字符类型

使用 char 表示,用单引号声明,是最原生的字母类型,大小为四个字节。

rust
let c = 'z'; let z: char = 'ℤ'; // with explicit type annotation let heart_eyed_cat = '😻';

在 Rust 中,带变音符号的字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。

复合类型

复合类型Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。

元组类型

元组长度固定,一旦声明,其长度不会增大或缩小。元组中的每一个位置都有一个类型,不必相同。

元组可以用解构或索引取值。

rust
let tup = (500, 6.4, 1); // 解构 let (x, y, z) = tup; println!("{x}, {y}, {z}"); let t: (i32, f64, u8) = (500, 6.4, 1); let num1 = t.0; let num2 = t.1; let num3 = t.2; println!("num1: {num1}, num2: {num2}, num3: {num3}");

不带任何值的元组有个特殊的名称,叫做 单元(unit) 元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。

数组类型

数组中元素类型必须相同,数组在栈 (stack) 上为数据分配空间。

数组的长度是固定的,如果希望伸缩长度,应该使用 vector

rust
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; let a: [i32; 5] = [1, 2, 3, 4, 5]; // 数组包含 5 个元素,元素的值为 3 let a = [3; 5];

数组的元素访问使用方括号:a[0],如果进行无效的元素访问(例如超出数组长度),Rust会立即退出而不是允许内存访问并继续执行。

函数

Rust 代码中的函数和变量名使用 snake case 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。

rust
fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("Another function."); }

函数可以定义在任何位置,只要其在可被调用的作用域内即可,像这样也可以:

rust
pub fn tuples() { let tup = (500, 6.4, 1); let (x, y, z) = tup; println!("{x}, {y}, {z}"); ab(); fn ab() { println!("hello"); } }

参数

必须声明每个参数的类型,参数之间使用逗号分隔。

rust
fn show_number(num: i32) { println!("The number is: {num}"); }

语句和表达式

语句Statements)是执行一些操作但不返回值的指令。 表达式Expressions)计算并产生一个值

let y = 6 是一个语句,其中 6 是表达式,表达式可以是语句的一部分。

大部分 Rust 代码都是由表达式组成的,例如数学运算 5*6 是一个表达式,函数调用是一个表达式,宏调用是一个表达式,用大括号创建一个新的块作用域也是一个表达式。例如:

rust
{ let x = 3; x + 1 }

这个代码块是一个表达式,值为4。

表达式的结尾**没有分号**。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值。

具有返回值的函数

具有返回值的函数,应当用 -> 声明返回值的类型。

rust
pub fn get_five() -> i32 { 5 }

在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。

rust
fn plus_one(x: i32) -> i32 { x + 1 }

注释

单行注释

可以独立一行存在,也可以在代码行的末尾

rust
// 这是一行注释

文档注释

文档注释使用三斜杠 /// 而不是双斜杠以支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。

rust
/// Adds one to the number given. /// /// # Examples /// /// ``` /// let arg = 5; /// let answer = my_crate::add_one(arg); /// /// assert_eq!(6, answer); /// ``` pub fn add_one(x: i32) -> i32 { x + 1 }

常见的文档注释标题:

  • Example:运行示例,文档注释中增加示例代码块可以用来进行 cargo test
  • Panics:这个函数可能会 panic! 的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。
  • Errors:如果这个函数返回 Result,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。
  • Safety:如果这个函数使用 unsafe 代码(这会在第十九章讨论),这一部分应该会涉及到期望函数调用者支持的确保 unsafe 块中代码正常工作的不变条件(invariants)。

文档注释风格 //! 为包含注释的项,而不是位于注释之后的项增加文档。这通常用于 crate 根文件(通常是 src/lib.rs)或模块的根文件为 crate 或模块整体提供文档。

作为一个例子,为了增加描述包含 add_one 函数的 my_crate crate 目的的文档,可以在 src/lib.rs 开头增加以 //! 开头的注释。注意 //! 的最后一行之后没有任何代码。

rust
//! # My Crate //! //! `my_crate` is a collection of utilities to make performing certain //! calculations more convenient. /// Adds one to the number given. // --snip--

控制流

根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码的能力是大部分编程语言的基本组成部分。

if 表达式

基本使用

if 表达式代码中的条件必须bool 值,如果不是,Rust将会抛出错误:Rust 并不会尝试自动地将非布尔值转换为布尔值,必须总是显式地使用布尔值作为 if 的条件。

rust
fn main() { let number = 3; if number < 5 { println!("条件为真"); } else { println!("条件为假"); } }

使用 else if 处理多重条件

rust
fn main() { let number: i32 = 56; if number % 4 == 0 { println!("number能被4整除"); } else if number % 3 == 0 { println!("number能被3整除"); } else if number % 2 == 0 { println!("number能被2整除"); } else { println!("其他情况") } }

使用过多的 else if 表达式会使代码显得杂乱无章,所以如果有多于一个 else if 表达式,最好重构代码(例如使用 match)。

let 语句中使用 if

因为 if 是一个表达式,可以在 let 语句中,将 if 表达式的返回值赋给一个变量,表达式的返回值必须是相同类型

rust
fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("符合条件的数字是:{number}"); }

注意

代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。

使用循环重复执行

使用 loop 重复执行代码

loop 关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。

rust
fn main() { loop { println!("again") } }

loop 的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的 break 表达式,它会被停止的循环返回:

rust
fn cou() { let mut counter = 0; let result = loop { counter += 1; if(counter == 10) { // 使用break关键字返回值 counter * 2 break counter * 2; } }; println!("结果是:{result}") }

如果存在嵌套循环,这时 breakcontinue 作用于最内层循环,可以为一个循环指定一个循环标签,然后将标签与 breakcontinue 一起使用,使这些关键字作用于打了标签的循环

rust
fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if(remaining == 9) { break; } if(count == 2) { break 'counting_up; } remaining -= 1; } count += 1; } println!("最终count的值是{count}") }

该段代码的执行结果为:

image.png

while 条件循环

rust
fn main() { let mut number: i32 = 3; while number > 0 { println!("number: {number}"); number -= 1; } println!("循环结束"); }

使用 for 遍历集合

使用 while 也可以通过不断递增索引值来遍历集合,但是如果索引长度或测试条件不正确会导致程序panic(例如更改了数组长度却忘记更新条件)

rust
fn main() { let a: [i32; 5] = [10, 20, 30, 40, 50]; for element in a { println!("{element}"); } }

for 中使用 Range

rust
fn main() { // rev 方法用来反转 range for number in(1..4).rev() { println!("{number}"); } println!("循环结束"); }

尝试

相互转换摄氏与华氏温度

rust
use std::io; fn main() { loop { println!("请选择要输入的温度类型:"); println!("1.摄氏温度"); println!("2.华氏温度"); println!("其他数字:退出"); let mut convert_type: String = String::new(); let mut temperature: String = String::new(); io::stdin().read_line(&mut convert_type).expect("读入失败"); let convert_type: u32 = match convert_type.trim().parse() { Ok(num) => num, Err(_) => continue, }; if convert_type != 1 && convert_type != 2 { break; } println!("请输入温度:"); io::stdin().read_line(&mut temperature).expect("读入失败"); let temperature: f64 = match temperature.trim().parse() { Ok(num) => num, Err(_) => continue, }; if convert_type == 1 { let fahrenheit = 32.0 + temperature * 1.8; println!("华氏温度为:{fahrenheit}°F"); } else if convert_type == 2 { let celsius = (temperature - 32.0) / 1.8; println!("摄氏温度为:{celsius}°C"); } else { break; } } }

生成第n个斐波那契数

rust
use std::io; fn main() { let mut n: String = String::new(); io::stdin().read_line(&mut n).expect("读入失败"); let mut n: u32 = match n.trim().parse() { Ok(num) => num, Err(_) => return, }; let mut prev_number: u32 = 1; let mut next_number: u32 = 1; let mut result: u32 = 0; if n == 1 { result = prev_number; } else if n == 2 { result = next_number; } else { while n > 2 { result = prev_number + next_number; prev_number = next_number; next_number = result; n -= 1; } } println!("第{n}个斐波那契数是{result}"); }

本文作者:Morales

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!