ADA 95教程 PACKAGES

包是ADA存在的原因

Ada的最大优势之一,比大多数其他编程语言,是它定义良好的模块化和独立编译系统。尽管Ada允许单独编译,但它通过强制编译顺序和兼容性检查规则来维护不同编译之间的强类型检查。Ada使用单独的编译,但是FORTRAN作为一个典型的例子,使用独立编译,在这个编译中,各个部分都是编译的,而不知道将与之相结合的其他编译单元。当我们通过这一材料和以后的额外材料的进展,如果您发现许多事情需要记住,在做单独的编译时,不要气馁。这些规则不是为了成为障碍,而是在一个大型复杂系统中工作时对您实际有好处。

 

让我们看看一个包

Example program ------> e_c15_p1.ada

 -- Chapter 15 - Program 1

                 -- Interface of AdderPkg
package AdderPkg is
   type MY_ARRAY is array(INTEGER range <>) of FLOAT;
   procedure Add_Em_Up(In_Dat : in     MY_ARRAY;
                       Sum    :    out FLOAT);
end AdderPkg;


                 -- Implementation of AdderPkg
package body AdderPkg is
   procedure Add_Em_Up(In_Dat : in     MY_ARRAY;
                       Sum    :    out FLOAT) is
   Total : FLOAT;
   begin
      Total := 0.0;
      for Index in In_Dat'FIRST..In_Dat'LAST loop
         Total := Total + In_Dat(Index);
      end loop;
      Sum := Total;
   end Add_Em_Up;
end AdderPkg;




-- Result of execution

-- (This is not a stand alone package, so it has no output.)

 

检查名为e_c15_p1.ada的文件,了解我们的第一个单独编译的ada包示例。包(Ada中使用的术语)是指相关实体的集合,集合由过程、函数、变量、常量、类型、子类型甚至其他包组成。在本例中,包由一个类型和一个过程组成。

 

包规范

第4行到第8行定义了名为AdderPkg的包的规范,它实际上是一个非常简单的包,出于说明的目的特意保持简单。定义了MY_ARRAY的类型,以及包的规范部分中Add_Em_Up的过程标题。为了使用包,用户只需要知道包的一些信息,这些信息是在包规范中定义的,因此它就成为了与外部世界的接口。有了一些定义语句,用户就只需要知道包的情况,他就可以脱离过程如何完成其工作的实际细节。我们将在本章后面看到更多关于信息隐藏的主题。

请注意,在包的规范部分声明的任何实体都可以在with's this package.一起使用的任何其他包中使用。

我们在第5行中声明了一个无约束数组类型,在本教程中还没有研究过它。下标的范围由“<>”定义,这是稍后定义实际类型时必须填写的框。我们将在高级数组主题一章中详细介绍这一点。

 

包的主体

第12行到第23行定义了包的主体,它与规范的区别在于其头中的保留字主体,以及在这里完全定义了过程。在包的规范部分中定义或声明的任何内容都可以在这里使用,就像它是在本节开头定义的一样。这里完整地重新定义了过程头,它必须与规范中的定义完全匹配,否则将出现编译错误而无法编译。这个过程与我们所看到的任何其他过程都没有什么不同,它恰好位于包体中。

请注意,任何类型、变量、常量、过程或函数都可以在包体中声明以在包体中使用,但它们都不能在包外使用,因为它们没有在包的规范部分中定义。第15行中声明的名为Total的变量在此包外部不可用,如果不在包规范中声明,则无法在包外部引用它。事实上,由于它是嵌入在过程中的,所以无论如何它都是隐藏的,但事实上除了在包的规范中声明的实体之外,包之外没有任何实体可用。

在包规范中放置任何子程序体都是不合法的。

请注意,由于数组没有定义的限制,因此必须使用属性来定义循环范围。尽管您可能会觉得这有点混乱,但在我们学习了一些更高级的主题之后,您会欣赏到这里的灵活性。

 

可以编译但不能执行

可以编译此文件,但由于它不是完整的程序,因此无法链接和执行它,它只是一个包含类型和过程的包,可以从另一个程序调用,就像我们调用的过程 Put  in Ada.Text_IO文件本教程中的包。

在我们查看使用这个包的程序之前,必须先指出另一点,规范和主体不需要在同一个文件中,它们可以包含在单独的文件中,并单独编译。如果是这样,那么在编译主体之前必须编译规范,因为主体使用在规范编译期间生成的信息。实际上,即使在本例中它们在一个文件中,编译器也会单独考虑它们,它们是以串行方式编译的。据说,这个文件由两个编译单元组成。

 

文件名与包名称

在本教程中迄今为止的所有示例程序中,我们使用了相同的名称来表示文件名和程序名,或者过程名。这不是真正必要的,但有人认为,应该将额外的复杂性推迟到稍后。我们到了需要解释的时候。

当Ada编译程序时,它使用程序名而不是文件名将编译结果添加到其库中。此库条目包含将程序与其他所需库条目链接所需的所有信息。稍后,当您使用编译器附带的链接器时,链接器将收集所有必要的编译包和子程序,并将它们合并到可执行程序中。

为了编译程序,必须给出文件名,这样操作系统才能找到Ada编译器的程序,但为了链接程序,文件名不再有任何用处,因为真正重要的名称是程序名,程序名就是链接器使用的。Ada允许标识符具有任意长度,但操作系统有一定的长度限制,如果您使用的是MS炣DOS,则如果中间结果放入单个文件中,Ada编译器可能需要以某种方式来补齐一个新名称。您将决定编译器如何存储中间结果,以及它如何为这些结果组成文件名。

因此,为了简单起见,所有程序名都被限制为8个字符,在本教程的大部分时间中,文件名都使用相同的名称。

 

现在使用新的包

Example program ------> e_c15_p2.ada

-- Chapter 15 - Program 2
with Ada.Text_IO, Ada.Float_Text_IO;
use Ada.Text_IO, Ada.Float_Text_IO;

with AdderPkg;
use AdderPkg;

procedure Adder1 is

   FIRST : constant := 2;
   LAST  : constant := 7;
   Sum_Of_Values : FLOAT;

   New_Array : MY_ARRAY(FIRST..LAST);

   procedure Summer(In_Dat : MY_ARRAY;
                    Sum    : out FLOAT) renames AdderPkg.Add_Em_Up;
begin
   for Index in New_Array'FIRST..New_Array'LAST loop
      New_Array(Index) := FLOAT(Index);
   end loop;

   Put_Line("Call Add_Em_Up now");
   Add_Em_Up(New_Array, Sum_Of_Values);
   Put("Back from Add_Em_Up, total is");
   Put(Sum_Of_Values, 5, 2, 0);
   New_Line;

          -- The next three statements are identical
   Add_Em_Up(New_Array, Sum_Of_Values);
   AdderPkg.Add_Em_Up(New_Array, Sum_Of_Values);
   Summer(New_Array, Sum_Of_Values);

end Adder1;




-- Result of execution

-- Call Add_Em_Up now
-- Back from Add_Em_Up, total is   27.00

 

名为e_c15_p2.ada的示例文件演示了如何使用前面研究的包。除了使用我们定义并命名为AdderPkg的包之外,这个程序与我们使用的任何其他程序没有什么不同,它在第5行和第6行中确认了这一点,在第5行和第6行中,它告诉系统使用with包在手头的程序中使用use它。它还使用了第16行中的重命名语句,我们将在后面讨论。程序的其余部分很简单,您应该可以毫不费力地破译它。这两个示例的主要目的是说明如何编写库包。

在第14行中,我们声明了一个MY_ARRAY类型的数组,此时我们提供了下标的范围限制。您可能开始看到这种数组声明方法的灵活性,但我们稍后将详细研究它。

 

with子句

现在终于到了对with子句为我们做什么做一个完整定义的时候了。当我们将一个包放入我们的程序中时,我们告诉系统我们希望该包的规范中声明的所有内容都可以在这个程序中使用。因此,系统将查看在该包的规范中声明的项,并且每次使用这些项中的一个时,它将看到我们拥有正确的参数数量,并且我们拥有正确为每个参数声明的类型。请注意,这发生在编译过程中,并解释了为什么Ada被称为单独编译,而不是独立于其他编译单元。从某种意义上说,withed package中可用的资源就好像它们是Ada编程语言的扩展一样。因此,with子句被称为上下文子句。

当您到达链接操作时,带withed的包将自动添加到可执行文件中,其他带withed的包也会自动添加到程序中。请注意,名为Standard的包自动包含在每个Ada程序、子程序或包中。名为Standard的包定义了许多预定义的实体,如INTEGER、BOOLEAN、FLOAT等。

在一个大型程序中,由于同一个包在多个包中使用,因此可以多次使用同一个包。您可以放心,在链接操作期间只包含一个包的副本。不会存储多个副本。

在离开本主题之前还必须指出另一点,即所有with子句必须位于程序或包的开头。这是有充分理由的。必须在每个包的开头给出依赖包,因此必须给出整个程序结构,这样就可以很容易地确定整个结构,而不必强制搜索每个包的整个列表。

 

use 子句

Ada中的use子句允许您在从包命名过程、函数、变量等时使用速记。您可以简单地使用过程名称,让系统知道它来自哪个包,而不是使用扩展的命名约定,或者所谓的“dot”符号,包括包名后面的过程名,并将其点在一起。在我们迄今研究的大多数项目中,我们包括 Ada.Text_IO文件使用子句中的包。如果省略use子句,我们将不得不在每次使用其中一个程序时识别包。Put("This is Ada");必须更改为读取 Ada.Text_IO.Put("This is Ada");它会使列表有点混乱,但消除了所有的歧义。

由于在重载过程名的非常特殊的条件下,可能得到与您预期的不同的过程,所以使用use子句在软件工程文献中会受到一些不名誉的影响。没有use子句,您必须为每个过程调用输入附加信息。但是,每个子程序调用都预先准备好的包名的存在,不会导致歧义,因此遵循Ada的基本前提,即程序被写入一次,但多次读取。额外的击键值得麻烦地包括在其中。

使用Use子句是个人喜好的问题,或者可能是项目风格指南所规定的风格。

 

重命名过程

可以重命名过程,以减少标识符的长度,特别是如果需要较长的扩展名。根据通用程序的实际使用,最好使用更具描述性的名称。本程序第16和17行说明了重命名方法。最重要的是,必须重复整个形式参数列表。这样做是为了完成新过程名称的定义,并且在程序调试期间应该有帮助。

这个程序的第32行说明了新名称的使用,它当然只是同义词,而不是新的过程。

如果编译了前面的文件,名为e_uc15_up1.ada,那么您可以编译、链接和执行此文件,以了解系统知道如何将两者链接在一起。

 

合并文件

如果您愿意,可以合并这两个文件,前提是将第二个文件附加到第一个文件的末尾。然后编译器将依次编译这三个结果,然后您可以链接结果并执行结果。必须首先编译库文件,这样编译器才能检查过程调用中的类型,以确保它们是否一致,因此在调用程序符合此规则之前放置它们。如果您将它们放在一个文件中,您仍然需要e_c15_p2.ada的第5行和第6行中的语句来告诉系统 with and use 文件中早期定义的库文件,因为编译器将认为它们是三个独立的编译。

 

另一种组合文件的方法

Example program ------> e_c15_p3.ada

名为e_c15_p3.ada的示例程序说明了组合最后两个文件的另一种方法,在本例中,将包包含在程序的声明部分。包装的规格部分在第12行到第16行,主体在第24行到第35行。在本例中,在这两部分之间定义了一个新类型,以说明可以这样做。由于包是作为主程序的一部分编译的,因此不必在with语句中提及它。编译器知道它是程序的一部分,但必须提到如何使用它来告诉系统从何处获取过程名和类型。当然,可以省略该用法,并且扩展命名约定用于对包的所有引用。

尽管主体是在第20行声明变量 New_Array之后定义的,但是这个变量不能直接在主体中使用,因为包结构有效地在封闭的语句周围构建了一道坚固的墙,任何东西都不能进出。主体的唯一输入和输出是在包的规范部分中定义的。当然,变量 New_Array对过程可用,因为它是作为参数传入的,但不直接可见。

这里还有一个与前两个文件不同的区别。此嵌入包不可供任何其他程序使用,因为它包含在此程序中,因此不是库包。整个文件只包含一个编译单元。

 

另一种单独的编译

Example program ------> e_c15_p4.ada

 -- Chapter 15 - Program 4
with Ada.Text_IO, Ada.Float_Text_IO;
use Ada.Text_IO, Ada.Float_Text_IO;

procedure Adder3 is

   FIRST : constant := 2;
   LAST  : constant := 7;
   Sum_Of_Values : FLOAT;

                      -- Interface of AdderStb
   package AdderStb is
      type MY_ARRAY is array(INTEGER range <>) of FLOAT;
      procedure Add_Em_Up(In_Dat : in     MY_ARRAY;
                          Sum    :    out FLOAT);
   end AdderStb;


   use AdderStb;
   New_Array : MY_ARRAY(FIRST..LAST);


                       -- Implementation of AdderStb
   package body AdderStb is separate;


begin
   for Index in New_Array'FIRST..New_Array'LAST loop
      New_Array(Index) := FLOAT(Index);
   end loop;

   Put_Line("Call Add_Em_Up now.");
   Add_Em_Up(New_Array, Sum_Of_Values);
   Put("Back from Add_Em_Up, total is");
   Put(Sum_Of_Values, 5, 2, 0);
   New_Line;

end Adder3;




-- Result of execution

-- Call Add_Em_Up now
-- Back from Add_Em_Up, total is   27.00

 名为e_c15_p4.ada的示例文件说明了另一种单独编译的方法。这个程序与上一个程序相同,只是包体被移到一个单独的文件中进行单独编译,称为存根。第24行中的语句向编译器指示正文将在其他地方找到。尽管这说明了对包体的单独编译,但同样的方法也可以用于对过程的单独编译。如果您希望从这里定义的逻辑中删除一个大型过程以使其更易于管理,可以使用这个方法。

 

Example program ------> e_c15_p5.ada

 -- Chapter 15 - Program 5
separate(Adder3)
                 -- Implementation of AdderStb
package body AdderStb is
   procedure Add_Em_Up(In_Dat : in     MY_ARRAY;
                       Sum    :    out FLOAT) is
   Total : FLOAT;
   begin
      Total := 0.0;
      for Index in In_Dat'FIRST..In_Dat'LAST loop
         Total := Total + In_Dat(Index);
      end loop;
      Sum := Total;
   end Add_Em_Up;
end AdderStb;

 单独编译的主体位于名为e_uC15_uP5.ada的文件中,该文件以保留词分隔开头,表示这是存根。使用此存根的主程序或其他包、过程或函数在保留词后面的括号中定义,以告诉编译器使用该存根的位置。此存根不能被任何其他程序调用或使用,因为它实际上是程序Adder3的一部分,而不是通用程序。在使用存根的点上可用的任何变量、类型、过程等都可以在定义存根的点上使用。因此,存根具有与使用点存在的环境相同的环境,在本例中,e_c15_p4.ada.的第24行。

 

存根的编译顺序

由于所有变量、类型等都必须对存根可用,因此必须在编译存根本身之前编译使用的程序。编译完这两个程序后,就可以链接并执行程序了。您应该首先编译e_c15_p4.ada,然后编译e_c15_p5.ada,最后链接ADDER3。然后您将有一个可执行的ADDER3程序。

 

编译顺序并不神秘

本章包含的示例文件旨在以有意义的方式向您说明所需的编译顺序,而不是作为一组要记住的规则。如果您了解文件之间的依赖关系,那么编译顺序将是有意义的,并且您将能够智能地安排程序在各个单独编译的文件之间使用Ada类型检查。请记住,Ada与Pascal不同,它的设计允许开发需要单独编译工具的大型程序,同时保留模块之间强大的类型检查。

 

带有初始化部分的包

Example program ------> e_c15_p6.ada

-- Chapter 15 - Program 6

                 -- Interface of AdderPkg
package AdderPkg is
   Total : FLOAT;               -- Global variable
   type MY_ARRAY is array(INTEGER range <>) of FLOAT;
   procedure Add_Em_Up(In_Dat : in     MY_ARRAY;
                       Sum    :    out FLOAT);
end AdderPkg;


                 -- Implementation of AdderPkg
package body AdderPkg is
   procedure Add_Em_Up(In_Dat : in     MY_ARRAY;
                       Sum    :    out FLOAT) is
   begin
      for Index in In_Dat'FIRST..In_Dat'LAST loop
         Total := Total + In_Dat(Index);
      end loop;
      Sum := Total;
   end Add_Em_Up;

begin            -- Initialization section
   Total := 0.0;
end AdderPkg;




with Ada.Text_IO;
use Ada.Text_IO;

with AdderPkg;
use AdderPkg;

procedure Adder4 is

   package Flt_IO is new Ada.Text_IO.Float_IO(FLOAT);
   use Flt_IO;

   FIRST : constant := 2;
   LAST  : constant := 7;
   Sum_Of_Values : FLOAT;

   New_Array : MY_ARRAY(FIRST..LAST);

   procedure Summer(In_Dat : MY_ARRAY;
                    Sum    : out FLOAT) renames AdderPkg.Add_Em_Up;
begin
   for Index in New_Array'FIRST..New_Array'LAST loop
      New_Array(Index) := FLOAT(Index);
   end loop;

   Put_Line("Call Add_Em_Up now.");
   Add_Em_Up(New_Array, Sum_Of_Values);
   Put("Back from Add_Em_Up, total is");
   Put(Sum_Of_Values, 5, 2, 0);
   New_Line;

          -- The next three statements are identical
   Add_Em_Up(New_Array,Sum_Of_Values);
   AdderPkg.Add_Em_Up(New_Array, Sum_Of_Values);
   Summer(New_Array, Sum_Of_Values);

end Adder4;




-- Result of execution

-- Call Add_Em_Up now
-- Back from Add_Em_Up, total is   27.00

 

名为e_c15_p6.ada的示例文件说明了包的另一个特性。这个程序与本章的第一个程序几乎相同。构成第一个示例程序的两个文件都已合并到一个文件中,以便于编译,名为Total的变量已在包中成为全局变量。因为它在包中是全局的,所以包中的任何子程序或初始化部分都可以引用它,如第23行和第24行所示。此部分是可选的,但可以包含在任何包中。

注意,初始化部分只在加载时执行一次。因此,这个程序不会像第一个程序那样完全运行。在这两种情况下,输出的结果是相同的,但是额外的调用不会产生相同的结果,因为在这个示例程序中,变量Total在每次调用期间都不会像第一个示例中那样被清除为零。初始化部分只初始化变量一次,不可能从包的可执行部分调用这段代码。

 

一些标准包装

ada95提供了许多新的包来改进编程模型,防止程序员出错,并在编程时提供额外的便利。在本教程前面的Ada学习过程中,我们已经讨论了一些附加包。我们将对一些你可能会发现有用的,但确实不适合在教程中任何地方的评论。

package Standard-这是ADA95中所有包的根,是定义整数、浮点、布尔等实体的地方。它固有地自动嵌入到每个Ada编译单元中。

package Ada.Command_Line -这实际上是上述标准软件包的一部分,但您实际上看不到这一事实。这个包提供了在用户开始执行结果程序时检索用户提供的任何参数的能力。这个包提供了一种以可移植的方式检索这些参数的方法。这在ADA83中并不存在,因此每个编译器编写者都提出了一种不可移植的方法来实现这一点。

package Ada.Numerics.Elementary_Functions-这个软件包提供了trig函数、浮点求幂运算符和许多其他数学函数。

package Ada.Characters.Latin_1-这个包取代了ASCII包,ASCII包在ada83中可用,但现在已经过时了。这个新的包提供了ASCII字符集的定义。

 

与其他语言的接口

ARM的附录B定义了与编译器可能支持或不支持的其他语言的接口方法。名为Interface.CInterface.COBOL, and Interface.Fortran, 如果您的编译器提供,将使您能够以定义良好的方式将这些语言中的代码与Ada代码结合起来。特别重要的是处理字符串、指针、数组和结构的方法,因为它们对于其他语言可能非常不同。接口由各种pragmas and subprograms组成,用于定义接口特性。如果需要与Ada编译器支持的语言进行接口,您可以自己研究细节。

 

编程练习

1.从 e_c15_p3.ada中移除包体并使其成为存根。按照正确的顺序编译两个结果文件,并链接并执行结果程序。(Solution 1)(Solution 2)

2.从上一章中名为e_c14_p1.ada的程序中删除所有use子句,并用正确的包名作为I/O过程调用的前缀。您应该清楚,这样做之后就不会有命名冲突了(Solution)

3.在不修改文件名的情况下,更改e_c15_p1.ada中两个编译单元的名称,并修改e_c15_p2.ada以对应新的包名。使用包含的字符数超过操作系统允许的文件名的名称。编译每个文件并将它们链接在一起,生成一个可执行程序(Solution 1)(Solution 2)

 

---------------------------------------------------------------------------------------------------------------------------

原英文版出处:https://perso.telecom-paristech.fr/pautet/Ada95/a95list.htm

翻译(百度):博客园  一个默默的 *** 的人

 

posted @ 2021-04-08 21:02  yangjianfeng  阅读(207)  评论(0编辑  收藏  举报