Ada 编程/输入输出/文本示例
我们中那些有美国朋友的人经常不得不处理华氏温度标度。虽然世界其他地方已经采用摄氏温度标度,但美国和其他一些国家仍然忠于华氏度。
虽然在与朋友打交道时,这是一个既小又无关紧要的问题,但它仍然是一个值得拥有自己的程序的问题:华氏度到摄氏度的转换器。
在我们开始之前,我们需要做一些准备。首先在您的计算机上创建以下目录/文件结构
$ mkdir FtoC
$ cd FtoC
$ mkdir exe objects
$ touch ftoc.adb ftoc.gpr
上面应该在 FtoC
目录中留下以下内容
exe/ objects/ ftoc.adb ftoc.gpr
我们将以以下方式使用这两个目录和文件
- exe/ 这是编译程序时
ftoc
可执行文件放置的目录 - objects/ 编译程序时,编译器会创建 ALI 文件、目标文件和树文件。这些文件将放置在此目录中。
- ftoc.adb ftoc 程序的主要 Ada 源文件。
- ftoc.gpr 这是一个 Ada 项目文件。此文件控制程序的各种属性,例如如何编译它,包含哪些源代码,将文件放在哪里等等。
将此添加到 ftoc.gpr
文件中
project ftoc is
for Source_Dirs use (".");
for Main use ("ftoc.adb");
for Exec_Dir use "exe";
for Object_Dir use "objects";
package Ide is
for Compiler_Command ("ada") use "/usr/gnat/bin/gnatmake";
end Ide;
package Compiler is
Common_Options := ("-gnatwa",
"-gnaty3abcdefhiklmnoprstux",
"-Wall",
"-O2");
for Default_Switches ("Ada") use Common_Options;
end Compiler;
end ftoc;
请前往 此处 了解有关上面项目文件的不同部分的说明。
项目文件已完成,我们现在将注意力转向实际的 ftoc
代码。将此添加到 ftoc.adb
文件中
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
procedure FtoC is
subtype Fahrenheit_Degree_Range is Natural range 0 .. 212;
-- Now, that is a Natural Range for Ada, but I can tell you that Americans
-- are not nearly so happy with "0" and not even an Icelander likes it at "212"!
Fahr : Fahrenheit_Degree_Range := Fahrenheit_Degree_Range'First;
Factor : constant := 5.0 / 9.0;
Offset : constant := 32;
Step : constant := 1;
begin
loop
Put (Item => Fahr, Width => Fahrenheit_Degree_Range'Width);
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 0);
New_Line;
exit when Fahr = Fahrenheit_Degree_Range'Last;
Fahr := Fahr + Step;
end loop;
end FtoC;
保存文件,然后我们继续最后一步:编译。
如果您使用优秀的 GNAT Studio IDE,编译只需简单地按下 F4
来编译项目,并按下 Shift+F2
来执行它。如果您没有使用此 IDE,则执行以下操作
$ gnatmake -P ftoc.gpr
您应该会看到一些输出滚动
gcc -c -gnatwa -gnaty3abcdefhiklmnoprstux -Wall -O2 -I- -gnatA /home/thomas/FtoC/ftoc.adb gnatbind -I- -x /home/thomas/FtoC/objects/ftoc.ali gnatlink /home/thomas/FtoC/objects/ftoc.ali -o /home/thomas/FtoC/exe/ftoc
您现在在 exe
/ 目录中有一个可执行文件。当您运行它时,您会得到以下内容(此处略微缩写)
0 -17.78 1 -17.22 2 -16.67 ... 26 -3.33 27 -2.78 28 -2.22 29 -1.67 30 -1.11 31 -0.56 32 0.00 33 0.56 34 1.11 35 1.67 ... 48 8.89 49 9.44 50 10.00 51 10.56 52 11.11 ... 67 19.44 68 20.00 69 20.56 70 21.11 71 21.67 ... 84 28.89 85 29.44 86 30.00 87 30.56 ... 210 98.89 211 99.44 212 100.00
现在我们知道 0 华氏度等于 -17.78 摄氏度,在 212 华氏度时水沸腾,对于我们摄氏度用户来说,这是 100 摄氏度。该程序可以正常工作!让我们详细地回顾一下,以了解它是如何工作的。
注意: 以下示例中的所有输出都将大幅缩写,因为为每个小示例打印 213 行输出简直是疯狂。
让我们从前三行开始
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
这些被称为 with
子句和 use
声明。Ada with
子句可以比作 C #include
。当您看到 with Ada.Text_IO
时,这意味着 Ada.Text_IO
包的实用程序将提供给程序。当您接下来看到声明 use Ada.Text_IO
时,这意味着 Ada.Text_IO
的实用程序可以直接被程序看到,即您不必编写
Ada.Text_IO.Put ("No use clause");
来调用 Put
过程。相反,您只需执行
Put ("With use clause");
在 FtoC 程序中,我们利用并使其可见三个包:Ada.Text_IO
、Ada.Integer_Text_IO
和 Ada.Flot_Text_IO
。这些包提供了它们名称所暗示的确切含义:不同类型的文本 IO 功能。
查看源代码,您可能会想知道 Ada.Text_IO
的必要性在哪里,因为我们只输出数字。我们会很快讲到这一点,敬请关注。
接下来我们有这段代码
procedure FtoC is
subtype Fahrenheit_Degree_Range is Natural range 0 .. 212;
Fahr : Fahrenheit_Degree_Range := Fahrenheit_Degree_Range'First;
Factor : constant := 5.0 / 9.0;
Offset : constant := 32;
Step : constant := 1;
begin
这被称为 Ada 程序的声明部分。在这里,我们声明变量、常量、类型等等。在 FtoC 的情况下,我们有 5 个声明。让我们谈谈前两个
subtype Fahrenheit_Degree_Range is Natural range 0 .. 212;
Fahr : Fahrenheit_Degree_Range := Fahrenheit_Degree_Range'First;
这正是 Ada 最大的卖点之一:能够创建自己的类型,并设置自己的约束。它可能看起来并不重要,但相信我,它确实很重要。在这种情况下,我们创建了内置子类型 Natural (3.5.4 [带注释的]) 的一个新 子类型。我们将该类型的范围约束为 0 .. 212
,这意味着声明为 Fahrenheit_Degree_Range
类型的对象永远 不能 低于 0 或高于 212。如果将超出此范围的值分配给 Fahrenheit_Degree_Range
类型 的对象,则会引发 Constraint_Error
异常。
有了 Fahrenheit_Degree_Range
类型,我们现在将注意力转向 Fahr
变量的声明和赋值。如果您从未见过此语法或它对您来说毫无意义,请阅读本维基上的 常量 文章,然后返回此处。
Fahr
变量是 Fahrenheit_Degree_Range
类型的,其初始值为 0。为什么是 0?因为我们将其赋值为 Fahrenheit_Degree_Range'First
,而 'First
部分等于该类型范围约束中的第一个值,在本例中为 0。因此,Fahrenheit_Degree_Range'Last
的值为 212。
让我们看一下最后三个对象声明
Factor : constant := 5.0 / 9.0;
Offset : constant := 32;
Step : constant := 1;
这里发生了什么?为什么这些声明中没有与任何声明相关的 type
?好吧,如果您阅读了 常量 文章,您将知道这些常量被称为 命名数字,它们的 type
被称为通用类型 (3.4.1 [带注释的])。命名数字可以是任何大小和精度。
Factor
和 Offset
常量用于华氏度到摄氏度的计算,而 Step
定义了我们在程序中执行的转换次数。
声明已完成,我们现在将注意力转向程序的实体
begin
loop
Put (Item => Fahr, Width => Fahrenheit_Degree_Range'Width);
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 0);
New_Line;
exit when Fahr = Fahrenheit_Degree_Range'Last;
Fahr := Fahr + Step;
end loop;
end FtoC;
保留字 begin
表示 body
的开始,而相同的 body
以最终的 end FtoC
结束。在这两者之间,我们有一堆语句。
第一个是 loop
语句。 循环 在 Ada 中以多种不同的形式出现,每种形式都遵循循环的基本前提
loop
-- some statements
end loop;
循环可以使用 exit
关键字终止
loop
-- some statements
if X = Y then
exit;
end if;
end loop;
这种结构非常常见,因此提供了一个简短版本
loop
-- some statements
exit when X = Y;
end loop;
正是我们最后使用的版本在 FtoC 程序中,当 Fahr
等于 Fahrenheit_Degree_Range
类型的 'Last
值时,我们将退出循环。
紧随 loop
语句之后,我们遇到了 Put
过程
Put (Item => Fahr, Width => Fahrenheit_Degree_Range'Width);
我们从声明中知道 Fahr
是 Natural
的子类型,而 Natural
又 是 Integer
的子类型,因此这一行对 Put
的调用实际上调用了 Ada.Integer_Text_IO.Put
。如您所见,我们为 Put
提供了两个参数:Item
和 Width
。Item
的含义应该是显而易见的:它是我们想要输出的整数,在本例中是 Fahr
变量。
另一方面,Width
并不那么直观。Width
参数指定了将整数类型(在本例中是子类型)作为字面量输出所需的最小字符数,再加上可能出现的负号所需的 1 个字符。如果 Width
参数设置过高,整数字面量将用空格填充。如果设置过低,它将根据需要自动扩展。将 Width
参数设置为 0,会导致字段的宽度为包含整数所需的最小宽度。
'Width
属性返回类型的最大宽度,因此如果我们稍后更改 Fahrenheit_Degree_Range
,我们就不必对这个对 Put
的调用做任何事情;它会自动相应调整。
让我们使用不同的 Width
参数进行一些测试
Put (Item => Fahr); -- Default Width parameter
输出现在看起来像这样
0 -17.78 1 -17.22 2 -16.67 3 -16.11 4 -15.56 ...
有很多浪费的空间。这是因为 Put
现在为 Integer
预留了空间,而 Fahrenheit_Degree_Range
是从该类型派生的。让我们尝试使用 0
Put (Item => Fahr, Width => 0); -- Minimum required characters for the integer
以及输出
0 -17.78 1 -17.22 2 -16.67 3 -16.11 9 -12.78 10 -12.22 11 -11.67 99 37.22 100 37.78 101 38.33 ...
不幸的是,为了提高可读性,Fahr
整数字面量不再右对齐。每个数字都将获得包含它所需的精确宽度,不多也不少。
让我们尝试一个足够容纳部分 Fahr
值,但不能容纳所有值的 Width
Put (Item => Fahr, Width => 2); -- Minimum width of 2. Expands if necessary
这将输出
0 -17.78 1 -17.22 2 -16.67 9 -12.78 10 -12.22 11 -11.67 98 36.67 99 37.22 100 37.78 101 38.33 102 38.89 103 39.44 ...
如您所见,一位数的值右对齐并用 1 个空格填充,两位数的值输出一致,但其余结果则扩展以容纳第三个字符。
在处理整数类型的 Put
之后,我们继续进行下一个 Put
>
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 0);
对 Put
的这个调用中的 Item
参数是一个浮点数,因为 Factor
是一个包含小数点的命名数字,表达式 Fahr - Offset
使用 Float (Fahr - Offset)
表达式转换为浮点数。因此,在调用这个 Put
时,我们实际上调用的是 Ada.Float_Text_IO.Put
。
Fore
参数指定了在小数点之前输出值所需的最小字符数。与整数类型的 Width
一样,Fore
会在必要时自动扩展。Aft
设置小数点后的精度,在本例中为 2。最后,Exp
设置指数字段大小。Exp => 0
的值表示不输出指数。非零值将输出指数符号“E”,+/–,以及指数的数字。注意:Exp
的值不应小于零!
让我们尝试几种不同的组合
Put (Item => Factor* Float (Fahr - Offset),
Fore => 10,
Aft => 4,
Exp => 0);
输出
0 -17.7778 1 -17.2222 15 -9.4444 16 -8.8889 17 -8.3333 26 -3.3333 27 -2.7778 100 37.7778 101 38.3333 102 38.8889 103 39.4444 104 40.0000 ...
或者,试试这个
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 1);
以及输出
0 -1.78E+1 1 -1.72E+1 2 -1.67E+1 8 -1.33E+1 9 -1.28E+1 10 -1.22E+1 11 -1.17E+1 18 -7.78E+0 37 2.78E+0 98 3.67E+1 99 3.72E+1 100 3.78E+1 101 3.83E+1 ...
最后
Put (Item => Factor* Float (Fahr - Offset),
Fore => 0,
Aft => 1,
Exp => 0);
这将输出
0-17.8 8-13.3 9-12.8 10-12.2 11-11.7 31-0.6 320.0 499.4 9836.7 9937.2 10037.8 10138.3 10238.9 ...
这显然看起来不太美观。
FtoC 程序的最后四行完成了我们的格式化,如果我们已经完成则退出,否则将 Fahr
的下一个值设置为要转换的值
New_Line;
exit when Fahr = Fahrenheit_Degree_Range'Last;
Fahr := Fahr + Step;
end FtoC;
对 New_Line
的唯一调用是我们在使用 with Ada.Text_IO
使 Ada.Text_IO
可用于 FtoC 程序的原因。New_Line
的作用是输出一个换行符。如果没有对 New_Line
的调用,程序运行后将产生如下所示的输出
0 -17.78 1 -17.22 2 -16.67 3 -16.11 4 -15.56 5 -15.00 6 -14.44 7 -13.89 8 -13.33 9 -12.78 10 -12.22 11 -11.67 ...
New_Line
过程接受一个 Spacing
参数,这意味着您可以执行以下操作来输出连续的换行符
New_Line (Spacing => 5);
或者,简单地
New_Line (5);
我们已经讨论了 终止循环 的 exit when
方法,最后一条语句只是一个简单的计数器。Fahr
的值在每次循环迭代时都会以 Step
增加。当 Fahr
等于 Fahrenheit_Degree_Range'Last
时,循环将终止。
最后一行 end Ftoc;
表示程序的结束。没有更多要做的,也没有更多要看的。控制权将返回给最初调用程序的任何内容,生命继续。
希望您喜欢这个关于构建华氏度到摄氏度转换表的简短教程。留给读者去弄清楚如何添加开氏度。祝您玩得开心!