跳至内容

Ada 编程/输入输出/文本示例

来自 Wikibooks,为开放世界提供开放书籍

Ada. Time-tested, safe and secure.
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;

保存文件,然后我们继续最后一步:编译。

编译并运行 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 行输出简直是疯狂。

FtoC 程序如何工作?
[编辑 | 编辑源代码]

让我们从前三行开始

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_IOAda.Integer_Text_IOAda.Flot_Text_IO。这些包提供了它们名称所暗示的确切含义:不同类型的文本 IO 功能。

查看源代码,您可能会想知道 Ada.Text_IO 的必要性在哪里,因为我们只输出数字。我们会很快讲到这一点,敬请关注。

FtoC 声明

[编辑 | 编辑源代码]

接下来我们有这段代码

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 [带注释的])。命名数字可以是任何大小和精度。

FactorOffset 常量用于华氏度到摄氏度的计算,而 Step 定义了我们在程序中执行的转换次数。

FtoC 实体

[编辑 | 编辑源代码]

声明已完成,我们现在将注意力转向程序的实体

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 值时,我们将退出循环。

FtoC 输出 - 将整数放在屏幕上

[编辑 | 编辑源代码]

紧随 loop 语句之后,我们遇到了 Put 过程

Put (Item  => Fahr, Width => Fahrenheit_Degree_Range'Width);

我们从声明中知道 FahrNatural 的子类型,而 Natural 又 是 Integer 的子类型,因此这一行对 Put 的调用实际上调用了 Ada.Integer_Text_IO.Put。如您所见,我们为 Put 提供了两个参数:ItemWidthItem 的含义应该是显而易见的:它是我们想要输出的整数,在本例中是 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 个空格填充,两位数的值输出一致,但其余结果则扩展以容纳第三个字符。

FtoC 输出 - 现在使用浮点数

[编辑 | 编辑源代码]

在处理整数类型的 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; 表示程序的结束。没有更多要做的,也没有更多要看的。控制权将返回给最初调用程序的任何内容,生命继续。

希望您喜欢这个关于构建华氏度到摄氏度转换表的简短教程。留给读者去弄清楚如何添加开氏度。祝您玩得开心!

另请参见

[编辑 | 编辑源代码]

维基教科书

[编辑 | 编辑源代码]
华夏公益教科书