ADA 95教程 高级特性 高级记录主题
带有判别式的记录
Example program ------> e_c20_p1.ada
-- Chapter 20 - Program 1 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Discrim1 is type SQUARE is array(INTEGER range <>, INTEGER range <>) of INTEGER; type LINEAR_TYPE is array(INTEGER range <>) of POSITIVE; type STUFF(List_Size : POSITIVE) is record Matrix : SQUARE(1..List_Size, 1..List_Size); Elements : INTEGER := List_Size * List_Size; Linear : LINEAR_TYPE(1..List_Size); Number : INTEGER := List_Size; end record; type ANOTHER_STUFF is new STUFF; subtype STUFF_5 is STUFF(5); Data_Store : STUFF(5); Big_Store : STUFF(12); Extra_Store : ANOTHER_STUFF(5); More_Store : STUFF(5); Five_Store : STUFF_5; Name_Store : STUFF(List_Size => 5); begin for Index1 in Data_Store.Matrix'RANGE(1) loop Data_Store.Linear(Index1) := Index1; for Index2 in Data_Store.Matrix'RANGE(2) loop Data_Store.Matrix(Index1, Index2) := Index1 * Index2; end loop; end loop; Five_Store := Data_Store; More_Store := Five_Store; Put("The number of elements in More_Store.Matrix is"); Put(More_Store.Elements, 5); New_Line; end Discrim1; -- Result of execution -- The number of elements in More_Store.Matrix is 25
检查名为e_c20_p1.ada的文件,以获得第一个带有判别式的记录示例。在我们得到判别式和它的作用之前,需要一点时间和研究,但是我们会一步一步地去做。
我们首先在声明部分的第7行定义一个无约束的二维数组类型,在第10行定义一个无约束的一维数组类型。接下来我们定义一个记录类型,从第12行开始,使用一个鉴别器,鉴别器是一个名为List_Size的变量,其类型为POSITIVE。记录由四个字段组成,每个字段部分由判别式定义。名为Matrix的变量是一个正方形数组,其大小由判别式的值给定,而Elements 则初始化为判别式的平方。同样,其他两个字段被定义为判别式的函数。请记住,判别式还没有值,它只是用作记录模式的一部分。
为了以后使用,我们在第20行定义了一个派生类型,在第22行定义了一个子类型。子类型被定义为STUFF类型,但判别式固定为5。稍后我们将对这两种类型有更多的介绍。
我们现在需要定义一些数据
在第24行中,我们声明一个名为Data_Store的变量为STUFF类型,判别集等于5。因此,作为名为Data_Store的记录的一部分的矩阵变量有两个下标,并且都覆盖1到5的范围。名为Elements的变量将被初始化为5的平方,其他字段也将被定义。变量Big_Store将具有更大的数组,其子字段Elements的值将初始化为12的平方。由于这两个变量有不同的元素数,它们不能进行赋值兼容,也不能进行相等或不相等的比较。
变量Data_Store被声明为另一个类型的STUFF ,判别式为5,但由于类型不同,此变量与名为Data_Store的第一个变量的赋值不兼容。More_Store 被声明为类型STUFF,判别式为5,因此它与Data_Store的赋值兼容。因为它是STUFF的一个子类型,并且它的判别式是5, Five_Store,正如在子类型声明中定义的,所以它的赋值与数据存储兼容。最后,应该清楚的是,最后一个例子Name_Store与Data_Store是赋值兼容的,因为它唯一的区别是使用了命名的判别选择方法。这是一个提示,提示您可以使用额外的鉴别器,并且实际上对可作为被鉴别器记录类型的一部分的数量没有限制。我们很快就会有一个示例程序,它有三个判别式。
谁与谁相容?
如前所述,名为Data_Store, More_Store, Five_Store, and Name_Store,的变量都是同一类型的,可以自由地分配给彼此。他们也可以互相比较平等或不平等。其他变量的类型不同,不能作为一个完整的记录分配给彼此,也不能比较是否相等或不相等。
判别式一旦声明,就被认为是一个常数,不能修改。变量数据存储区的判别式在程序中作为Data_Store.List_Size 为了阅读它。程序的可执行部分应该是清晰的。其中一个声明的矩阵变量是使用循环限制的RANGE属性赋值的。然后将整个记录分配给一些额外的记录变量,并显示一个数据点作为示例。
当你理解了这个程序,编译并执行它来证明你自己它是如图所示工作的。注意,像往常一样,记录的元素不能是匿名类型,但必须命名。
我们如何使用新记录?
Example program ------> e_c20_p2.ada
-- Chapter 20 - Program 2 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Discrim2 is type SQUARE is array(INTEGER range <>, INTEGER range <>) of INTEGER; type LINEAR_TYPE is array(INTEGER range <>) of POSITIVE; type STUFF(List_Size : POSITIVE) is record Matrix : SQUARE(1..List_Size, 1..List_Size); Elements : INTEGER := List_Size * List_Size; Linear : LINEAR_TYPE(1..List_Size); Number : INTEGER := List_Size; end record; Data_Store : STUFF(5); Big_Store : STUFF(12); function Add_Elements(In_Array : STUFF) return INTEGER is Total : INTEGER := 0; begin for Index1 in In_Array.Matrix'RANGE(1) loop for Index2 in In_Array.Matrix'RANGE(2) loop Total := Total + In_Array.Matrix(Index1, Index2); end loop; end loop; return Total; end Add_Elements; procedure Set_To_Ones(Work_Array : in out STUFF) is begin for Index1 in Work_Array.Matrix'RANGE(1) loop for Index2 in Work_Array.Matrix'RANGE(2) loop Work_Array.Matrix(Index1, Index2) := 1; end loop; end loop; end Set_To_Ones; begin for Index1 in 1..Data_Store.List_Size loop Data_Store.Linear(Index1) := Index1; for Index2 in 1..Data_Store.List_Size loop Data_Store.Matrix(Index1, Index2) := Index1 * Index2; end loop; end loop; Set_To_Ones(Big_Store); Put("The total of Data_Store is"); Put(Add_Elements(Data_Store), 5); New_Line; Put("The total of Big_Store is "); Put(Add_Elements(Big_Store), 5); New_Line; end Discrim2; -- Result of execution -- The total of Data_Store is 225 -- The total of Big_Store is 144
检查名为e_c20_p2.ada 的示例程序,了解如何使用已识别记录的几个示例。类型声明与上一个程序相同,但这次只声明了两个记录,Data_Store and Big_Store,这两个记录的类型不同,因为它们具有不同的判别式。
程序的声明部分在该程序中添加了函数声明和过程声明。如果仔细观察,您会发现两个子程序中用于形式变量的类型都是记录类型STUFF,但是没有为这两个子程序定义判别式。这些是无约束的记录,为子程序的使用增加了灵活性。子程序中的循环使用的限制取决于调用程序中定义的实际类型的限制,因此子程序可以用于STUFF类型的任何记录变量,而不考虑判别式的值。
无约束子程序的使用
第45行到第50行中的嵌套循环指定数组变量数据中包含的Data_Store.Matrix 一个乘法表以备日后使用。在第52行中,我们调用程序Set_To_Ones with the record Big_Store将其所有Matrix 值设置为1。最后,我们通过为每个记录调用一次Add_Elements 函数来显示所有元素的总和。尽管这些记录实际上是不同类型的,但是由于函数本身的灵活性,函数可以正确地处理这两种类型。请注意,即使记录在每个子程序中都是不受约束的,但在调用子程序时它也会受到约束,因为判别式被约束为调用中实际参数的值。
一定要编译和执行这个程序,并研究输出,直到你确定你明白结果。
可变判别式
Example program ------> e_c20_p3.ada
-- Chapter 20 - Program 3 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Discrim3 is type SQUARE is array(INTEGER range <>, INTEGER range <>) of INTEGER; type LINEAR_TYPE is array(INTEGER range <>) of POSITIVE; subtype SMALL_POS is POSITIVE range 1..20; type STUFF(List_Size : SMALL_POS := 2) is record Matrix : SQUARE(1..List_Size, 1..List_Size); Elements : INTEGER := List_Size * List_Size; Linear : LINEAR_TYPE(1..List_Size); Number : INTEGER := List_Size; end record; Data_Store : STUFF(5); Big_Store : STUFF(12); Var_Store : STUFF; function Add_Elements(In_Array : STUFF) return INTEGER is Total : INTEGER := 0; begin for Index1 in In_Array.Matrix'RANGE(1) loop for Index2 in In_Array.Matrix'RANGE(2) loop Total := Total + In_Array.Matrix(Index1, Index2); end loop; end loop; return Total; end Add_Elements; procedure Set_To_Ones(Work_Array : in out STUFF) is begin for Index1 in Work_Array.Matrix'RANGE(1) loop for Index2 in Work_Array.Matrix'RANGE(2) loop Work_Array.Matrix(Index1, Index2) := 1; end loop; end loop; end Set_To_Ones; begin for Index1 in 1..Data_Store.List_Size loop Data_Store.Linear(Index1) := Index1; for Index2 in 1..Data_Store.List_Size loop Data_Store.Matrix(Index1, Index2) := Index1 * Index2; end loop; end loop; Var_Store := Data_Store; Put("The total of Var_Store is "); Put(Add_Elements(Var_Store), 4); New_Line; Set_To_Ones(Var_Store); Put("The total of Var_Store is "); Put(Add_Elements(Var_Store), 4); New_Line; Set_To_Ones(Big_Store); Var_Store := Big_Store; Put("The total of Var_Store is "); Put(Add_Elements(Var_Store), 4); New_Line; end Discrim3; -- Result of execution -- The total of Var_Store is 225 -- The total of Var_Store is 25 -- The total of Var_Store is 144
检查名为e_c20_p3.ada的程序,查看可以动态更改的判别式示例。这个程序与上一个示例程序几乎相同,但是有两个非常小的变化。在第14行中,鉴别器被初始化为值2,如果变量声明中没有给出任何值,则该值将用于鉴别器。第二个变化如第24行所示,其中变量Var_Store被声明为STUFF类型,并默认为其判别式的初始化值2。名为Var_Store的变量还获得了另一个属性,即在程序执行期间将其鉴别器更改为任何合法值的能力。
Data_Store和Big_Store这两个变量的判别式分别固定为5和12,在程序执行过程中不能改变。
如何改变判别式?
只有通过在一条语句中更改整个记录才能更改判别式,如第55行所示,其中名为Data_Store的整个记录(判别式为5)被分配给变量Var_Store。然后,变量Var_Store可以在任何合法的地方使用判别式5的记录,如第56行到第62行所示。注意,在第55行赋值之前,变量Var_Store可以用作一个记录,其判别式设置为2,因为这是默认值。
在第66行中,变量Var_Store被分配了Big_Store的整个记录,从而有效地将其更改为具有12个判别式的记录变量。编译和执行此程序后,可以看到的输出证明了它发生了更改。请注意,Big_Store中包含的所有值都被复制到Var_Store中。一定要编译并执行这个程序。
多重判别式
Example program ------> e_c20_p4.ada
-- Chapter 20 - Program 4 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Discrim4 is type SQUARE is array(INTEGER range <>, INTEGER range <>) of INTEGER; type LINEAR_TYPE is array(INTEGER range <>) of POSITIVE; subtype SMALL_POS is POSITIVE range 1..20; type STUFF(List_Length : SMALL_POS := 3; List_Width : SMALL_POS := 5; Linear_Size : SMALL_POS := 7) is record Matrix : SQUARE(1..List_Length, 1..List_Width); Elements : INTEGER := List_Length * List_Width; Linear : LINEAR_TYPE(1..Linear_Size); Number : INTEGER := Linear_Size; end record; Data_Store : STUFF(5, 5, 5); Big_Store : STUFF(12, 12, 12); Different : STUFF(5, 7, 4); More_Store : STUFF(Linear_Size => 15, List_Length => 6, List_Width => 2); Variable : STUFF; function Add_Elements(In_Array : STUFF) return INTEGER is Total : INTEGER := 0; begin for Index1 in In_Array.Matrix'RANGE(1) loop for Index2 in In_Array.Matrix'RANGE(2) loop Total := Total + In_Array.Matrix(Index1, Index2); end loop; end loop; return Total; end Add_Elements; procedure Set_To_Ones(Work_Array : in out STUFF) is begin for Index1 in Work_Array.Matrix'RANGE(1) loop for Index2 in Work_Array.Matrix'RANGE(2) loop Work_Array.Matrix(Index1, Index2) := 1; end loop; end loop; end Set_To_Ones; begin for Index1 in 1..Data_Store.List_Length loop Data_Store.Linear(Index1) := Index1; for Index2 in 1..Data_Store.List_Width loop Data_Store.Matrix(Index1, Index2) := Index1 * Index2; end loop; end loop; Variable := Data_Store; Put("The total of Variable is"); Put(Add_Elements(Variable), 6); New_Line; Variable := More_Store; Set_To_Ones(Big_Store); Set_To_Ones(Different); Set_To_Ones(More_Store); Set_To_Ones(Variable); Put("The total of Variable is"); Put(Add_Elements(Variable), 6); New_Line; Variable := Different; Put("The total of Variable is"); Put(Add_Elements(Variable), 6); New_Line; end Discrim4; -- Result of execution -- The total of Variable is 225 -- The total of Variable is 12 -- The total of Variable is 35
通过检查名为e_c20_p4.ada的示例程序,您可以看到一个如何在记录类型中使用多重鉴别器的示例。第一个差异出现在第14行到第16行,其中为记录类型定义了三个鉴别器,下一个较大的差异出现在第24行到第30行,其中用该类型声明了5个变量,说明了本章中描述的各种鉴别器初始化。
由于我们已经在最后几个示例程序中研究了所有这些,因此我们将不详细说明它们,只需提及第30行中定义的变量,即variable,可以为其他任何变量赋值。然后,它将拥有分配变量所拥有的全部判别式集,并将存储在源记录中的所有当前值分配给它。这与上一个程序中的说明完全相同。
一定要在你理解了这个程序要传达给你的概念之后编译并运行它。
变体记录
Example program ------> e_c20_p5.ada
-- Chapter 20 - Program 5 procedure Variant1 is type POWER is (GAS, STEAM, DIESEL, NONE); type VEHICLE (Engine : POWER) is record Model_Year : INTEGER range 1888..1992; Wheels : INTEGER range 2..18; case Engine is when GAS => Cylinders : INTEGER range 1..16; when STEAM => Boiler_Size : INTEGER range 5..22; Coal_Burner : BOOLEAN; when DIESEL => Fuel_Inject : BOOLEAN; when NONE => Speeds : INTEGER range 1..15; end case; end record; Ford : VEHICLE(GAS); Truck : VEHICLE(DIESEL); Schwinn : VEHICLE(NONE); Stanley : VEHICLE(STEAM); begin Ford.Model_Year := 1956; -- Component assignment Ford.Wheels := 4; Ford.Cylinders := 8; Ford := (GAS, 1956, 4, 8); -- Positional aggregate assignment -- Named aggregate assignment Ford := (Model_Year => 1956, Cylinders => 8, Engine => GAS, Wheels => 4); Stanley.Model_Year := 1908; Stanley.Wheels := 4; Stanley.Boiler_Size := 21; Stanley.Coal_Burner := FALSE; -- Mixed aggregate assignment Stanley := (STEAM, 1908, 4, Coal_Burner => FALSE, Boiler_Size => 21); Schwinn.Speeds := 10; Schwinn.Wheels := 2; Schwinn.Model_Year := 1985; Truck.Model_Year := 1966; Truck.Wheels := 18; Truck.Fuel_Inject := TRUE; end Variant1; -- Result of Execution -- (No results are output.)
检查名为e_c20_p5.ada的文件,以获取我们的第一个变量记录示例。根据定义,变量记录必须有一个鉴别器,因为鉴别器将定义每个记录变量将由哪些变量组成。如果你是一个Pascal程序员,你会发现variant记录比Pascal更加严格,你可以随意更改variant。如果您记得Ada是一种非常强类型的语言,并且为了检测程序员无意中引入的错误而不接受对其定义标准的任何偏离,那么您将欣赏简洁的定义和限制。
我们的示例记录的鉴别器在第5行中声明为一个具有四个允许值的枚举类型。声明一个名为Engine的变量,其类型为POWER,将用作判别式,并声明两个变量作为记录的第一部分。记录的变量部分从第11行开始,保留字大小写后跟判别变量的名称,在本例中是引擎。这四个变量的声明方式与使用变量声明代替可执行语句的普通case语句基本相同。必须为判别式的每个可能值列出一个子句,并且可以为每个值定义任意数量的变量,包括无变量。如果没有为一个case声明变量,那么保留字null将用于向编译器表明您真正的意思是在那里不包含任何变量。
如果记录中有一个变量,那么它必须是记录的最后一部分,并且首先声明所有公共变量。记录的一个或多个变体部分可以有变体部分本身,前提是它是变体部分的最后一部分。嵌套没有定义的限制。
如何使用变体记录?
在第20行中,我们将变量Ford声明为类型VEHICLE的记录,并将其约束为记录的GAS变量。因为Ford变量被约束为GAS变量,所以它只能使用声明为GAS变量一部分的变量,当然还有两个公共变量。将数据赋给在记录类型的其他变体中声明的Ford变量的字段是非法的。而且,由于变量Ford被赋予了GAS的判别值,所以它是一个常数,永远不能改变。同样地,名为Truck的变量将始终是带有variant DIESEL的VEHICLE类型的记录,因为这就是它声明的内容。第22行和第23行中声明的其他两个变量也是如此。我们将在下一个程序中看到,以这样一种方式声明一个变量是可能的,即在程序执行期间可以动态地将它修改为任何变量。
我们如何给变量赋值?
第27行到第29行您应该很熟悉,因为这是用于为没有变量的记录赋值的方法,我们在前面研究过。第31行说明了使用位置聚合表示法的值赋值,第34行说明了使用命名聚合表示法的值赋值。在这两种情况下,所有四个字段都必须命名,即使某些字段没有更改。即使是不变的判别式也必须包含在集合中,这似乎有点麻烦,因为它是一个常数。这最后两条规则的原因很可能是有根据的,并且与编写编译器的容易程度有关,这当然不是一项小工作。
第37行到第40行中的语句将值赋给名为Stanley的记录变量的每个子字段,第42行说明了混合聚合赋值,其中提到了所有五个变量,尽管判别式是常数。最后,Schwinn和Truck变量在第45行到第51行中赋值。编译并执行这个程序,以确保编译器确实能正确编译它。
可变记录
Example program ------> e_c20_p6.ada
-- Chapter 20 - Program 6 procedure Variant2 is type POWER is (GAS, STEAM, DIESEL, NONE); type VEHICLE (Engine : POWER := NONE) is record Model_Year : INTEGER range 1888..1992; Wheels : INTEGER range 2..18; case Engine is when GAS => Cylinders : INTEGER range 1..16; when STEAM => Boiler_Size : INTEGER range 5..22; Coal_Burner : BOOLEAN; when DIESEL => Fuel_Inject : BOOLEAN; when NONE => Speeds : INTEGER range 1..15; end case; end record; Ford, Truck, Schwinn : VEHICLE; Stanley : VEHICLE(STEAM); begin Ford := (GAS, 1956, 4, 8); Ford := (DIESEL, 1985, Fuel_Inject => TRUE, Wheels => 8); Truck := (DIESEL, 1966, 18, TRUE); Truck.Model_Year := 1968; Truck.Fuel_Inject := FALSE; Stanley.Model_Year := 1908; -- This is constant as STEAM Stanley.Wheels := 4; Stanley.Boiler_Size := 21; Stanley.Coal_Burner := FALSE; Schwinn.Speeds := 10; -- This defaults to NONE Schwinn.Wheels := 2; Schwinn.Model_Year := 1985; end Variant2; -- Result of Execution -- (No output when executed)
检查名为e_c20_p6.ada的程序,以获取变量记录的示例,在该示例中,我们可以在程序执行期间动态更改变量。这里的一个主要区别是,判别式默认为第7行中列出的值,即NONE的值。如果没有声明变量,则在声明中将其默认为NONE,如第20行中所做的那样,其中使用判别式的默认值声明了三个变量记录。变量Stanley再次声明为variant STEAM,并且在整个程序执行过程中保持不变,因为使用判别值声明的任何变量都不能动态更改其判别值,而是一个常量,尽管可以更改记录的各个元素。
现在使用一些新的变量
在第25行中,变量Ford被分配了数据,使得它是气体变量,使用位置聚合符号,并且在下一个语句中被重新定义为柴油变量,使用混合聚合符号。这样做是为了向您说明,如果变量是用默认变量声明的,那么可以更改变量的变量。在第28行中,变量Truck被分配了带有位置聚合符号的DIESEL变量,并且在接下来的两个语句中更改了其中两个字段。
任何字段都可以单独更改,但区别变量除外,它只能通过使用列出所有值的聚合来更改。请记住,这是这个构造的加重部分,每个记录聚合中都必须提到所有字段。即使您可以更改记录的各个值,也只能使用作为当前变量一部分的那些变量。Pascal允许您使用其他变量的变量名,但是Ada不允许这样做。但是,您可以使用位置、命名或混合聚合表示法。在你理解了这些概念之后,一定要编译并执行这个程序。
运算符重载
Example program ------> e_c20_p7.ada
-- Chapter 20 - Program 7 with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Infix is type THREE_INTS is record Length, Height, Width : INTEGER; end record; Big, Medium, Sum : THREE_INTS; function "+"(Record1, Record2 : THREE_INTS) return THREE_INTS is Temp : THREE_INTS; begin Temp.Length := Record1.Length + Record2.Length; Temp.Height := Record1.Height + Record2.Height; Temp.Width := Record1.Width + Record2.Width; return Temp; end "+"; begin Big.Length := 10 + 2; Big.Height := 22; Big.Width := 17; Medium.Length := 5; Medium.Height := 7; Medium.Width := 4; Sum := Big + Medium; Put("The sum is"); Put(Sum.Length, 4); Put(Sum.Height, 4); Put(Sum.Width, 4); New_Line; end Infix; -- Result of Execution -- The sum is 17 29 21
在本教程中,名为e_c20_p7.ada的示例程序可以放在几个不同的地方,因为它实际上不太适合任何地方。由于使用操作符重载最有利的地方之一是使用记录类型变量,因此这似乎是一个很好的地方。这个程序本身非常简单,但是这个概念可能非常强大。
我们首先定义一个由三个简单变量组成的记录,所有这些变量都是INTEGER类型的,并使用这个定义声明三个这种类型的变量。下一个声明是一个名为“+”的函数,我们可以定义它来执行我们希望它执行的任何操作。在本例中,我们输入两个记录类型的变量THREE_INTS并返回一个相同类型的记录。在函数中,我们将一个变量的所有三个字段添加到另一个变量的相应字段中,并返回由两个输入记录之和组成的记录。为了更加方便,Ada允许您使用中缀表示法中的重载运算符,如程序的第30行所示,其中两个记录相加并分配给名为Sum的记录变量。这一行实际上是对我们前面定义的名为“+”的函数的调用。
如果您考虑第24行的加法,其中使用了常用的加法运算符,可能会有点混乱。Ada将根据要加在一起的常量或变量的类型来决定使用哪个+运算符。如果您试图对其他类型的记录(例如上一个程序中的车辆记录)使用此运算符,系统将在编译期间生成类型错误。如前所述,只可能使现有操作符过载。不能定义新运算符(如&=)并将其用于某些操作。
回想一下名为Advanced Array Topics的一章,我们在名为e_c19_p4.ada的示例程序中重载了“+”运算符。这个操作符的两个重载都可以包含在一个程序中,并且系统可以通过检查所使用的类型来为每次使用找到正确的重载。确保编译并执行这个程序,即使它没有输出。
编程练习
1.向e_c20_p5.ada添加一些输出语句以显示一些结果。(Solution)
2.尝试用我们在示例程序e_c20_p6.ada中更改Ford的变体的方法来更改Stanley的变体,以查看您收到的错误消息是什么类型的。(Solution)
3.向e_c20_p7.ada添加另一个重载函数,该函数使用“+”运算符将两个记录的所有六个元素相加,并返回一个整数类型变量。系统可以通过与函数调用关联的返回类型来判断要使用哪个重载(Solution)
---------------------------------------------------------------------------------------------------------------------------
原英文版出处:https://perso.telecom-paristech.fr/pautet/Ada95/a95list.htm
翻译(百度):博客园 一个默默的 *** 的人