分类 软件开发 下的文章

在本系列的第四章,学习在 Bash 中使用基本数学运算。

你可以使用 Bash 脚本做很多事情。对变量执行简单的算术运算就是其中之一。

Bash shell 中算术运算的语法如下:

$((arithmetic_operation))

假设你必须计算两个变量的总和。你这样做:

sum=$(($num1 + $num2))

(()) 内空格的使用没有限制。你可以使用 $(( $num1+ $num2))$(( $num1+ $num2 )) 或者 $(( $num1+ $num2 ))。它们都一样。

在通过示例详细讨论之前,我先分享一下它支持的算术运算符。

Bash 中的基本算术运算符

以下是 Bash shell 中算术运算符的列表。

运算符描述
+加法
-减法
*乘法
/整数除法(不带小数)
%模除法(仅余数)
**求幂(a 的 b 次方)
? Bash 不支持浮点数(小数)。你必须使用其他命令(例如 bc)来处理它们。

Bash 中的加法和减法

让我们通过编写一个脚本来看看它,该脚本从用户那里获取两个数字,然后打印它们的总和和减法。

#!/bin/bash

read -p "Enter first number: " num1
read -p "Enter second number: " num2

sum=$(($num1+$num2))
sub=$(($num1-$num2))
echo "The summation of $num1 and $num2 is $sum"
echo "The substraction of $num2 from $num1 is $sub"

我相信你熟悉上一章中使用 read 命令来 在 Bash 中接受用户输入

你应该关注这两行:

sum=$(($num1+$num2))
sub=$(($num1-$num2))

将此脚本保存为 sum.sh 并运行它。给它一些输入并检查结果。

Example of addition and subtraction in Bash shell script

Bash 中的乘法

现在让我们转向乘法。

这是一个将公里转换为米的示例脚本(这给美国读者带来了麻烦 ?)。作为参考,1 公里等于 1000 米。

#!/bin/bash

read -p "Enter distance in kilometers: " km
meters=$(($km*1000))

echo "$km KM equals to $meters meters"

将脚本保存为 multi.sh,赋予其执行权限并运行它。这是一个示例输出:

Multiplication in bash script

看起来不错,不是吗? 让我们继续进行除法。

Bash 脚本中的除法

让我们用一个非常简单的脚本来看看除法:

#!/bin/bash

num1=50
num2=5

result=$(($num1/$num2))

echo "The result is $result"

你很容易猜到结果:

The result is 10

没关系。但是让我们更改数字并尝试将 50 除以 6。结果如下:

The result is 8

但这不正确。 正确答案应该是 8.33333。

这是因为 Bash 默认情况下只处理整数。你需要额外的命令行工具来处理浮点(小数)。

最流行的工具是 bc,它是一种处理数学运算的非常强大的计算器语言。不过,你现在不需要关注细节。

你必须通过管道将算术运算“回显”给 bc

echo "$num1/$num2" | bc -l

于是,将之前的脚本修改为:

#!/bin/bash

num1=50
num2=6

result=$(echo "$num1/$num2" | bc -l)

echo "The result is $result"

现在你得到结果:

The result is 8.33333333333333333333

请注意 result=$(echo "$num1/$num2" | bc -l),它现在使用你在 本系列第 2 章 中看到的命令替换。

-l 选项加载标准数学库。默认情况下,bc 最多保留 20 位小数。你可以通过以下方式将比例更改为较小的位数:

result=$(echo "scale=3; $num1/$num2" | bc -l)

让我们看看 Bash 中浮点的更多示例。

在 Bash 脚本中处理浮点

让我们修改 sum.sh 脚本来处理浮点。

#!/bin/bash

read -p "Enter first number: " num1
read -p "Enter second number: " num2

sum=$( echo "$num1+$num2" | bc -l)
sub=$( echo "scale=2; $num1-$num2" | bc -l)
echo "The summation of $num1 and $num2 is $sum"
echo "The substraction of $num2 from $num1 is $sub"

现在尝试运行它,看看是否可以正确处理浮点:

Floating points in bash script

?️? 练习时间

是时候一起做一些数学和 Bash 练习了。

练习 1:创建一个脚本,接受以 GB 为单位的输入并以 MB 和 KB 为单位输出其等效值。

练习 2:编写一个带有两个参数并以指数格式输出结果的脚本。因此,如果输入 2 和 3,输出将为 8,即 2 的 3 次方。

提示:使用幂运算符 **

练习 3:编写一个将摄氏度转换为华氏度的脚本。

提示:使用公式 F = C x (9/5) + 32。你必须在此处使用 bc 命令。

你可以在社区中讨论练习及其方案。

在下一章中,你将 了解 Bash 中的数组。敬请关注。

(题图:MJ/8a9dfb90-99a4-4203-bc44-d805d09bc16f)


via: https://itsfoss.com/bash-arithmetic-operation/

作者:Abhishek Prakash 选题:lkxed 译者:geekpi 校对:wxy

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

在 Bash 基础系列的这一章中,学习如何向 Bash 脚本传递参数并使它们具有交互性。

来让 Bash 脚本有参数吧 ?

你可以通过向 Bash 脚本传递变量来使其更加有用和更具交互性。

让我通过示例详细向你展示这一点。

将参数传递给 Shell 脚本

当你运行 Shell 脚本时,你可以按以下方式向其中添加其他变量:

./my_script.sh var1 var2

在脚本内部,你可以使用 $1 作为第一个参数,$2 作为第二个参数,依此类推。

? $0 是一个特殊变量,保存正在执行的脚本的名称。

让我们通过一个实际的例子来看看。切换到保存练习 Bash 脚本的目录。

mkdir -p bash_scripts && cd bash_scripts

现在,创建一个名为 arguments.sh (我想不出更好的名称)的新 Shell 脚本,并向其中添加以下行:

#!/bin/bash

echo "Script name is: $0"
echo "First argument is: $1"
echo "Second argument is: $2"

保存文件并使其可执行。现在像往常一样运行脚本,但这次向其中添加任意两个字符串。你将看到屏幕上打印的详细信息。

? 参数由空格(空格、制表符)分隔。如果参数中有空格,请使用(英文)双引号将其引起来,否则它将被视为单独的参数。

Pass arguments to the bash scripting

? Bash 脚本最多支持 255 个参数。但对于参数 10 及以上,你必须使用花括号 ${10}${11}...${n}

正如你所看到的,$0 代表脚本名称,而其余参数存储在编号变量中。你还可以在脚本中使用一些其他特殊变量。

特殊变量变量描述
$0脚本名称
$1$2、……$9脚本参数
${n}脚本参数从 10 到 255
$#参数数量
$@所有参数
$$当前 Shell 的进程 ID
$!最后执行的命令的进程 ID
$?最后执行命令的退出状态
?️‍♀️ 修改上面的脚本以显示参数数量。

如果参数数量不匹配怎么办?

在上面的示例中,你为 Bash 脚本提供了两个参数并在脚本中使用了它们。

但是,如果你只提供一个参数或三个参数怎么办?

让我们实际做一下吧。

Passing fewer or more arguments to bash script

正如你在上面所看到的,当你提供的参数超出预期时,结果仍然是一样的。不使用其他参数,因此不会产生问题。

但是,当你提供的参数少于预期时,脚本将显示空白。如果脚本的一部分依赖于缺少的参数,这可能会出现问题。

接受用户输入并制作交互式 Bash 脚本

你还可以创建提示用户通过键盘提供输入的 Bash 脚本。这使你的脚本具有交互性。

read 命令提供了此功能。你可以这样使用它:

echo "Enter something"
read var

上面的 echo 命令不是必需的,但最终用户不会知道他们必须提供输入。然后用户在按回车键之前输入的所有内容都存储在 var 变量中。

你还可以显示提示消息并在单行中获取值,如下所示:

read -p "Enter something? " var

让我们看看它的实际效果。创建一个新的 interactive.sh Shell 脚本,内容如下:

#!/bin/bash

echo "What is your name, stranger?"
read name
read -p "What's your full name, $name? " full_name
echo "Welcome, $full_name"

在上面的示例中,我使用 name 变量来获取名称。然后我在提示中使用 name 变量,并在 full_name 变量中获取用户输入。我使用了两种使用 read 命令的方法。

现在,如果你授予执行权限,然后运行此脚本,你会注意到该脚本显示 What is your name, stranger?,然后等待你从键盘输入内容。你提供输入,然后它会显示 What's your full name 消息,并再次等待输入。

以下是供你参考的示例输出:

Interactive bash shell script

?️ 练习时间

是时候练习你所学到的东西了。尝试为以下场景编写简单的 Bash 脚本。

练习 1:编写一个带有三个参数的脚本。你必须使脚本以相反的顺序显示参数。

预期输出:

abhishek@itsfoss:~/bash_scripts$ ./reverse.sh ubuntu fedora arch
Arguments in reverse order:
arch fedora ubuntu

练习 2:编写一个脚本,显示传递给它的参数数量。

提示:使用特殊变量 $#

预期输出:

abhishek@itsfoss:~/bash_scripts$ ./arguments.sh one and two and three
Total number of arguments: 5

练习 3:编写一个脚本,将文件名作为参数并显示其行号。

提示:使用 wc 命令来计算行号。

你可以在社区中讨论你的解决方案。

很好! 现在你可以(传递)参数了 ? 在下一章中,你将学习在 Bash 中执行基本数学运算。

(题图:MJ/5a75aa2f-1cb1-4009-a4e6-683cf61bc892)


via: https://itsfoss.com/bash-pass-arguments/

作者:Abhishek Prakash 选题:lkxed 译者:geekpi 校对:wxy

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

采用 Apache APISIX 的 API 主导架构。

API 网关是一个单一节点,提供对 API 调用入口。网关聚合了所请求的服务,并相应传回合适的响应信息。为了令你的 API 网关有效地工作,设计一个可靠、高效且简洁地 API 至关重要。本文介绍一种设计风格,但只要你理解其中的重点内容,它就能解决你的相关问题。

由 API 主导的方法

API 主导的方法是将 API 置于应用程序和它们需要访问的业务能力之间的通信核心,从而在所有数字通道上一致地交付无缝功能。API 主导的连接是指使用一种可重用、且设计得当的 API 来连接数据和应用程序的方法。

API 主导的架构

API 主导的架构是一种架构方法,它着眼于实现重用 API 的最佳方式。它能解决以下问题:

  • 保护 API,使外界无法在未授权情况下访问 API
  • 确保应用程序能找到正确的 API 端点
  • 限制对 API 的请求次数,从而确保持续的可用性
  • 支持持续集成、测试、生命周期管理、监控、运维等等
  • 防止错误在栈间传播
  • 对 API 的实时监测和分析
  • 实现可伸缩和灵活的业务能力(例如支持 微服务 架构)

API 资源路由

实现一个 API 网关,把它作为与所有服务通信的单一入口点,意味着使用者只需要知道 URL 就能使用 API。将请求路由到相应的服务端点,并执行相应的功能是 API 网关的职责。

Image depicting the API routing traffic.

由于客户端应用程序不需要从多个 HTTP 端点调用功能,这个办法就减少了 API 使用者的操作复杂度。对每个服务来说,也不需实现一个单独的层级去实现认证、授权、节流和速度限制。大多数API 网关,如开源的 Apache APISIX,已经包含了这些核心功能。

API 基于内容的路由

基于内容的路由机制也使用 API 网关根据请求的内容进行路由调用。例如,一个请求可能是基于 HTTP 请求的头部内容或消息体被路由,而不只基于它的目标 URI。

考虑这样一个场景:为了将负载在多个数据库实例间均分,需要对数据库进行分区。当记录总数较大,单个数据库实例难以管理负载时,常常会用这个办法。

还有一个更好的办法,就是把记录在多个数据库实例间分散开来。然后你实现多个服务,每个不同的数据库都有一个服务,把一个 API 网关作为访问所有服务的唯一入口。然后,你可以配置你的 API 网关,根据从 HTTP 头或有效载荷中获得的密钥,将调用路由到相应的服务。

Image of the API gateway exposing a single customer.

在上面的图表中,一个 API 网关向多个客户服务暴露一个单一的 /customers 资源,每个服务都有对应的不同数据库。

API 地理路由

API 地理路由解决方案根据 API 调用的来源将其路由到最近的 API 网关。为了防止地理距离导致的延迟问题(例如一个位于亚洲的客户端调用了位于北美地区的 API),你可以在多个地区部署 API 网关。对于一个 API 网关,你可以在每个区域使用不同的子域名,让应用程序基于业务逻辑选择最近的网关。因此 API 网关就提供了内部负载均衡,确保进入的请求分布于可用的实例之间。

Image of a DNS traffic management system.

通常使用 DNS 流量管理服务和 API 网关,针对该区域的负载均衡器解析子域名,定位到距离最近的网关。

API 聚合器

这项技术对多个服务执行操作(例如查询),并向客户端服务以单个 HTTP 响应的形式返回结果。API 聚合器使用 API 网关在服务器端代表使用者来执行这项工作,而非让客户端程序多次调用 API。

假定你有一款移动端 APP,对不同的 API 发起多次调用。这增加了客户端代码的复杂性,导致网络资源的过度使用,而且由于延迟性,用户体验也不好。网关可以接收所有需要的信息,可以要求认证和验证,并理解来自每个 API 的数据结构。它也可以传递响应的有效载荷,因此它们也会作为一个用户需要的统一负载传回移动端。

Image of an API gateway.

API 集中认证

在这种设计中,API 网关就是一个集中式认证网关。作为一个认证者,API 网关在 HTTP 请求头中查找访问凭据(例如不记名的令牌)。然后它借助于身份验证提供方执行验证凭据的业务逻辑。

Image of a tree showing API gateway's centralized authentication.

使用 API 网关的集中式身份验证能解决很多问题。它完全取代了应用程序中的用户管理模块,通过对来自客户端应用程序的身份验证请求的快速响应来提高性能。Apache APISIX 提供了一系列插件,支持不同的 API 网关认证方法。

Image showing Apache ASPISIS and various plugins.

API 格式转换

API 格式转换是通过同一传输方式将有效载荷从一种格式转换为另一种格式的能力。例如,你可以通过 HTTPS 从 XML/SOAP 格式转换为 JSON 格式,反之亦然。API 网关提供了支持 REST API 的功能,可以有效地进行负载和传输的转换。例如,它可以把消息队列遥测传输(MQTT)转换为 JSON 格式。

Image depicting APISIX transfers.

Apache APISIX 能够接收 HTTP 请求,对其进行代码转换,然后将其转发给 gRPC 服务。它通过 gRPC Transcode 插件获取响应并将其以 HTTP 格式返回给客户端。

API 的可观察性

现在,你知道 API 网关为进入各种目的地的流量提供了一个中心控制点。但它也可以是一个中心观察点,因为就监控客户端和服务器端的流量来说,它有独特的资格。为了收集监测工具所需要的数据(结构化日志、度量和跟踪),你可以对 API 网关作出调整。

Apache APISIX 提供了 预先构建的连接器,因此你可以跟外部监测工具结合使用。你可以利用这些连接器从你的 API 网关收集日志数据,进一步获得有用的指标,并获取完整可见的服务使用情况。

API 缓存

API 缓存通常在网关内部实现。它可以减少对端点的调用次数,同时通过缓存上游的响应,改进了请求延迟的情况。如果网关缓存对请求资源有一个新副本,它会直接使用这个副本来响应这个请求,而不必对端点发出请求。如果缓存数据不存在,就将请求传到目标上游服务。

Image depicting how the API gateway cache functions.

API 错误处理

由于各种原因,API 服务可能会出错。在这种情况下,API 服务需要有一定的弹性,来应对可预见的错误。你也希望确保弹性机制能正常工作。弹性机制包括错误处理代码、断路器、健康检查、回退、冗余等等。新式的 API 网关支持各种常见错误处理功能,包括自动重试和超时设置。

Image depicting some of the many mechanisms that the modern API Gatway can support.

API 网关作为一个协调器,它会根据各方面情况来决定如何管理流量、将负载均衡发送到一个健康的节点,还能快速失败。当有异常状况,它也会向你发出警示。API 网关也保证路由和其他网络级组件能协同将请求传给 API 进程。它能帮助你在早期检测出问题,并修复问题。网关的错误注入机制(类似于 Apache APISIX 使用的那种)可用于测试应用程序或微服务 API 在各种错误发生时的弹性。

API 版本管理

版本管理是指定义和运行多个并发的 API 版本的功能。这点也很重要,因为 API 是随着时间推移不断改进的。如果能对 API 的并发版本进行管理,那么 API 使用者就可以较快地切换到新的版本。这也意味着较老的版本可以被废弃并最终退役。API 也跟其他应用程序类似,无论是开发新功能还是进行错误修复,都存在演变的过程。

Image of using the API Gateway to implement API versioning.

你可以使用 API 网关来实现 API 版本管理。版本管理可以是请求头,查询参数或路径。

APISIX 的网关

如果你需要令 API 服务可伸缩,就需要使用 API 网关。Apache APISIX 提供了必要的功能,可以实现健壮的入口,它的好处是显而易见的。它遵循 API 主导的架构,并且有可能改变客户端与托管服务交互的方式。

本文经作者许可,从 Apache APISIX 博客改编并转载。

(题图:MJ/f1d05345-48f5-4e3e-9c65-a2ba9105614a)


via: https://opensource.com/article/23/1/api-gateway-apache-apisix

作者:Bobur Umurzokov 选题:lkxed 译者:cool-summer-021 校对:wxy

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

到目前为止,我们已经讲解了包括 变量、可变性、常量数据类型函数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 行,如果用户输入了一个无效的字符串,我将 continue mart 循环。

对用户输入做出反应

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

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

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!("\n\n总价是 {} 卢比。", 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 带入下一个层次 系列,并学习更多 Rust 编程语言的内容!

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

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


via: https://itsfoss.com/milestone-rust-program/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

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

在 Rust 系列的 上一篇文章 中,我介绍了如何使用 ifelse 关键字来处理 Rust 程序的控制流。

这是处理程序控制流的一种方法。另一种方法是使用循环。因此,让我们在本文中看看循环。

Rust 中可用的循环

Rust 编程语言有三种不同的循环,基于你想要实现什么以及可用的内容:

  • for
  • while
  • loop

我假设你对 forwhile 已经很熟悉了,但 loop 对你来说可能是个新概念。让我们先从熟悉的概念开始。

for 循环

for 循环主要用于迭代一种称为迭代器的东西。

这个迭代器可以从任何东西中创建,从数组、向量(很快就会介绍!)、一系列值,或者任何自定义的东西。这里的可能性是无限的。

来看看 for 循环的语法。

for 迭代变量 in 迭代器 {
    <语句>;
}

其中的 迭代变量 在大多数其他编程语言教程中通常被称为 i ; )

迭代器 可以是任何东西,只要它能告诉下一个值是什么,如果有的话。

来通过一个程序来理解这个。

fn main() {
    let my_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    println!("迭代数组");
    for element in my_arr {
        println!("{}", element);
    }

    println!("\n迭代一个真正的迭代器");
    for element in my_arr.iter() {
        println!("{}", element);
    }

    println!("\nPython 风格的范围");
    for element in 0..10 {
        println!("{}", element);
    }
}

这里,我声明了一个数组,它包含从 0 到 9 的 10 个数字。在第 5 行的 for 循环中,我只是将这个数组指定为迭代器,Rust 会自动处理对这个数组的所有元素的迭代。不需要花哨的 my_arr[i] 魔法。

但是,在第 10 行,我调用了 .iter() 函数。这是一个明确的提及,它基于 my_arr 的值来获取一个迭代器。这个循环和第 5 行的循环之间唯一的区别是,这里你是通过在数组上调用 .iter() 函数来明确地调用它的。

在这个上下文环境中,在一个数据类型上调用 .iter() 函数不是必须的。因为这是一个数组,是语言本身提供的一种数据类型,Rust 已经知道如何处理它了。但是你 需要 在自定义数据类型中使用它。

最后,在第 15 行,我们有一个循环,它循环遍历一个范围。嗯,差不多是这样。如果你仔细看,这个范围看起来很像切片 “类型”。Rust 也知道这一点,并且 你处理了迭代(哈哈,明白了吗?)。

LCTT 译注:此处的梗是,“为你处理了迭代” 的英文原文是 “handles iteration for you",其中的 “for” 与 “for 循环” 的 “for” 是同一个单词。

输出如下:

迭代数组
0
1
2
3
4
5
6
7
8
9

迭代一个真正的迭代器
0
1
2
3
4
5
6
7
8
9

Python 风格的范围
0
1
2
3
4
5
6
7
8
9

while 循环

while 循环可以被认为是非常类似于 if 条件语句。使用 if 语句,只要用户提供的条件为 trueif 语句体中的代码就会被执行 一次

但是,在 while 循环中,如果条件评估为 true,循环就会开始循环循环体。只要条件继续评估为 true,循环就会继续迭代。

while 循环只有在循环完成当前迭代中所有语句的执行并且在检查条件时,它的结果为 false 时才会停止。

来看看 while 循环的语法...

while 条件 {
    <语句>;
}

看到了吗?和 if 条件语句非常相似!不过没有 else 块 ; )

来看一个程序来更好地理解这个。

fn main() {
    let mut var = 0;

    while var < 3 {
        println!("{var}");
        var += 1;
    }
}

我有一个可变变量 var,它的初始值为 0。只要可变变量 var 中存储的值小于 3,while 循环就会执行。

在循环中,var 的值被打印出来,然后它的值被增加 1。

这是上面代码的输出:

0
1
2

loop 循环

Rust 有一个无限循环。是的,一个没有开始条件和停止条件的循环。它只是一直循环,直到永远。当然,它有触发器来停止代码本身的循环执行。

无限循环的语法如下:

loop {
    <语句>;
}
? 这些循环主要用于 GUI 软件,退出是一个 显式 操作。

在我给你一个例子之前,因为这个循环非常特殊,让我们先看看如何 退出 它 :p

要停止无限循环的执行,需要在循环内使用 break 关键字。

来看一个例子,只有 0 到 3 之间的整数(包括 0 和 3)才会被打印到程序输出。

fn main() {
    let mut var = 0;

    loop {
        if var > 3 {
            break;
        }

        println!("{}", var);
        var += 1;
    }
}

看待这个特定的例子的最好方法是将它看作是一个增加了一堆没有必要的东西的 while 循环 ; )

你有一个可变变量 var,它的初始值为 0,它被用作迭代器。无限循环从一个 if 条件开始,如果 var 的值大于 3,break 关键字就会被执行。后来,就像 while 循环的前一个例子一样,var 的值被打印到标准输出,然后它的值被增加 1。

它的输出如下:

0
1
2
3

标记循环

假设有两个无限循环,一个嵌套在另一个中。由于某种原因,退出条件在最内层循环中被检查,但这个退出条件是为了退出最外层循环。

在这种情况下,标记循环可能是有益的。

? breakcontinue 关键字并不仅仅用于无限循环。它们可以用于 Rust 语言提供的所有三种循环。

接下来是如何标记循环。

'标记: loop {}

要告诉编译器一个循环被标记了,从一个单引号字符开始,输入它的标签,然后跟着一个冒号。然后,继续使用你通常定义循环的方式。

当你需要退出某个循环时,只需像这样指定循环标签:

break '标记;

来看一个例子来更好地理解这个。

fn main() {
    let mut a = 0;
    let mut b = 0;

    'parent: loop {
        a += 1;

        loop {
            println!("a: {}, b: {}", a, b);
            b += 1;

            if a + b == 10 {
                println!("\n{} + {} = 10", a, b);
                break 'parent;
            }
        }
    }
}

这里,我使用两个可变变量 ab,它们的初始值都设置为 0。

然后,最外层的循环被标记为 parentparent 循环将变量 a 的值增加 1,并有一个内部/子循环。

这个(在第 8 行的)子循环打印变量 ab 的值。在这个循环内部,变量 b 的值增加了 1。退出条件是 a + b == 10。这意味着只要变量 ab 中存储的值相加,结果为 10,parent 循环就会被打破。即使第 14 行的 break 条件“属于”内部循环,它也会打破 parent 循环。

来看看程序的输出。

a: 1, b: 0
a: 1, b: 1
a: 1, b: 2
a: 1, b: 3
a: 1, b: 4
a: 1, b: 5
a: 1, b: 6
a: 1, b: 7
a: 1, b: 8

1 + 9 = 10

就像从程序输出中可以看出的那样,循环在 ab 分别具有值 1 和 9 时停止。

continue 关键字

如果你已经在其他编程语言(如 C/C++/Java/Python)中使用过循环,你可能已经知道 continue 关键字的用法。

break 关键字用于完全停止循环执行时,continue 关键字用于“跳过”循环执行的 当前迭代 并从下一迭代开始(如果条件允许)。

来看一个例子来理解 continue 关键字的工作原理。

fn main() {
    for i in 0..10 {
        if i % 2 == 0 {
            continue;
        }
        println!("{}", i)
    }
}

在上面的代码中,我有一个 for 循环,它迭代了 0 到 9 之间的整数(包括 0 和 9)。一旦循环开始,我就设置了一个条件检查,看看这个数字是不是偶数。如果这个数字是偶数,continue 关键字就会被执行。

但是如果这个数字是奇数,这个数字就会被打印到程序输出。

来看看这个程序的输出。

1
3
5
7
9

正如你所看到的,循环似乎一直在“进行”,尽管 0 到 9 之间显然有偶数。但是因为我使用了 continue 关键字,当遇到这个关键字时,循环执行就会停止。

这个循环跳过了它下面的任何东西,并继续下一次迭代。这就是为什么偶数没有被打印出来,但是 0 到 9 之间的所有奇数都被打印到了程序输出中。

总结

要总结这篇长文,我演示了 3 种不同循环的用法:forwhileloop。我还讨论了两个关键字,它们影响这些循环的控制流:breakcontinue

我希望你现在能理解每个循环的适当用例。如果你有任何问题,请告诉我。

(题图:MJ/25579e09-ae1c-47d3-8266-3bd9a54456c0)


via: https://itsfoss.com/rust-loops/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

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

如果你和我一样,在 20 世纪七八十年代使用计算机长大,你可能学过一种常见的个人计算机编程语言,名为 BASIC(全称是 “ 初学者的通用符号指令代码 Beginner's All-purpose Symbolic Instruction Code ”)。那个时期,包括 TRS-80、Apple II 和 IBM PC 在内的每台个人计算机都可以找到 BASIC 实现。当时,我是一个自学的 BASIC 程序员,在尝试了 Apple II 上的 AppleSoft BASIC 后,转向 IBM PC 上的 GW-BASIC,后来在 DOS 上学习了 QuickBASIC。

我通过编写一个示例程序来探索 BASIC 和 FORTRAN 77 中的 FOR 循环,以将数字列表从 1 加到 10。

但是曾经,一种在科学编程领域受欢迎的语言是 FORTRAN(即 “ 公式翻译 FORmula TRANslation ”)。尽管在 1990 年对该语言进行的规范以后,该名称更常见的风格是 “Fortran”。

当我在 1990 年代初作为大学本科物理学生学习物理学时,我利用自己在 BASIC 上的经验学习了 FORTRAN 77。那时我意识到 BASIC 许多概念都来源于 FORTRAN。当然,FORTRAN 和 BASIC 在很多其他方面也存在差异,但我发现了解一点 BASIC 可以帮助我快速学习 FORTRAN 编程。

我想通过使用两种语言编写相同的程序,展示它们之间的一些相似之处。通过编写一个示例程序来探索 BASIC 和 FORTRAN 77 中的 FOR 循环,这个程序将把 1 到 10 之间的数字相加。

Bywater BASIC

BASIC 存在许多种不同的版本,这取决于你的计算机,但该语言总体保持不变。我喜欢的一种 BASIC 版本是 Bywater BASIC,这是一种开源的 BASIC 实现,适用于包括 Linux 和 DOS 在内的不同平台。

要在 FreeDOS 上使用 Bywater BASIC,你必须首先从 FreeDOS 1.3 Bonus CD 中 安装该软件包。然后进入 C: 目录并输入 bwbasic 命令,这将启动 BASIC 解释器。你可以在这个提示符下输入程序:

bwBASIC:

Bywater BASIC 使用较早的 BASIC 编程标准,需要你在每个程序指令上编写一个行号。将行号视为索引。你可以使用行号轻松地引用程序中的任何指令。当你将程序键入 Bywater BASIC 解释器时,请在每个指令前添加行号:

bwBASIC: 10 print "Add the numbers from 1 to 10 ..."
bwBASIC: 20 sum = 0
bwBASIC: 30 for i = 1 to 10
bwBASIC: 40 sum = sum + i
bwBASIC: 50 next i
bwBASIC: 60 print sum
bwBASIC: 70 end

可以使用 list 命令查看你已经输入到解释器中的程序:

bwBASIC: list
10 print "Add the numbers from 1 to 10 ..."
20 sum = 0
30 for i = 1 to 10
40 sum = sum + i
50 next i
60 print sum
70 end

这个简短的程序演示了 BASIC 中的 FOR 循环。 FOR 是任何编程语言中最基本的循环构造,允许你迭代一组值。在 Bywater BASIC 中,FOR 循环的一般语法看起来像这样:

FOR 变量 = 起始值 TO 终止值

在这个示例程序中,指令 for i = 1 to 10 开始一个循环,迭代值为 1 到 10。在每个循环中,变量 i 被设置为新值。

在 BASIC 中,所有到 next 指令前的指令都将作为 FOR 循环的一部分执行。因为你可以将一个 FOR 循环放入另一个 FOR 循环中,Bywater BASIC 使用语法 NEXT 变量 来指定要迭代的循环变量。

在提示符下键入 run 来执行程序:

bwBASIC: run
Add the numbers from 1 to 10 ...
55

Bywater BASIC 被称为 BASIC 解释器,因为只能从 Bywater BASIC 环境中运行程序。这意味着解释器会处理与操作系统的交互的所有繁重工作,因此你的程序不需要自己完成这个工作。 这样做的代价是,程序在解释环境中运行会比它作为编译程序运行慢一些。

FreeBASIC

另一个流行的 BASIC 实现是 FreeBASIC,这是一个开源的 BASIC 编译器,适用于多个平台,包括 Linux 和 DOS。要使用 FreeBASIC,你需要从 FreeDOS 1.3 Bonus CD 安装 FreeBASIC 包,然后进入 C: 目录,你会在这里找到 FreeBASIC 程序。

FreeBASIC 是一个编译器,因此你首先需要创建一个包含程序指令的源文件,然后使用源代码运行编译器以创建一个可运行的程序。我编写了一个类似于“将 1 到 10 的数字相加”的程序版本,将其保存为 BASIC 文件,并命名为 sum.bas

dim sum as integer
dim i as integer
print "Add the numbers from 1 to 10 ..."
sum = 0
for i = 1 to 10
sum = sum + i
next
print sum
end

如果你将这段代码与 Bywater BASIC 版本的程序进行比较,你可能会注意到 FreeBASIC 不需要行号。FreeBASIC 实现了一种更现代的 BASIC 版本,使得编写程序时不需要跟踪行号更容易。

另一个主要的区别是你必须在源代码中定义或声明变量。使用 DIM 指令在 FreeBASIC 中声明变量,例如 dim sum as integer,以定义一个名为 sum 的整数变量。

现在可以在命令行上使用 fbc 编译 BASIC 程序:

C:\DEVEL\FBC> fbc sum.bas

如果你的代码没有任何错误,编译器将生成一个可以运行的程序。例如,我的程序现在称为 sum。运行我的程序将从 1 加到 10:

C:\DEVEL\FBC> sum
Add the numbers from 1 to 10 ...
55

FORTRAN 77

FORTRAN 编程语言类似于旧式和现代 BASIC 之间的混合体。FORTRAN 比 BASIC 更早出现,而 BASIC 显然从 FORTRAN 中汲取灵感,就像后来的 FORTRAN 版本从 BASIC 中获得启示一样。你可以将 FORTRAN 程序以源代码的形式写成文件,但并不需要在每个地方使用行号。但是,FORTRAN 77 在某些指令中使用行号(称为标签),包括 FOR 循环。在 FORTRAN 77 中,FOR 实际上被称为 DO 循环,它执行相同的功能并具有几乎相同的用法。

在 FORTRAN 77 中,DO 循环的语法如下:

DO 行号 变量 = 起始值, 终止值

这种情况是需要行号来指示 DO 循环结束位置的一种情况。你在 BASIC 中使用了 NEXT 指令,但 FORTRAN 需要一个行标签。通常,该行是一个 CONTINUE 指令。

查看这个示例 FORTRAN 程序,了解如何使用 DO 循环来循环一组数字。我将此源文件保存为 sum.f

PROGRAM MAIN
      INTEGER SUM,I
      PRINT *, 'ADD THE NUMBERS FROM 1 TO 10 ...'
      SUM = 0
      DO 10 I = 1, 10
        SUM = SUM + I
   10 CONTINUE
      PRINT *, SUM
      END

在 FORTRAN 中,每个程序都需要以 PROGRAM 指令开始,并指定程序名称。你可能会将此程序命名为 SUM,但随后在程序中不能使用变量 SUM。当我学习 FORTRAN 时,我从 C 编程中借鉴了一些东西,并以 PROGRAM MAIN 开始了我的所有 FORTRAN 程序,做法类似于 C 程序中的 main() 函数,因为我不太可能使用名为 MAIN 的变量。

FORTRAN 中的 DO 循环类似于 BASIC 中的 FOR 循环。它迭代从 1 到 10 的值。变量 I 在每次循环中获取新值。这样可以将 1 到 10 的每个数字相加,并在完成时打印总和。

你可以在每个平台上找到适合的 FORTRAN 编译器,包括 Linux 和 DOS。FreeDOS 1.3 的 Bonus CD 中包括 OpenWatcom FORTRAN 编译器。在 Linux 上,你可能需要安装一个包来安装 GNU Fortran 支持(在 GNU 编译器集合(GCC)中)。在 Fedora Linux 上,你可以使用以下命令添加 GNU Fortran 支持:

$ sudo dnf install gcc-gfortran

然后你可以使用以下命令编译 sum.f 并运行程序:

$ gfortran -o sum sum.f
$ ./sum
ADD THE NUMBERS FROM 1 TO 10 ...
55

一点不同之处

我发现 FORTRAN 和 BASIC 非常相似,但也存在一些不同之处。这些语言的核心是不同的,但如果你了解一些 BASIC,你可以学习 FORTRAN,同样,如果你了解一些 FORTRAN,你也可以学习 BASIC。

如果你想探索这两种语言,有几点需要注意:

  • FORTRAN 77 使用全大写,但后来的 FORTRAN 版本允许大小写混用,只要对变量、函数和子程序使用相同的大小写。大多数 BASIC 实现都不区分大小写,这意味着你可以自由地混合大小写字母。
  • 有许多不同版本的 BASIC,但它们通常做同样的事情。如果你学会了一种 BASIC 实现方式,很容易学会另一种。注意 BASIC 解释器或编译器的警告或错误信息,查阅手册了解差异。
  • 某些 BASIC 实现需要使用行号,例如 Bywater BASIC 和 GW-BASIC。更现代的 BASIC 版本允许你编写不使用行号的程序。FreeBASIC 需要使用 -lang 废弃选项编译带有行号的程序。

(题图:MJ/dba28597-dd62-4ffe-bb4a-e38874a65239)


via: https://opensource.com/article/23/4/basic-vs-fortran-77

作者:Jim Hall 选题:lkxed 译者:ChatGPT 校对:wxy

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