rust 程序,rust编程开发教程

软件开发 | Rust 基础系列 #8:编写里程碑 Rust 程序

到目前为止,我们已经讲解了包括 变量、可变性、常量、数据类型、函数、if-else 语句和循环在内的一些关于 Rust 编程的基础知识。

在 Rust 基础系列的最后一章里,让我们现在用 Rust 编写一个程序,使用这些主题,以便更好地理解它们在现实世界中的用途。让我们来编写一个相对简单的程序,用来从水果市场订购水果。

我们程序的基本结构

来让我们首先向用户问好,并告诉他们如何与程序交互。

fn main { println!("欢迎来到水果市场!"); println!("请选择要购买的水果。n"); println!("n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄"); println!("购买完成后,请输入“quit”或“q”。n");}

获取用户输入

上面的代码非常简单。目前,你不知道接下来该做什么,因为你不知道用户接下来想做什么。

所以让我们添加一些代码,接受用户输入并将其存储在某个地方以便稍后解析,然后根据用户输入采取适当的操作。

use std::io;fn main { println!("欢迎来到水果市场!"); println!("请选择要购买的水果。n"); println!("n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄"); println!("购买完成后,请输入“quit”或“q”。n"); // 获取用户输入 let mut user_input = String::new; io::stdin .read_line(&mut user_input) .expect("无法读取用户输入。");}

有三个新元素需要告诉你。所以让我们对这些新元素进行浅层次的探索。

1. 理解 use 关键字

在这个程序的第一行,你可能已经注意到我们“使用”(哈哈!)了一个叫做 use的新关键字。Rust 中的use关键字类似于 C/C++ 中的#include指令和 Python 中的import关键字。使用use关键字,我们从 Rust 标准库std中“导入”了io(输入输出)模块。

LCTT 译注:“使用”在原文中为“use”,与新介绍的关键字一样。

你可能会想知道为什么我们在可以使用 println宏来将某些内容输出到标准输出时,导入io模块是必要的。Rust 的标准库有一个叫做prelude的模块,它会自动被包含。该模块包含了 Rust 程序员可能需要使用的所有常用函数,比如println宏。(你可以在这里阅读更多关于std::prelude模块的内容。)

Rust 标准库 std中的io模块是接受用户输入所必需的。因此,我们在程序的第一行添加了一个use语句。

2. 理解 Rust 中的 String 类型

在第 11 行,我创建了一个新的可变变量 user_input,正如它的名字所表示的那样,它将被用来存储用户输入。但是在同一行,你可能已经注意到了一些“新的”东西(哈哈,又来了!)。

LCTT 译注:“新的”在原文中为“new”,在第 11 行的代码中,原作者使用了 String::new函数,所以此处的梗与“使用”一样,原作者使用了一个在代码中用到的单词。

我没有使用双引号("")声明一个空字符串,而是使用String::new函数来创建一个新的空字符串。

""String::new的区别是你将在 Rust 系列的后续文章中学习到的。现在,只需要知道,使用String::new函数,你可以创建一个可变的,位于堆上的字符串。

如果我使用 ""创建了一个字符串,我将得到一个叫做“字符串切片”的东西。字符串切片的内容也位于堆上,但是字符串本身是不可变的。所以,即使变量本身是可变的,作为字符串存储的实际数据是不可变的,需要被覆盖而不是修改。

3. 接受用户输入

在第 12 行,我调用了 std::iostdin函数。如果我在程序的开头没有导入std::io模块,那么这一行将是std::io::stdin而不是io::stdin

sdtin函数返回一个终端的输入句柄。read_line函数抓住这个输入句柄,然后,正如它的名字所暗示的那样,读取一行输入。这个函数接受一个可变字符串的引用。所以,我传入了user_input变量,通过在它前面加上&mut,使它成为一个可变引用。

⚠️ read_line函数有一个怪癖。这个函数在用户按下回车键之后停止读取输入。因此,这个函数也会记录换行符(n),并将一个换行符存储在你传入的可变字符串变量的结尾处。

所以,请在处理它时要么考虑到这个换行符,要么将它删除。

Rust 中的错误处理入门

最后,在这个链的末尾有一个 expect函数。让我们稍微偏题一下,来理解为什么要调用这个函数。

read_line函数返回一个叫做Result的枚举。我会在后面的文章中讲解 Rust 中的枚举,但是现在只需要知道,枚举在 Rust 中是非常强大的。这个Result枚举返回一个值,告诉程序员在读取用户输入时是否发生了错误。

expect函数接受这个Result枚举,并检查结果是否正常。如果没有发生错误,什么都不会发生。但是如果发生了错误,我传入的消息(无法读取用户输入。)将会被打印到 STDERR,程序将会退出

📋 所有我简要提及的新概念将会在后续的新 Rust 系列文章中讲解。

现在我希望你应该已经理解了这些新概念,让我们添加更多的代码来增加程序的功能。

验证用户输入

我接受了用户的输入,但是我没有对其进行验证。在当前的上下文中,验证意味着用户输入了一些“命令”,我们希望能够处理这些命令。目前,这些命令有两个“类别”。

第一类用户可以输入的命令是用户希望购买的水果的名称。第二个命令表示用户想要退出程序。

我们的任务现在是确保用户输入不会偏离 可接受的命令

use std::io;fn main { println!("欢迎来到水果市场!"); println!("请选择要购买的水果。n"); println!("n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄"); println!("购买完成后,请输入“quit”或“q”。n"); // 获取用户输入 let mut user_input = String::new; io::stdin .read_line(&mut user_input) .expect("无法读取用户输入。"); // 验证用户输入 let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"]; user_input = user_input.trim.to_lowercase; let mut input_error = true; for input in valid_inputs { if input == user_input { input_error = false; break; } }}

要使验证更容易,我创建了一个叫做 valid_inputs的字符串切片数组(第 17 行)。这个数组包含了所有可以购买的水果的名称,以及字符串切片qquit,让用户可以传达他们是否希望退出。

用户可能不知道我们希望输入是什么样的。用户可能会输入“Apple”、“apple”或 “APPLE” 来表示他们想要购买苹果。我们的工作是正确处理这些输入。

在第 18 行,我通过调用 trim函数从user_input字符串中删除了尾部的换行符。为了处理上面提到的问题,我使用to_lowercase函数将所有字符转换为小写,这样 “Apple”、“apple” 和 “APPLE” 都会变成 “apple”。

现在,来看第 19 行,我创建了一个名为 input_error的可变布尔变量,初始值为true。稍后在第 20 行,我创建了一个for循环,它遍历了valid_inputs数组的所有元素(字符串切片),并将迭代的模式存储在input变量中。

在循环内部,我检查用户输入是否等于其中一个有效字符串,如果是,我将 input_error布尔值的值设置为false,并跳出for循环。

处理无效输入

现在是时候处理无效输入了。这可以通过将一些代码移动到无限循环中来完成,如果用户给出无效输入,则 继续该无限循环。

use std::io;fn main { println!("欢迎来到水果市场!"); println!("请选择要购买的水果。n"); let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"]; 'mart: loop { let mut user_input = String::new; println!("n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄"); println!("购买完成后,请输入“quit”或“q”。n"); // 读取用户输入 io::stdin .read_line(&mut user_input) .expect("无法读取用户输入。"); user_input = user_input.trim.to_lowercase; // 验证用户输入 let mut input_error = true; for input in valid_inputs { if input == user_input { input_error = false; break; } } // 处理无效输入 if input_error { println!("错误: 请输入有效的输入"); continue 'mart; } }}

这里,我将一些代码移动到了循环内部,并重新组织了一下代码,以便更好地处理循环的引入。在循环内部,第 31 行,如果用户输入了一个无效的字符串,我将 continuemart循环。

对用户输入做出反应

现在,所有其他的状况都已经处理好了,是时候写一些代码来让用户从水果市场购买水果了,当用户希望退出时,程序也会退出。

因为你也知道用户选择了哪种水果,所以让我们问一下他们打算购买多少,并告诉他们输入数量的格式。

use std::io;fn main { println!("欢迎来到水果市场!"); println!("请选择要购买的水果。n"); let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"]; 'mart: loop { let mut user_input = String::new; let mut quantity = String::new; println!("n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄"); println!("购买完成后,请输入“quit”或“q”。n"); // 读取用户输入 io::stdin .read_line(&mut user_input) .expect("无法读取用户输入。"); user_input = user_input.trim.to_lowercase; // 验证用户输入 let mut input_error = true; for input in valid_inputs { if input == user_input { input_error = false; break; } } // 处理无效输入 if input_error { println!("错误: 请输入有效的输入"); continue 'mart; } // 如果用户想要退出,就退出 if user_input == "q" || user_input == "quit" { break 'mart; } // 获取数量 println!( "n你选择购买的水果是 "{}"。请输入以千克为单位的数量。(1 千克 500 克的数量应该输入为 '1.5'。)", user_input ); io::stdin .read_line(&mut quantity) .expect("无法读取用户输入。"); }}

在第 11 行,我声明了另一个可变变量,它的值是一个空字符串,在第 48 行,我接受了用户的输入,但是这次是用户打算购买的水果的数量。

解析数量

我刚刚增加了一些代码,以已知的格式接受数量,但是这些数据被存储为字符串。我需要从中提取出浮点数。幸运的是,这可以通过 parse方法来完成。

就像 read_line方法一样,parse方法返回一个Result枚举。parse方法返回Result枚举的原因可以通过我们试图实现的内容来轻松理解。

我正在接受用户的字符串,并尝试将其转换为浮点数。浮点数有两个可能的值。一个是浮点数本身,另一个是小数。

字符串可以包含字母,但是浮点数不行。所以,如果用户输入的不是浮点数和小数,parse函数将会返回一个错误。

因此,这个错误也需要处理。我们将使用 expect函数来处理这个错误。

use std::io;fn main { println!("欢迎来到水果市场!"); println!("请选择要购买的水果。n"); let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"]; 'mart: loop { let mut user_input = String::new; let mut quantity = String::new; println!("n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄"); println!("购买完成后,请输入“quit”或“q”。n"); // 读取用户输入 io::stdin .read_line(&mut user_input) .expect("无法读取用户输入。"); user_input = user_input.trim.to_lowercase; // 验证用户输入 let mut input_error = true; for input in valid_inputs { if input == user_input { input_error = false; break; } } // 处理无效输入 if input_error { println!("错误: 请输入有效的输入"); continue 'mart; } // 如果用户想要退出,就退出 if user_input == "q" || user_input == "quit" { break 'mart; } // 获取数量 println!( "n你选择购买的水果是 "{}"。请输入以千克为单位的数量。(1 千克 500 克的数量应该输入为 '1.5'。)", user_input ); io::stdin .read_line(&mut quantity) .expect("无法读取用户输入。"); let quantity: f64 = quantity .trim .parse .expect("请输入有效的数量。"); }}

如你所见,我通过变量遮蔽将解析后的浮点数存储在变量 quantity中。为了告诉parse函数,我的意图是将字符串解析为f64,我手动将变量quantity的类型注释为f64

现在,parse函数将会解析字符串并返回一个f64或者一个错误,expect函数将会处理这个错误。

计算价格 + 最后的修饰

现在我们知道了用户想要购买的水果及其数量,现在是时候进行计算了,并让用户知道结果/总价了。

为了真实起见,我将为每种水果设置两个价格。第一个价格是零售价,我们在购买少量水果时向水果供应商支付的价格。水果的第二个价格是当有人批量购买水果时支付的批发价。

批发价将会在订单数量大于被认为是批发购买的最低订单数量时确定。这个最低订单数量对于每种水果都是不同的。每种水果的价格都是每千克多少卢比。

想好了逻辑,下面是最终的程序。

use std::io;const APPLE_RETAIL_PER_KG: f64 = 60.0;const APPLE_WHOLESALE_PER_KG: f64 = 45.0;const BANANA_RETAIL_PER_KG: f64 = 20.0;const BANANA_WHOLESALE_PER_KG: f64 = 15.0;const ORANGE_RETAIL_PER_KG: f64 = 100.0;const ORANGE_WHOLESALE_PER_KG: f64 = 80.0;const MANGO_RETAIL_PER_KG: f64 = 60.0;const MANGO_WHOLESALE_PER_KG: f64 = 55.0;const GRAPES_RETAIL_PER_KG: f64 = 120.0;const GRAPES_WHOLESALE_PER_KG: f64 = 100.0;fn main { println!("欢迎来到水果市场!"); println!("请选择要购买的水果。n"); let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"]; 'mart: loop { let mut user_input = String::new; let mut quantity = String::new; println!("n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄"); println!("购买完成后,请输入“quit”或“q”。n"); // 读取用户输入 io::stdin .read_line(&mut user_input) .expect("无法读取用户输入。"); user_input = user_input.trim.to_lowercase; // 验证用户输入 let mut input_error = true; for input in valid_inputs { if input == user_input { input_error = false; break; } } // 处理无效输入 if input_error { println!("错误: 请输入有效的输入"); continue 'mart; } // 如果用户想要退出,就退出 if user_input == "q" || user_input == "quit" { break 'mart; } // 获取数量 println!( "n你选择购买的水果是 "{}"。请输入以千克为单位的数量。(1 千克 500 克的数量应该输入为 '1.5'。)", user_input ); io::stdin .read_line(&mut quantity) .expect("无法读取用户输入。"); let quantity: f64 = quantity .trim .parse .expect("请输入有效的数量。"); total += calc_price(quantity, user_input); } println!("nn总价是 {} 卢比。", total);}fn calc_price(quantity: f64, fruit: String) -> f64 { if fruit == "apple" { price_apple(quantity) } else if fruit == "banana" { price_banana(quantity) } else if fruit == "orange" { price_orange(quantity) } else if fruit == "mango" { price_mango(quantity) } else { price_grapes(quantity) }}fn price_apple(quantity: f64) -> f64 { if quantity > 7.0 { quantity * APPLE_WHOLESALE_PER_KG } else { quantity * APPLE_RETAIL_PER_KG }}fn price_banana(quantity: f64) -> f64 { if quantity > 4.0 { quantity * BANANA_WHOLESALE_PER_KG } else { quantity * BANANA_RETAIL_PER_KG }}fn price_orange(quantity: f64) -> f64 { if quantity > 3.5 { quantity * ORANGE_WHOLESALE_PER_KG } else { quantity * ORANGE_RETAIL_PER_KG }}fn price_mango(quantity: f64) -> f64 { if quantity > 5.0 { quantity * MANGO_WHOLESALE_PER_KG } else { quantity * MANGO_RETAIL_PER_KG }}fn price_grapes(quantity: f64) -> f64 { if quantity > 2.0 { quantity * GRAPES_WHOLESALE_PER_KG } else { quantity * GRAPES_RETAIL_PER_KG }}

对比之前的版本,我做了一些改动……

水果的价格可能会波动,但是在我们程序的生命周期内,这些价格不会波动。所以我将每种水果的零售价和批发价存储在常量中。我将这些常量定义在 main函数之外(即全局常量),因为我不会在main函数内计算每种水果的价格。这些常量被声明为f64,因为它们将与quantity相乘,而quantityf64。记住,Rust 没有隐式类型转换 😉

当水果名称和用户想要购买的数量被存下来之后,calc_price函数被调用来计算用户指定数量的水果的价格。这个函数接受水果名称和数量作为参数,并将价格作为f64返回。

当你看到 calc_price函数的内部时,你会发现它是许多人所说的包装函数。它被称为包装函数,因为它调用其他函数来完成它的脏活。

因为每种水果都有不同的最低订单数量,才能被认为是批发购买,为了确保代码在未来可以轻松维护,每种水果都有单独的函数负责计算价格。

所以,calc_price函数所做的就是确定用户选择了哪种水果,并调用相应的函数来计算所选水果的价格。这些水果特定的函数只接受一个参数:数量。这些水果特定的函数将价格作为f64返回。

现在,price_*函数只做一件事。它们检查订单数量是否大于被认为是批发购买的最低订单数量。如果是这样,quantity将会乘以水果的每千克批发价格。否则,quantity将会乘以水果的每千克零售价格。

由于乘法行末尾没有分号,所以函数返回乘积。

如果你仔细看看 calc_price函数中水果特定函数的函数调用,这些函数调用在末尾没有分号。这意味着,price_*函数返回的值将会被calc_price函数返回给它的调用者。

而且 calc_price函数只有一个调用者。这个调用者在mart循环的末尾,这个调用者使用这个函数返回的值来增加total的值。

最终,当 mart循环结束(当用户输入qquit时),存储在变量total中的值将会被打印到屏幕上,并且用户将会被告知他/她需要支付的价格。

总结

这篇文章中,我使用了之前讲解的 Rust 编程语言的所有主题来创建一个简单的程序,这个程序仍然在某种程度上展示了一个现实世界的问题。

现在,我写的代码肯定可以用一种更符合编程习惯的方式来写,这种方式最好地使用了 Rust 的喜爱特性,但是我还没有讲到它们!

Rust 基础系列到此结束。欢迎你的反馈。

(题图:MJ/6d486f23-e6fe-4bef-a28d-df067ef2ec06)

via: ***/milestone-rust-program/

本文由 LCTT原创编译,Linux中国荣誉推出

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:dandanxi6@qq.com

(0)
上一篇 2023年 8月 1日 下午12:12
下一篇 2023年 8月 1日 下午12:22

相关推荐

  • 新式骗局泛滥,己有1000多人被骗,总金额达2.6亿

    近期“AI换脸”新型诈骗频发 一起利用人工智能(AI) 实施电信诈骗的典型案例冲上热搜 和你视频对话的 可能不是真人! “好友”突然打来视频 10分钟被骗走430万元 近日,“平安…

    2023年 5月 27日
  • 怎么通过支付宝给公交卡充值

    你是怎样给公交卡充值的?还是跑到公交卡指定的充值地点去充值吗?比如:上海公交卡充值,需要到地铁站或指定的银行。如果真是这样,那你就真的有点落伍了。现在,我们的国家早就进入到了移动支…

    2023年 6月 3日
  • 老年人如何备份手机照片

    如今,越来越多的老年人喜欢用手机拍照、录像,记录美好瞬间,不过有人担心手机空间毕竟有限,一旦被存满了该怎么办?而且,万一手机丢了或者坏了,所有资料也丢了,有什么办法可以将手机中的照…

    2023年 1月 25日
  • 钟点工做家务的步骤有哪些

    虽然看似做家务,这是一件比较简单的事情,然而这一件事情说起来特别的容易,但是真正落实的话,确实特别困难的。很多人在成家之前根本就体会不到做家务的困难,总觉得结婚之后一定要把自己的家…

    2023年 2月 19日
  • 把银行卡买给别人刷了几百万流水,会被判刑吗?

    银行卡可不能随便提供给他人, 一男子提供自己的银行卡给他人“刷流水”,非法获利4100元,之后被公安机关抓获。近日,广东省丰顺县人民法院对该案做出一审判决,认定被告人徐某林构成掩饰…

    互联网 2022年 12月 18日
  • word文档如何添加页码有公式

    小编上一篇发文中分别介绍了word内容格式、表格制作、调整行距和excel中换行、筛选的简单操作。接下来我将介绍下办公软件中特殊符号、部分公式、页码的快速插入,同样大神请绕过。 特…

    2023年 1月 13日
  • 微信小程序开发过程中,目前要求应用文件的大小上限为

    微信小程序是使用 Javascript 和微信开发者工具开发的移动应用。 1、我想开发个小程序,能够做小区信息对接的,比如说家政对接? 开发小区信息对接的微信小程序是可行的。具体的…

    互联网 2023年 9月 3日
  • 比特币是怎么挖出来的,看完秒懂

    据欧易OKEx数据显示,2021年以来,比特币价格已经上涨了一倍多,比12月31日的收盘价28987.60美元上涨了107%。比特币如此受欢迎,如何获取比特币呢?挖矿是获取比特币的…

    2023年 9月 2日
  • 共享充电宝为什么价格越来越高(为什么现在共享充电宝这么贵)

    共享充电宝因其“价格越抬越高、充电慢”受到广泛争议。 7月以来,“共享充电宝每小时4元起步贵吗”、“共享充电宝电用完只充了30%”多次冲上微博热搜,再次将共享充电宝的收费问题推向风…

    2023年 8月 16日
  • 怎么在qq网名上加横线(怎样才能在qq网名中间加横线)

    我们会经常在一些地方看到有人的网名上有一条横线,最多的莫过于在QQ昵称上了。那么,这种在网名上加横线的昵称是怎么写的呢?下面以QQ昵称为例给大家讲解怎么在网名上加横线。 方法/步骤…

    2023年 3月 6日