ADA 95教程 子程序
LET'S LOOK AT A PROCEDURE
Example program ------> e_c08_p1.ada
with Ada.Text_IO; use Ada.Text_IO; procedure Proced1 is procedure Write_A_Line is begin Put("This is a line of text."); New_Line; end Write_A_Line; begin Write_A_Line; Write_A_Line; end Proced1; -- Result of execution -- This is a line of text. -- This is a line of text.
Ada被设计成非常模块化的,我们将学习模块化的第一种也是最简单的形式,过程procedure。如果您检查名为e_c08_p1.ada的程序,您将得到程序的第一个示例。有些语言将这种子程序称为子例程,有些则称为函数,但Ada将其称为过程procedure。
THE PROCEDURE就像MAIN PROGRAM
从主程序的可执行部分开始,我们在第14行和第15行中有两个可执行语句,如果名称有任何含义,每个语句都调用一个过程来写一行。一如既往,这两条语句是连续执行的,每条语句都是一个过程调用,我们希望将控制权转移到该过程调用。程序本身在第7行到第11行中定义,仔细检查会发现程序的结构与主程序的结构非常相似。事实上,它与主程序是相同的,正如我们开始执行主程序时提到它的名称一样,我们开始执行过程时提到它的名称。该过程有一个声明性部分(在本例中为空)和一个可执行部分(表示显示一行文本并将光标返回到下一行的开头)。
当执行到达过程的end语句时,过程完成,控制返回到调用程序中的下一个连续语句。
我们所说的关于主程序的一切,实际上是一个过程,对于这个过程来说都是正确的。因此,我们可以定义新的类型、声明变量和常量,甚至在这个过程的声明性部分定义其他过程。同样,我们可以调用其他过程,并在过程的可执行部分执行赋值和比较。
声明顺序
最初的Ada规范ada83要求所有过程必须在所有类型、常量和变量声明之后。这是为了迫使您安排您的程序,以便所有较小的声明必须排在第一位,然后是较大的声明,这样较小的声明就不会因为隐藏在较大的声明之间而丢失。这已经从Ada 95的要求中删除,您可以按照您想要的顺序列出各种实体。
embedded procedure 嵌入过程具有为主程序定义的所有灵活性和要求。在接下来的几个示例程序中,我们将给出每一点的进一步示例。此时,编译并运行这个程序,并确保您完全理解它的操作。
三种过程
Example program ------> e_c08_p2.ada
-- Chapter 8 - Program 2 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Proced2 is Counter : INTEGER; procedure Write_A_Header is begin Counter := 1; Put("This is the heading for this little program."); New_Line(2); end Write_A_Header; procedure Write_And_Increment is begin Put("This is line number"); Put(Counter, 2); Put_Line(" of this program."); Counter := Counter + 1; end Write_And_Increment; procedure Write_An_Ending_Statement is begin New_Line; Put_Line("This is the end of this little program."); end Write_An_Ending_Statement; begin Write_A_Header; for Index in 1..7 loop Write_And_Increment; end loop; Write_An_Ending_Statement; end Proced2; -- Result of execution -- This is the heading for this little program. -- -- This is line number 1 of this program. -- This is line number 2 of this program. -- This is line number 3 of this program. -- This is line number 4 of this program. -- This is line number 5 of this program. -- This is line number 6 of this program. -- This is line number 7 of this program. -- -- This is the end of this little program.
检查名为e_c08_p2.ada的程序,您将在该程序的声明性部分看到三个过程。跳转到主程序,从第30行开始,您将看到程序将编写一个头,写入并递增7次,然后编写一个结束语句。注意,描述性过程名称的使用如何导致我们理解程序将做什么,而不必查看过程本身。名字很长,输入起来有点乏味,但是由于Ada被设计成一种只写一次,读很多次的语言,当将来有几个人学习源代码时,额外的时间将会得到回报。
什么是全局变量?
第7行中定义的名为Counter的变量是一个全局变量,因为它是在任何过程之前定义的。因此,这些程序中的任何一个都可以使用它,事实上,其中两个程序也可以使用它。在第11行中,当调用名为Write_A_Header的过程时,变量计数器被赋值为1。每次调用Write_and_Increment时,Counter 的当前值将与一条消息一起写入显示器,并且该值将递增。请仔细注意,这些过程不是按照它们的顺序执行的,因为它们在程序的声明部分中的相对顺序,而是因为主程序按该顺序调用它们。如果您将第一个过程移动到过程Write_An_Ending_Statement之后,并编写一个结束语句,那么程序的工作方式将完全相同。执行顺序由调用顺序控制,而不是过程的物理顺序。
为什么程序要放在声明部分?
程序的声明性部分是我们在可执行部分中定义实体的地方。程序实际上是如何做某事的定义,所以它们就在它们所属的地方。编译并运行这个程序,确保您理解输出。
带参数的过程
Example program ------> e_c08_p3.ada
-- Chapter 8 - Program 3 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Proced3 is Dogs, Cats, Animals : INTEGER; -- This is a procedure specification procedure Total_Number_Of_Animals(Variety1 : in INTEGER; Variety2 : in INTEGER; Total : out INTEGER); -- This is a procedure body procedure Total_Number_Of_Animals(Variety1 : in INTEGER; Variety2 : in INTEGER; Total : out INTEGER) is begin Total := Variety1 + Variety2; end Total_Number_Of_Animals; begin Dogs := 3; Cats := 4; Total_Number_Of_Animals(Dogs, Cats, Animals); Put("The total number of animals is"); Put(Animals, 3); New_Line; end Proced3; -- Result of execution -- The total number of animals is 7
检查名为e_c08_p3.ada的文件,您会发现一个过程,每次调用它时都需要向它提供一些数据。三个变量被定义为INTEGER类型,并在第25行的过程调用中使用。我们将忽略第9行到第12行中的一些奇怪的构造。从第15行开始的过程头说明它需要三个参数,每个参数的类型都必须是INTEGER,所以到目前为止我们是兼容的。调用过程时,第一个变量(在本例中称为Dogs)的值被带到过程中,其中过程更喜欢用Variety1来引用它。同样,Cats的值也被赋予了这个过程,称为Variety2。名为Animals 的变量在过程中由Total名称引用。该过程现在可以对这些变量做一些有意义的工作,但是由于过程头的模式字段,它对这些变量所能做的有些限制。
参数的模式
在过程头中调用的形式参数Variety1属于 in模式,这意味着它是一个输入参数,因此不能在过程中更改。Variety1和Variety2出于同样的原因,在过程中是一个常量,任何给它赋值的尝试都会导致编译错误。但是,名为Total的形式参数是模式out,因此可以在过程中为其指定一个新值。Ada 83规定不能读取输出模式变量,但Ada 95放宽了这一要求,允许您在程序中读取输出模式变量。必须格外小心,不要试图读取尚未初始化为某个有意义值的out mode参数的值。属于“in ”或“ in out ”模式的参数不存在此问题,因为根据定义,它们在调用过程时已初始化。
如果一个参数被定义为 in out模式,那么它既可以被读取也可以被写入。如果没有给定模式,系统将使用中的模式作为默认模式。
参数模式选择
所有变量都可以定义为模式in out,不会有任何问题,因为会有最大的灵活性,或者看起来是这样。还有一条规则必须考虑,这条规则说,out或in-out模式的每个参数都必须用一个变量作为调用程序中的实际参数来调用。这允许返回值并将其赋给变量。模式 in的变量可以使用常量或变量作为调用程序中的实际参数。我们一直在使用New_Line过程,其中包含一个常量,例如New_Line(2),如果它是用模式 in out的形式参数定义的,我们就必须定义一个变量,将值2赋给它,并在调用中使用该变量。这会使程序更难使用,事实上,有些尴尬。因此, New_Line 的形式参数是使用中的模式 in定义的。您应该非常小心地选择形式参数的模式。
这三个形式参数可以在过程中使用,只要按图示定义它们,就好像它们是在过程的声明性部分中定义的一样。它们不可用于任何其他过程或主程序,因为它们是为该过程在本地定义的。但是,在使用时,它们的使用必须与每个的定义模式一致。
一些全局变量
第7行中声明的三个变量可以在程序和主程序中引用。因为可以在过程中引用它们,所以可以在过程中直接更改它们。当控件返回到主程序时,也可以修改变量Animals,因为它在过程头中声明为out模式。这种可能性会导致一些不寻常的结果。你应该花点时间
程序规范
第10、11和12行给出了一个程序规范的示例。这是一个不完整的过程声明,当您开始编写更大的程序时,您会发现它很有用。如果需要,过程规范可以包含在任何过程中,它描述了过程的外部接口,而不声明过程实际执行的操作。Pascal程序员将认识到这与forward声明非常相似。关于这个话题,我们将在后面进行更多的讨论。请注意,在这种情况下不需要程序规范,它只是作为一个说明。
在你理解了简单的加法和赋值之后,编译并运行这个程序。
调用其他程序的程序
Example program ------> e_c08_p4.ada
-- Chapter 8 - Program 4 with Ada.Text_IO; use Ada.Text_IO; procedure Calling is procedure One is begin Put("This is procedure One talking."); New_Line; end One; procedure Two is begin Put("This is procedure Two talking."); New_Line; One; end Two; procedure Three is begin Put("This is procedure Three talking."); New_Line; Two; One; end Three; begin One; Two; Three; end Calling; -- Result of execution -- This is procedure One talking. -- This is procedure Two talking. -- This is procedure One talking. -- This is procedure Three talking. -- This is procedure Two talking. -- This is procedure One talking. -- This is procedure One talking.
示例程序e_c08_p4.ada包含调用另一个过程的过程的示例,如果被调用的过程在调用过程的范围内,则完全合法。更多关于范围的内容将在本教程后面部分介绍。这里要提到的唯一规则是,必须在调用过程之前定义它。理解这个程序应该不会有任何问题,当你理解的时候,你应该编译并执行它。确保您了解输出中的每一行来自何处,以及为什么它按顺序列出。
我们如何嵌套程序?
Example program ------> e_c08_p5.ada
-- Chapter 8 - Program 5 with Ada.Text_IO; use Ada.Text_IO; procedure Nesting is procedure Triple is procedure Second_Layer is procedure Bottom_Layer is begin Put_Line("This is the Bottom Layer talking."); end Bottom_Layer; begin Put_Line("This is the Second Layer talking."); Bottom_Layer; Put_Line("We are back up to the Second Layer."); end Second_Layer; begin Put_Line("This is procedure Triple talking to you."); Second_Layer; Put_Line("We are back up to the procedure named Triple."); end Triple; begin Put_Line("Start the triple nesting here."); Triple; Put_Line("Finished, and back to the top level."); end Nesting; -- Result of execution -- Start the triple nesting here. -- This is procedure Triple talking to you. -- This is the Second Layer talking. -- This is the Bottom Layer talking. -- We are back up to the Second Layer. -- We are back up to the procedure named Triple. -- Finished, and back to the top level.
检查名为e_c08_p5.ada的程序以获取嵌套过程的示例。我们前面提到过,可以在任何其他过程的声明性部分中嵌入一个过程。这在第9行到第20行中进行了说明,其中程序Second_Layer 嵌入到程序Triple中。另外,过程Second_Layer将过程Bottom_Layer 嵌入到第11行到第14行的声明部分中。这种嵌套可以无限期地继续下去,因为Ada中对嵌套的深度没有限制。
程序的可见性
程序的可见性是有限制的。任何过程都是可见的,因此,如果它位于调用过程的声明性部分的顶层,就可以调用它。如果任何过程在调用点之前、在同一级别上、在同一声明性部分中,那么它也是可见的。最后,如果任何过程在调用点嵌套的子程序之前、在同一级别上、在同一声明性部分内,则可以调用该过程。简单地说,一个过程在三种情况下是可见的。首先,如果它在调用过程的声明性部分中。第二种情况是,如果它是对等的(在父子程序的同一级别上),或者第三种情况是,如果它是任何父子程序的对等的。
因此,名为Triple的过程可以调用 Second_Layer,但不能调用底部的 Bottom_Layer,因为它位于较低的级别并且不可见。根据这些规则,主程序只允许调用Triple,因为其他两个过程嵌套太深,无法直接调用。一定要编译运行这个程序并研究结果。
ADA函数
Example program ------> e_c08_p6.ada
-- Chapter 8 - Program 6 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Funct is Twelve : INTEGER := 12; Sum : INTEGER; -- This is a function specification function Square(Val : INTEGER) return INTEGER; -- This is a function body function Square(Val : INTEGER) return INTEGER is begin return Val * Val; end Square; function Sum_Of_Numbers(Val1, Val2 : INTEGER) return INTEGER is begin return Val1 + Val2; end Sum_Of_Numbers; begin Put("The square of 12 is"); Put(Square(Twelve), 4); New_line; Sum := Sum_Of_Numbers(Twelve, 12); Put("The sum of 12 and 12 is"); Put(Sum, 4); New_Line; end Funct; -- Result of execution -- The square of 12 is 144 -- The sum of 12 and 12 is 24
名为e_c08_p6.ada的程序有两个ada函数示例。函数与过程的区别只有两个方面。函数返回一个值,该值用于替换其调用,函数的所有形式参数都必须是in类型,不允许使用其他模式。在所考虑的程序中,说明了两个函数,一个从第13行开始,另一个从第18行开始。请注意,每个函数都以保留字function.开头。
函数说明
以类似于为过程定义的方式,我们可以定义一个函数规范,该规范将函数的接口提供给任何潜在的调用者。您将发现函数规范在以后的Ada编程工作中很有用。它类似于Pascal forward声明。再次注意,在这种情况下不需要函数规范,这里仅给出一个示例。
名为Square的函数需要一个参数,它倾向于调用Val,并且该参数的类型必须是INTEGER。它向主程序return一个INTEGER类型的值,因为它是在保留字返回之间给定的类型,并且在第13行的函数头中。
函数必须返回一个值,该值是通过在保留字return 后面加上要返回的值来返回的。这个返回必须在程序的可执行部分完成,如第15行所示。不能执行return语句而落在函数末尾是错误的。这样的运行时错误将通过引发异常Program_Error来报告,这将在本教程后面进行解释。
过程中能RETURN吗?
最好指出,也可以使用return语句从过程中返回,但是不能给定值,因为过程返回值的方式与函数不同。return语句可以是过程或函数中的任意位置,如果逻辑规定了从多个不同位置返回的可能性,则可以有多个返回。
函数调用将替换一个值
通过检查程序的可执行部分,我们发现变量Twelve 已被初始化为值12,并在第25行中用作函数Square的参数。这导致调用Square,其中12的值是平方,结果返回为144。就好像结果值144替换了Put过程调用中的函数调用Square(Twelve) ,并且显示值144。继续到第27行,变量Twelve(仍然包含12的值)和常量12被赋予返回24的和的函数Sum。这个值被赋给名为Sum的变量,并存储在第29行中。
函数可以定义为不带输入参数,在这种情况下,调用函数时不带参数。这种情况对于随机数生成器很有用,其中调用可以是X:=random;假设每次调用函数时都返回一个新的随机数。编译并执行这个程序,研究生成的输出。
更完整的例子
Example program ------> e_c08_p7.ada
-- Chapter 8 - Program 7 -- This is a program to return an odd square of a number. The result -- will have the sign of the original number, and the magnitude of -- the square of the original number. The entire program can be -- written in one line (i.e. - Result := Index * abs(Index); ), but -- the purpose of the program is to illustrate how to put various -- procedures and functions together to build a useable program. -- Various levels of nesting are illustrated along with several -- parameters being used in procedure calls. with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure OddSqre is Result : INTEGER; function Square_The_Number(Number_To_Square : in INTEGER) return INTEGER is begin return Number_To_Square * Number_To_Square; end Square_The_Number; procedure Square_And_Keep_Sign(Input_Value : in INTEGER; Funny_Result : out INTEGER) is procedure Do_A_Negative_Number(InVal : in INTEGER; OutVal : out INTEGER) is begin OutVal := -Square_The_Number(InVal); end Do_A_Negative_Number; begin if Input_Value < 0 then Do_A_Negative_Number(Input_Value, Funny_Result); return; elsif Input_Value = 0 then Funny_Result := 0; return; else Funny_Result := Square_The_Number(Input_Value); return; end if; end Square_And_Keep_Sign; begin for Index in -3..4 loop Square_And_Keep_Sign(Index, Result); Put("The Odd Square of"); Put(Index, 3); Put(" is"); Put(Result, 5); New_Line; end loop; end OddSqre; -- Result of execution -- The Odd Square of -3 is -9 -- The Odd Square of -2 is -4 -- The Odd Square of -1 is -1 -- The Odd Square of 0 is 0 -- The Odd Square of 1 is 1 -- The Odd Square of 2 is 4 -- The Odd Square of 3 is 9 -- The Odd Square of 4 is 16
检查名为e_uc08_up7.ada的程序,它是一个相当奇怪的程序,它计算整数类型变量的平方,但保持变量的符号。在这个程序中,奇数平方为9,奇数平方为-3是-9。它的真正目的是说明几个过程和一个函数交互。
名为OddSqre的主程序具有一个函数和一个嵌套在声明部分中的过程,这两个程序都传递了参数。名为Square_And_Keep_Sign 的嵌套过程在其声明部分中嵌套了另一个过程,名为Do_A_Negative_Number ,它调用在下一个更高级别声明的函数。
这个程序是一个可怕的例子,说明如何解决手头的问题,但它是几个交互子程序的极好例子,并且您可以花足够的时间来彻底了解它的作用。
对e_uc08_up7.ada的评论
这个程序演示了一些纯粹是编程风格的选项。第一个选项如第21行所示,我们可以选择使用构造Number_To_Square**2 ,而不是简单的乘法。任何一种形式都是正确的,所使用的形式都应反映当前问题的性质。第二种选择是,第36、39和42行包含了三个返回,而在 if语句结束后可以使用一个返回。这样做是为了说明使用中的多重回报。在某些情况下,使用多个返回而不是只使用一个返回,程序的逻辑更清晰。最重要的是,这是个人品味的问题。一定要编译并执行这个程序。
重载
Example program ------> e_c08_p8.ada
我们在本教程前面随便提到了重载,现在是时候通过检查名为e_uc08_up8.ada的程序来获得重载的一个好例子了。该程序包括两个函数和两个过程,所有四个子程序都有相同的名称。Ada系统能够识别实际参数列表中包含的类型和返回类型想要使用哪些子程序。在第45行中,我们调用一个函数,它是一个INTEGER 整数类型常量,它是一个2,我们将返回的值分配给一个整数类型变量。系统将查找一个名为Raise_To_Power的函数,该函数具有一个整数类形式参数,并在第11行到第15行中找到一个INTEGER 整数类型返回,因此它执行此函数。实际搜索将在编译时完成,因此效率不会因重载名称而以任何方式降低。
如果继续研究此程序,您将看到系统如何通过比较用作正式参数的类型和返回的类型来找到正确的子程序。多个用途使用相同的名称被称为重载子程序名,是Ada语言中的一个重要概念。
重载会给你带来麻烦
如果我们在这个示例程序中犯了一个错误,不经意地省略了第47行中的小数点,并将结果赋给整数类型变量,那么系统只会使用错误的函数并为我们生成无效的数据。如果我们有一个函数使用整数作为输入,使用浮点作为输出,则会发现更糟糕的问题,因为只有一个小错误可能导致错误的结果。因此,对不同的操作使用不同的子程序名称会对您有利,除非使用相同的名称会产生清晰的代码。
在我们使用的文本输出过程中,重载输出子程序以避免混淆是很有意义的。名称Put用于输出字符串、整数、枚举类型等,我们不会混淆。过载在某些情况下可能是一种优势,但不应仅仅因为过载可用就滥用它。一定要编译并执行这个程序。
如何将参数传递给子程序?
如果您了解向子程序传递参数和从子程序传递参数的方法,则偶尔可以通过仔细选择类型来提高效率。出于这个原因,下面给出了一些描述,尽管是粗略的描述;
1.标量类型的参数总是通过copy传递,当子程序返回时,新值会被复制回原始值。因此,在out或in-out参数的情况下,在子程序完成之前,中间值不会反映回原始值。
2.复合参数(数组和记录)可以使用上面定义的副本,也可以使用对原始参数的引用。在这种情况下,编译器编写器可以选择参数传递的方式。
3.更高级的类型(如任务类型或受保护类型)总是通过引用传递给原始类型。
一般来说,如何传递各种类型对您来说并不重要,但在某些情况下可能很重要。
编程练习
1.重写e_c05_p6.ada,在程序中进行温度转换。(Solution)
2.重写e_c05_p6.ada以在函数中进行温度转换。(Solution)
3.如本文所述,向程序e_c08_p8.ada添加一个函数,该函数使用整数作为输入并返回浮点型结果。删除第47行的小数点,以查看在我们真正打算用浮点数调用过程时是否调用了新函数(Solution)
---------------------------------------------------------------------------------------------------------------------------
原英文版出处:https://perso.telecom-paristech.fr/pautet/Ada95/a95list.htm
翻译(百度):博客园 一个默默的 *** 的人