ADA 95教程 高级特性 二进制输入输出
任何有用的计算机程序都必须有一种方法将数据输入其中进行操作,并有一种方法将结果输出给用户。在本教程的第1部分中,我们研究了如何在计算机中输入和输出文本,现在我们将了解如何在计算机中输入和输出二进制数据。大多数二进制输入和输出都是到文件或从文件中输出的,因为数据总是以机器可读的格式,而不是人类读者容易理解的格式。一个二进制数据块可以被传送到一个硬件设备,该硬件设备被设计用来响应该数据,或者一个数据块可以从一个硬件设备输入,该硬件设备报告某个进程。稍后我们将对此进行更多的讨论。
二进制数据输出
Example program ------> e_c24_p1.ada
-- Chapter 24 - Program 1 with Ada.Text_IO; use Ada.Text_IO; with Ada.Sequential_IO; procedure BiSeqOut is type MY_REC is record Age : INTEGER; Sex : CHARACTER; Initial : CHARACTER; end record; package Seq_IO is new Ada.Sequential_IO(MY_REC); use Seq_IO; Myself : MY_REC; My_Out_File : Seq_IO.FILE_TYPE; begin Create(My_Out_File, Out_File, "NAMEFILE.TXT"); Myself.Sex := 'M'; Myself.Initial := 'X'; for Index in 1..100 loop Myself.Age := Index; Write(My_Out_File, Myself); end loop; Close(My_Out_File); end BiSeqOut; -- Result of Execution -- (The only output is a binary file named NAMEFILE.TXT)
以输出二进制序列数据为例,检查名为e_c24_p1.ada的程序。Ada提供了5个数据输入/输出库包,这些包在ada95参考手册(ARM)中定义,并且要求每个Ada编译器都提供这些包。它们按包名列出如下:;
Ada.Text_IO -它用于文本输入/输出,并且始终处于顺序操作模式。任何文件都可以用于输入或输出,但不能同时用于两者(本教程的大部分时间都在使用此软件包,第14章对此进行了详细描述。)
Ada.Sequential_IO -它用于二进制输入/输出,并且始终处于顺序操作模式。任何文件都可以用于输入或输出,但不能同时用于两者。
Ada.Direct_IO -它用于二进制输入/输出,可用于顺序或随机操作模式。任何文件都可以用于输入、输出或输入和输出。
Ada.Streams.Stream_IO -这可用于二进制或文本输入/输出,但允许使用异构数据,而其他三种则要求所有数据的类型相同。对于任何给定的文件。如果是Ada.Text_IO,数据必须全部为字符类型,因此也必须为单一类型。
Ada.Text_IO.Text_Streams -这允许将文本文件用于流输入/输出,以便可以将其与同一文件中的二进制数据混合。
有关这些包的定义,您应该参考编译器文档或ARM。所有定义见附件A。花一点时间研究每个包可用的过程和功能,以熟悉每个包的功能。学习本教程到目前为止所获得的知识应该能够使您理解这五个包的大部分规范。
名为Low_Level_IO的包
您的编译器可能有另一个名为Ada.Low_Level_IO输入输出包. 这不是ARM所需要的,但是如果它存在的话,它将被用于特定实现中与机器相关的输入/输出编程。如果您的编译器中有它,那么说明如何使用它的说明将包含在您的文档中,并且由于每个编译器都有完全不同的说明,因此这里不会试图解释它的用法。
返回e_c24_p1.ada
虽然这个程序做的很少,但要输出到二进制文件,必须执行几个步骤。它们将被按顺序排列,所以请紧跟其后。首先,我们必须告诉系统我们希望使用外部包Ada.Sequential_IO,我们在第4行使用with子句。接下来,我们定义一个记录类型来说明一种可以输出到文件的数据,并在第18行声明一个记录类型的变量。
为了使用sequential input/output package,我们必须实例化它的一个副本,因为它是一个通用包,不能直接使用。我们在第15行中这样做,使用希望输出到文件的数据类型,然后在第16行中添加use子句以使新包随时可用。我们需要一个内部文件名,并在第19行定义它,但有一个小困难,因为我们有一个重载文件类型可用。我们已经做了Ada.Text_IO 可以用来说明这个问题,尽管我们在这个程序中没有使用它,因为它包含一个名为FILE_TYPE的类型。我们实例化的名为Seq_IO 的包还包含一个名为FILE_TYPE的类型,我们希望使用这个类型。为了告诉系统我们想要哪一种,我们必须使用扩展命名符号来区分这两种类型。这在示例程序的第19行中完成。有了这些步骤,我们就可以开始程序的可执行部分了。
与我们之前研究的文本文件类似,我们在第23行中创建文件,在本例中使用mode Out_File,因为我们希望写入该文件。您还记得,这还将文件的内部名称与我们选择的外部名称联系起来,NAMEFILE.TXT,它必须遵循特定Ada编译器和操作系统的约定。如果您的操作系统有很大不同,则可能需要更改此名称。我们终于准备好实际使用输出文件了,所以我们将一些无意义的数据加载到第25行和第26行中声明的record变量中,然后进入一个循环,在这个循环中我们将更改记录的Age字段,以便有一些不同的数据写入文件。当我们在另一个示例程序中从这个文件读取数据时,这将使它更有用。
将二进制数据写入文件
实际上,我们将数据写入第30行中的文件,在该行中,我们使用过程write,它是我们前面命名为Seq_IO的实例化包的一部分,但是由于我们已经在use子句中定义了Seq_IO,因此我们不需要将限定符“点”添加到过程名称中。我们提到内部文件名是为了告诉系统我们希望写入哪个文件,以及我们希望输出哪个变量。要输出的变量必须是为其实例化包的类型,从而导致输出到任何给定文件的所有二进制记录必须是同一类型的规则。即使它们必须是同一类型的,也可以是具有不同变体的变体记录的记录。
在写入100条二进制记录之后,我们按照第33行所示的方式关闭文件,程序就完成了。
在最后几段中,我们讨论了很多领域,但是在接下来的几个示例程序中,这是我们需要的有用信息,当然,在我们希望实际使用二进制文件的任何时候,我们都需要它。应该很清楚,我们可以打开几个文件,每个文件存储不同的数据类型,并以任何顺序写入,前提是我们为每个文件写入了正确的数据类型。
生成的文件名为NAMEFILE.TXT,将用于说明接下来两个程序中的二进制读取,因此编译和执行此程序是非常必要的。运行它之后,您将找到名为NAMEFILE.TXT 在系统的默认目录中,如果您尝试使用文本编辑器查看它,它将具有一些非常奇怪的字符,因为它是一个二进制文件。但是,您会注意到,由于写入其中的数据的性质,其中大部分内容是可读的。
读取二进制文件
Example program ------> e_c24_p2.ada
-- Chapter 24 - Program 2 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; with Ada.Sequential_IO; procedure BiSeqIn is type MY_REC is record Age : INTEGER; Sex : CHARACTER; Initial : CHARACTER; end record; package Seq_IO is new Ada.Sequential_IO(MY_REC); use Seq_IO; Myself : MY_REC; My_In_File : Seq_IO.FILE_TYPE; begin Open(My_In_File, In_File, "NAMEFILE.TXT"); for Index in 1..100 loop Read(My_In_File, Myself); if Myself.Age >= 82 then Put("Record number"); Put(Myself.Age, 4); Put(" "); Put(Myself.Sex); Put(" "); Put(Myself.Initial); New_Line; end if; end loop; Close(My_In_File); end BiSeqIn; -- Result of Execution -- Record number 82 M X -- Record number 83 M X -- Record number 84 M X -- Record number 85 M X -- Record number 86 M X -- Record number 87 M X -- Record number 88 M X -- Record number 89 M X -- Record number 90 M X -- Record number 91 M X -- Record number 92 M X -- Record number 93 M X -- Record number 94 M X -- Record number 95 M X -- Record number 96 M X -- Record number 97 M X -- Record number 98 M X -- Record number 99 M X -- Record number 100 M X
名为e_c24_p2.ada的示例程序演示了如何以顺序模式读取二进制文件。除了包含Ada.Integer_Text_IO 在第2行和第3行中打包,以便以后使用。
可执行部分的唯一区别是使用Seq_O包中的Open过程,它使用文件打开的in_File模式。我们现在可以从上一个程序中写入的文件中读取,但不能从这个程序中写入。我们执行一个循环100次,每次通过循环从二进制文件中读取一条记录。记录定义与用于写入二进制文件的记录相同。如果记录在结构或数据类型上不同,则在读取时可能会得到任何结果,并且数据可能看起来与写入的原始数据没有任何关系,因为字节将混合在一起。
您可能想知道,如果我们请求系统打开的文件在我们预期的位置不可用,会发生什么情况。根据spirit of Ada,你可能会猜到一个例外会被提出,你将是正确的。如果文件不可打开,则会引发异常Name_Error作为一个指示符。包I IO_Exceptions 中定义了八个异常异常,这些异常是针对各种错误引发的。
假设文件确实正确打开了,所有100个元素都被读入,并且显示年龄字段大于或等于82的元素,以说明数据确实被写入了。最后,关闭二进制文件并终止程序。
便携性呢?
假设您使用一个Ada编译器编写二进制文件,并尝试使用另一个编译器读取它。由于每个实现者都可以自由地定义各种类型的内部位模式,以适合其特定的编译器,因此它是否能够工作还不确定。使用如图中所示的简单字段将带来很好的可移植性,但使用更复杂的记录或数组几乎肯定会导致不兼容问题。
解决这个问题的方法是用写文件的编译器读取文件。当然,一个以文本格式编写的文件,使用Ada.Text_IO,是便携式的,可以用不同的系统读取。
编译并执行这个程序,验证数据是否真的得到了预期的输出。如果没有编译并执行上一个示例程序,则不会生成名为NAMEFILE.TXT,则无法成功执行此程序。
随机输入输出
Example program ------> e_c24_p3.ada
-- Chapter 24 - Program 3 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; with Ada.Direct_IO; procedure BiRandIO is type MY_REC is record Age : INTEGER; Sex : CHARACTER; Initial : CHARACTER; end record; package Ran_IO is new Ada.Direct_IO(MY_REC); use Ran_IO; Myself : MY_REC; My_In_Out_File : Ran_IO.FILE_TYPE; procedure Display_Record(In_Rec : MY_REC) is begin Put("Record number"); Put(In_Rec.Age, 4); Put(" "); Put(In_Rec.Sex); Put(" "); Put(In_Rec.Initial); New_Line; end Display_Record; begin Open(My_In_Out_File, InOut_File, "NAMEFILE.TXT"); Read(My_In_Out_File, Myself, 37); Display_Record(Myself); Read(My_In_Out_File, Myself, 25); Display_Record(Myself); Read(My_In_Out_File, Myself); Display_Record(Myself); New_Line; Myself.Age := 33; Myself.Sex := 'F'; Myself.Initial := 'Z'; Write(My_In_Out_File, Myself, 91); Write(My_In_Out_File, Myself, 96); Write(My_In_Out_File, Myself); Set_Index(My_In_Out_File, 88); while not End_Of_File(My_In_Out_File) loop Read(My_In_Out_File, Myself); Display_Record(Myself); end loop; Close(My_In_Out_File); end BiRandIO; -- Result of Execution -- Record number 37 M X -- Record number 25 M X -- Record number 26 M X -- Record number 88 M X -- Record number 89 M X -- Record number 90 M X -- Record number 33 F Z -- Record number 92 M X -- Record number 93 M X -- Record number 94 M X -- Record number 95 M X -- Record number 33 F Z -- Record number 33 F Z -- Record number 98 M X -- Record number 99 M X -- Record number 100 M X
检查名为e_c24_p3.ada的文件以获取随机输入和输出的示例。随机文件访问意味着我们可以将数据输出到文件中,或者像读取数组一样从文件中读取数据。文件的元素不必按顺序访问。我们将在这个示例程序中说明所有这些。
这个程序的声明部分对您来说应该很熟悉,因为它与上两个示例程序几乎相同。最大的区别在于Ada.Direct_IO 包而不是Ada.Sequential_IO 包裹。我们实例化一个名为Ran_IO的副本,并使用它声明内部文件名My_In_Out_File。接下来,作为程序声明部分的一部分,我们声明一个函数,该函数将用于输出一些格式良好的数据。
从随机文件中读取
在对该文件执行任何操作之前,我们必须打开它,并且由于我们打算对该文件进行读写,因此我们使用InOut_File 模式打开它。当然,我们使用第19行中定义的内部文件名,以及已经写入的外部文件名。在第36行中,我们使用Read过程,从打开的文件中读取数据,然后将数据读入名为Myself的记录变量。这里真正新的是使用数字37作为过程调用的第三个实际参数。这告诉系统我们希望从指定的文件中读取第37条记录。我们使用Display_Record过程来显示读取的记录,然后告诉系统我们希望读取25号记录,并显示它。在第40行中,我们不告诉系统我们要显式读取哪个记录,因此它返回下一个记录,即我们显示的第26个记录。
写入随机文件
我们在第44行到第46行用无意义数据填充记录变量的三个字段,并将修改后的记录写入记录91、96和97。我们写入记录97是因为我们没有指定记录,系统将默认为下一个连续的记录编号。在第51行中,我们用值88调用过程Set_Index,正如您可能猜到的那样,它将记录指针设置为88,这是下一个记录,如果没有记录编号,则默认情况下将读取该记录。第52到55行中的循环读取并显示从88到最后一行的所有记录,因为我们一直在读取,直到找到文件的结尾。当您编译并执行这个程序时,您将看到我们确实修改了编号为91、96和97的记录。
您会发现二进制输入/输出对于临时存储大量数据非常有用,但是您必须记住,使用此技术编写的任何数据可能会被其他系统读取,也可能不会被其他系统读取。因此,除了要由另一个程序读取的数据外,不应使用二进制输出,该程序使用与数据生成器相同的Ada编译器编译。一定要编译并执行这个程序。
使用异构数据
Example program ------> e_c24_p4.ada
-- Chapter 24 - Program 4 with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Streams.Stream_IO; use Ada.Text_IO, Ada.Integer_Text_IO, Ada.Streams.Stream_IO; procedure Stream is My_File : Ada.Streams.Stream_IO.FILE_TYPE; My_File_Access : Ada.Streams.Stream_IO.STREAM_ACCESS; type RESULT is (WIN, LOSE, TIE, FORFEIT); type BOX is record Length : INTEGER; Width : INTEGER; Height : INTEGER; end record; Big_Box, Small_Box : BOX := (7, 8, 9); Football, Baseball : RESULT := TIE; Dogs, Pigs, Cats : INTEGER := 27; Animal : INTEGER := 100; begin -- Create a stream to a file in the output mode to write to. Ada.Streams.Stream_IO.Create(My_File, Out_File, "funny.txt"); My_File_Access := Ada.Streams.Stream_IO.Stream(My_File); INTEGER'Write(My_File_Access, Dogs); BOX'Write(My_File_Access, Big_Box); BOX'Write(My_File_Access, Small_Box); RESULT'Write(My_File_Access, Baseball); INTEGER'Write(My_File_Access, Pigs); RESULT'Write(My_File_Access, Football); INTEGER'Write(My_File_Access, Cats); Ada.Streams.Stream_IO.Close(My_File); -- Open the stream in the input mode to read from it. Open(My_File, In_File, "funny.txt"); My_File_Access := Stream(My_File); INTEGER'Read(My_File_Access, Animal); BOX'Read(My_File_Access, Small_Box); -- The others can be read here too Put("Animal has a value of "); Put(Animal, 6); New_Line; Close(My_File); end Stream; -- Result of execution -- Animal has a value of 27
检查示例程序e_c24_p4.ada,其中包含使用ADA95中添加的streams功能将多种类型的数据写入单个文件的示例。名为Ada.Streams.Stream_IO 包含所需的实体,因此它包含在第3行和第4行的上下文子句中,我们声明了一个文件对象和一个访问类型,以便与该文件一起使用。请注意,即使包包含在use子句中,第8行中也需要扩展命名表示法,因为在Ada.Text_IO,系统不知道根据可用信息选择哪个。扩展命名符号在第9行中不是必需的,但是给出它是为了清楚地表明这两种类型紧密地协同工作。
在第11行到第23行中,我们定义了几个类型和各种变量,以便在程序的可执行部分中使用。它们都是指定的初始值,没有意义,但提供所有实体的已知值。
在第27行中,我们创建了一个名为my_File的流,它将允许我们写入名为funny.txt“在默认目录中。在第28行中,我们使名为My_File_access的访问变量访问刚刚打开的文件。在第30行到第36行中,我们以任意顺序向同一个文件写入七个不同的变量,它们代表三种不同的类型。这种书写方法似乎非常奇怪,而且确实如此,因为它使用类型的属性来进行实际的书写。文件在第38行关闭,在第41行打开相同的文件进行读取。
前两个变量从第44行和第45行的文件中读取,注意按正确的顺序读取,并为每个变量使用正确的类型。为了说明数据读取正确,动物的值显示在监视器上以供检查。第52行再次关闭文件,程序完成。
这个非常有限的程序仅说明了ADA95提供的streams功能的一种用途。这个包还提供了随机输入/输出,而不仅仅是顺序的,并且流可以被定向到内存中而不是文件中,或者直接从内存中读取。
编程练习
修改e_c24_p1.ada以将相同的数据写入两个不同的文件,除非年龄在50到60岁之间。在此范围内,应更改其中一个文件的一个字符。(Solution)
修改e_c24_p2.ada以读取练习1输出的两个文件,并列出两个文件中的差异。(Solution)
将e_c24_p1.ada and e_c24_p2.ada组合在一起,使文件写入一个循环,然后读回并在连续循环中显示。(Solution)
---------------------------------------------------------------------------------------------------------------------------
原英文版出处:https://perso.telecom-paristech.fr/pautet/Ada95/a95list.htm
翻译(百度):博客园 一个默默的 *** 的人