当前位置: 首页 > news >正文

Rust 学习笔记:函数和控制流

Rust 学习笔记:函数和控制流

  • Rust 学习笔记:函数和控制流
    • 函数(Function)
    • 语句和表达式
    • 带返回值的函数
    • 注释
    • 控制流
      • if 表达式
      • 使用 else if 处理多个条件
      • 在 let 语句中使用 if
      • 循环
        • loop
        • 从循环中返回值
        • 循环标签消除多个循环之间的歧义
        • 带 while 的条件循环
        • 使用 for 循环遍历集合

Rust 学习笔记:函数和控制流

本篇文章介绍 Rust 的函数、注释和控制流。

函数(Function)

fn 关键字允许声明新函数。main 函数是程序的入口点。

在 Rust 中定义函数时,输入 fn,后跟函数名和一组圆括号。花括号告诉编译器函数体的开始和结束位置。

Rust 并不关心你在哪里定义你的函数,只关心它们被定义在调用者可以看到的作用域中。

示例:

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}");
}

运行结果:

在这里插入图片描述

在函数定义中,必须声明每个参数的类型。当定义多个参数时,用逗号分隔参数声明。

示例:

fn main() {print_labeled_measurement(5, 'h');
}fn print_labeled_measurement(value: i32, unit_label: char) {println!("The measurement is: {value}{unit_label}");
}

运行结果:

在这里插入图片描述

语句和表达式

在 Rust 中,函数体由一系列语句组成,可以由表达式结尾。

  • 语句是执行某些操作的指令,但不返回值。
  • 表达式计算并返回一个结果值。

函数定义也是语句。

创建变量并使用 let 关键字为其赋值是语句。因为语句不返回值,所以不能将 let 语句赋值给另一个变量。

示例:

fn main() {let x = (let y = 6);
}

运行报错:

在这里插入图片描述

let y = 6语句不返回值,因此没有任何东西可以绑定 x。这与其他语言(如 C)中的情况不同,在这些语言中,赋值返回赋值的值。在这些语言中,你可以写 x = y = 6,让 x 和 y 的值都是 6,但在 Rust 中却不是这样。

表达式的计算结果是一个值,并且构成了在 Rust 中编写的大部分其余代码。考虑一个数学运算,比如 5 + 6,它是一个计算值为 11 的表达式。表达式可以是语句的一部分,例如语句 let y = 6 中的 6 是求值为 6 的表达式。调用函数是一个表达式。调用宏是一个表达式。用大括号创建的新作用域块是一个表达式。

示例:

fn main() {let y = {let x = 3;x + 1};println!("The value of y is: {y}");
}

表达式

{let x = 3;x + 1
}

是一个块,在这个例子中,它的值是 4。作为 let 语句的一部分,这个值被绑定到 y 上。注意,x + 1 行末尾没有分号,这与目前看到的大多数行不同。

表达式不包括结束分号。如果在表达式的末尾添加分号,则将其转换为语句,然后它将不返回值。

带返回值的函数

函数可以向调用它们的代码返回值。我们不为返回值命名,但必须在箭头(->)后面声明它们的类型。在 Rust 中,函数的返回值与函数体块中最终表达式的值是同义的。通过使用 return 关键字并指定一个值,可以提前从函数返回,但是大多数函数隐式地返回最后一个表达式。下面是一个返回值的函数示例:

示例:

fn five() -> i32 {5
}fn main() {let x = five();println!("The value of x is: {x}");
}

在 five 函数中没有函数调用、宏,甚至没有 let 语句——只有数字 5 本身。这在 Rust 中是一个完全有效的函数。注意,函数的返回类型也被指定为 i32。

另一个示例:

fn main() {let x = plus_one(5);println!("The value of x is: {x}");
}fn plus_one(x: i32) -> i32 {x + 1
}

运行这段代码将打印 x 的值为 6。但是,如果我们在包含 x + 1 的行末尾放置一个分号,将其从表达式更改为语句,程序将报错。

注释

在 Rust 中,惯用的注释样式以两个斜杠开始注释,并且注释一直持续到行尾。对于超出单行的注释,你需要在每一行都包含 //,像这样:

// So we’re doing something complicated here, long enough that we need
// multiple lines of comments to do it! Whew! Hopefully, this comment will
// explain what’s going on.

注释可以放在它所注释的代码上方的单独一行,也可以放在包含以下代码的行尾。

fn main() {// I’m feeling lucky todaylet lucky_number = 7; // I’m feeling lucky today
}

控制流

在大多数编程语言中,根据条件是否为真来运行某些代码以及在条件为真时重复运行某些代码的能力是基本的构建块。Rust 代码执行流的最常见结构是 if 表达式和循环。

if 表达式

if 表达式允许根据条件对代码进行分支。提供一个条件,然后声明:“如果满足此条件,则运行此代码块;如果条件不满足,就不要运行这段代码。”

示例:

fn main() {let number = 3;if number < 5 {println!("condition was true");} else {println!("condition was false");}
}

运行结果:

在这里插入图片描述

同样值得注意的是,这段代码中的条件必须是 bool 类型。如果条件不是 bool 类型,我们将得到一个错误。例如,尝试运行以下代码:

fn main() {let number = 3;if number {println!("number was three");}
}

报错:

在这里插入图片描述

这个错误表明 Rust 期望的是 bool 值,但得到的却是整数。Rust 不会自动尝试将非布尔类型转换为布尔类型,必须明确地提供一个布尔值作为它的条件。

使用 else if 处理多个条件

可以通过在 else if 表达式中组合 if 和 else 来使用多个条件。

示例:

fn main() {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");}
}

当这个程序执行时,它依次检查每个 if 表达式,并执行条件为 true 的第一个语句体。

在 let 语句中使用 if

因为 if 是一个表达式,我们可以在 let 语句的右侧使用它将结果赋值给一个变量。

正确的示例:

fn main() {let condition = true;let number = if condition { 5 } else { 6 };println!("The value of number is: {number}");
}

有可能成为 if 的每个分支的结果的值必须是相同的类型,如果类型不匹配,将会报错。

错误的示例:

fn main() {let condition = true;let number = if condition { 5 } else { "six" };println!("The value of number is: {number}");
}

报错信息:

在这里插入图片描述

if 块中的表达式求值为整数,else 块中的表达式求值为字符串。这行不通,因为变量必须有单一类型,Rust 需要在编译时明确地知道 number 变量是什么类型。

循环

Rust 有三种循环:loop、while 和 for。我们每个都试试。

loop

loop 关键字将无限循环,直到显式停止程序。

示例:

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

大多数终端都支持键盘快捷键 Ctrl+C 来中断陷入连续循环的程序。

幸运的是,Rust 还提供了一种使用代码跳出循环的方法。可以在循环中放置 break 关键字,以告诉程序何时停止执行循环。

continue 关键字则在循环中告诉程序跳过此循环迭代中的任何剩余代码,并进入下一个迭代。

从循环中返回值

可以在用于停止循环的 break 表达式之后添加想要返回的值,该值将从循环中返回。

示例:

fn main() {let mut counter = 0;let result = loop {counter += 1;if counter == 10 {break counter * 2;}};println!("The result is {result}");
}

运行结果:

在这里插入图片描述

在循环之前,声明一个名为 counter 的变量,并将其初始化为 0。然后声明一个名为 result 的变量来保存循环返回的值。在循环的每次迭代中,我们给计数器变量加 1,然后检查计数器是否等于 10。如果是,则使用 break 关键字和值 counter * 2。循环结束后,使用分号结束将值赋给 result 的语句。最后,在 result 中打印值,在本例中为 20。

也可以从循环内部返回。break 只退出当前循环,而 return 总是退出当前函数。

循环标签消除多个循环之间的歧义

如果循环中有循环,则在该点中断并继续应用于最内层的循环。可以选择在循环上指定一个循环标签,然后可以使用 break 或 continue 来指定这些关键字应用于有标签的循环,而不是最内层的循环。循环标签必须以单引号开始。下面是两个嵌套循环的例子:

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!("End count = {count}");
}

外部循环具有 ‘counting_up’ 标签,它将从 0 数到 2。没有标签的内部循环从 10 数到 9。第一个没有指定标签的 break 只会退出内循环。

程序打印:

count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
带 while 的条件循环

示例:

fn main() {let mut number = 3;while number != 0 {println!("{number}!");number -= 1;}println!("LIFTOFF!!!");
}

这种结构消除了在使用 loop、if、else 和 break 时所必需的大量嵌套,并且更加清晰。当条件求值为 true 时,代码运行;否则,退出循环。

还可以使用 while 构造遍历集合(如数组)的元素。

示例:

fn main() {let a = [10, 20, 30, 40, 50];let mut index = 0;while index < 5 {println!("the value is: {}", a[index]);index += 1;}
}

这里,代码对数组中的元素进行计数。它从索引 0 开始,然后循环直到到达数组中的最终索引(也就是说,当 index < 5 不再为真时)。

然而,这种方法容易出错。如果索引值或测试条件不正确,我们可能会导致程序报错。例如,如果将 a 数组的定义更改为包含 4 个元素,但忘记将条件更新为 while index < 4,则代码将出现问题。它也很慢,因为编译器会添加运行时代码,在循环的每次迭代中执行索引是否在数组边界内的条件检查。

使用 for 循环遍历集合

作为一种更简洁的替代方法,可以使用 for 循环并为集合中的每个项执行一些代码。

示例:

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

这样做提高了代码的安全性,并消除了可能由于超出数组的末尾或不够远而丢失某些项而导致的错误的可能性。

相关文章:

  • tcp 和http 网络知识
  • 详解 Servlet 处理表单数据
  • 向量数据库实践:存储和检索向量数据
  • synchronization
  • 国产升压芯片SL4013能否支持输入三节锂电11V-12.6V升压至24V应用参数?
  • [特殊字符] Docker 从入门到实战:全流程教程 + 项目部署指南(含镜像加速)
  • uniapp-商城-38-shop 购物车 选好了 进行订单确认4 配送方式1
  • C++23 新特性深度落地与最佳实践
  • 79. 单词搜索
  • 图论---染色法(判断是否为二分图)
  • 深入解析 SMB 相关命令:smbmap、smbclient、netexec 等工具的使用指南
  • Python爬虫实战:获取网yi新闻网财经信息并做数据分析,以供选股做参考
  • 基于51单片机的超声波液位测量与控制系统
  • PMIC PCA9450 硬件原理全解析:为 i.MX 8M 平台供电的“大脑”
  • 23种设计模式-行为型模式之责任链模式(Java版本)
  • 4/24杂想
  • 30分钟算法题完成
  • 使用命令行加密混淆C#程序
  • python中的logging库详细解析
  • AI超级智能体教程(三)---程序调用AI大模型的四种方式(SpringAI+LangChain4j+SDK+HTTP)
  • 中法共创《海底两万里》,演员保剑锋重回戏剧舞台演船长
  • 安徽一交通事故责任认定引质疑:民警和司法鉴定人被处罚,已中止诉讼
  • “80后”李岩已任安徽安庆市领导
  • 公安部知识产权犯罪侦查局:侦破盗录传播春节档院线电影刑案25起
  • 目前中美未进行任何经贸谈判,外交部、商务部再次表明中方立场
  • 已有17个国家和国际组织、50多个国际科研机构加入国际月球科研站合作