Ada 编程/库/Ada.Containers.Vectors
此语言功能仅从 Ada 2005 开始可用。
Ada.Containers.Vectors 是从 Ada 2005 开始的 预定义语言环境 的一个单元。
此泛型包提供了一个容器(Vector),它可以在连续列表中存储任何 确定子类型。这使得一个 Ada.Containers.Vectors.Vector 类似于数组——然而,向量可以在声明后改变大小,而数组则不能。因此,向量也被称为动态数组或可调整大小的数组。
Ada 2005 的主要新增功能之一是容器库。此库使 Ada 开发人员能够操作数据结构,例如双向链表、映射、集合和向量。本页将展示 Ada.Containers.Vectors 包的工作方式。
但首先:什么是向量?以下是参考手册对它的解释
语言定义的泛型包 Containers.Vectors 提供了私有类型 Vector 和 Cursor,以及针对每种类型的操作集。向量容器允许在任何位置进行插入和删除,但它专门针对容器的高端(具有较高索引的端)进行插入和删除进行了优化。向量容器还提供了对其元素的随机访问。
向量容器在概念上表现得像一个数组,当插入项目时,它会根据需要扩展。向量的长度是向量包含的元素数量。向量的容量是可以在向量自动扩展之前插入到向量的最大元素数量。
向量容器中的元素可以通过泛型形式类型的一个索引值来引用。向量的第一个元素的索引值始终等于形式类型的下限。
向量容器可能包含空元素。空元素没有指定的值。
基本上它是一个一维数组,在很多方面它的行为与数组一样,允许对向量元素进行随机和顺序访问。主要区别在于,普通数组的大小是固定的,而向量则不是,只要有足够的资源来容纳向量。向量在添加新元素时会自动扩展。
向量的灵活性是一件很棒的事情,但它确实有代价:向量不像普通数组那样快和轻便。向量决不慢或占用太多资源,但它们比普通数组更慢更重。另一方面,如果你能忍受更高的资源需求,向量确实为你提供了一套非常直观的工具来管理你的数据列表。
学习如何使用 Ada.Containers.Vectors 库,在我看来,最好是用实际的、可运行的代码来完成。我不太喜欢伪代码,所以本页的所有示例都将编译并运行。
所有示例都围绕着一个名为“Linus Torvalds 语录”的小应用程序,在这个应用程序中,我们在一个普通的、扁平的文本文件中有一堆语录。该文件 (quotes.txt
) 将在所有示例中使用,它看起来像这样
I have an ego the size of a small planet. My name is Linus Torvalds and I am your god. Do you pine for the days when men were men and wrote their own device drivers? If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. An infinite number of monkeys typing into GNU emacs would never make a good program. Is "I hope you all die a painful death" too strong? Most days I wake up thinking I'm the luckiest bastard alive. I'm always right. This time I'm just even more right than usual. And what's the internet without the rick-roll?
如果不明确,每行代表来自 Linus 的一句语录。
我们的想法是,语录可以随时添加和删除,因此程序在读取所有语录之前并不知道有多少语录,这使得向量成为保存和操作所有语录的完美数据结构。
我们不会真正构建整个应用程序。我们只关注从 quotes.txt
文件中读取语录,然后将其添加到向量中的部分。
在我们开始编写实际代码之前,让我们先看看 Ada.Containers.Vectors 规范的开头
generic
type Index_Type is range <>;
type Element_Type is private;
请注意类型Index_Type
和 Element_Type
。前者定义了向量的**索引**,后者定义了向量包含的**元素类型**。了解这一点后,我们来查看一下基本的Quotes
程序本身。
with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Containers.Vectors; use Ada.Containers;
procedure Quotes is
package IO renames Ada.Text_IO;
package SUIO renames Ada.Text_IO.Unbounded_IO;
package Quote_Container is new Vectors (Natural, Unbounded_String);
use Quote_Container;
Quotes : Vector;
Input : IO.File_Type;
begin
IO.Open (File => Input,
Mode => IO.In_File,
Name => "quotes.txt");
while not IO.End_Of_File (File => Input) loop
Quotes.Append (New_Item => SUIO.Get_Line (File => Input));
end loop;
IO.Close (Input);
end Quotes;
如果我们关注程序中与向量相关的部分,我们会发现第 4 行添加了 Ada.Containers.Vectors 库单元。这一步对于使用向量至关重要,就像Ada.Text_IO
对于基本字符串 I/O 来说是必要的。如果您不熟悉这个概念,您应该先阅读有关 Ada 基础知识的内容,然后再回到这里。
有了可用的向量库,我们把注意力转向第 9 行,在这里我们用参数Natural
和 Unbounded_String
实例化了Vectors
泛型。结果是一个向量,其中索引从 0 开始(记住Natural
是Integer
的一个子类型,范围为0 .. Integer'Last
),元素的类型为Unbounded_String
。在第 11 行,我们将Quotes
变量声明为Vector
类型。由于第 10 行有use Quote_Container
子句,我们不需要完整的声明Quote_Container.Vector
。
现在我们有一个工作的向量Quotes
,下一步自然是要向其中添加一些数据。这在第 18 行完成,使用Append
过程将每个引号追加到向量中。最后,我们关闭文件并退出程序。
这个简单的Quotes
程序是本文的基础。下面几乎所有的示例都是在这个程序的基础上扩展的。但在继续探讨向量库的各种过程和函数之前,让我们简要地看一下用于不确定类型的向量包。
Ada.Containers.Indefinite_Vectors
[edit | edit source]兄弟库 Ada.Containers.Indefinite_Vectors 可用于不确定类型。它的工作方式与Ada.Containers.Vectors
非常相似,只是省略了一些Insert
过程。以下是一个关于它如何工作的示例。
with Ada.Text_IO;
with Ada.Containers.Indefinite_Vectors; use Ada.Containers;
procedure Quotes is
package IO renames Ada.Text_IO;
package Quote_Container is new Indefinite_Vectors (Natural, String);
use Quote_Container;
Quotes : Vector;
Input : IO.File_Type;
begin
IO.Open (File => Input,
Mode => IO.In_File,
Name => "quotes.txt");
while not IO.End_Of_File (File => Input) loop
Quotes.Append (New_Item => IO.Get_Line (File => Input));
end loop;
IO.Close (Input);
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
IO.Put_Line (Item => "Quote no. 2: " & Quotes.Element (Index => 1));
end Quotes;
编译并执行这段代码,您将得到:
No. of quotes: 10 Quote no. 2: My name is Linus Torvalds and I am your god.
需要注意的是,不确定向量比约束向量慢,因此如果性能很重要,请避免使用它们。
由于确定性和不确定性容器非常相似,本文的其余部分将只关注向量库的确定性版本。
过程和函数
[edit | edit source]Ada.Containers.Vectors
包中包含**很多**过程和函数。幸运的是,它们的名字都很贴切,因此通常很容易确定在特定情况下需要哪一个过程或函数。为了进一步帮助您识别过程 X 或函数 Y 究竟是做什么的,我将它们根据其主要功能分为几组。
有些过程/函数可能属于不止一类。在这些情况下,我将它们放在我认为它们最常用的类别中。
设置向量的尺寸/容量
[edit | edit source]在Ada.Containers.Vectors
包中,可以使用工具来创建指定尺寸/容量的向量。这些方法的主要目标是使程序员能够分配所需的资源量,从而避免在向向量添加新元素时内部调用Reserve_Capacity
所产生的性能开销。
Vectors.Reserve_Capacity
[edit | edit source]以下是Reserve_Capacity
的规范:
procedure Reserve_Capacity
(Container : in out Vector;
Capacity : Count_Type);
Ada 语言参考手册对Reserve_Capacity
的解释最为充分。
Reserve_Capacity
分配新的内部数据结构,以便结果向量的长度至少可以达到Capacity
的值,而无需额外调用Reserve_Capacity
,并且足以容纳Container
的当前长度。然后,Reserve_Capacity
将元素复制到新的数据结构中,并释放旧的数据结构。在分配期间引发的任何异常都会被传播,并且Container
不会被修改。
实际上,只有在少数情况下使用Reserve_Capacity
才有意义,其中一些罕见的情况包括:
- 为特定尺寸的向量准备资源。
- 释放过多的资源。
当然,可能还有其他原因,但这两个原因可能是最常见的,其中第一个原因是绝对的首选。因此,让我们在第 20 行IO.Close (Input);
调用之后,在Quotes
程序的主体中添加以下代码:
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
Quotes.Reserve_Capacity (Capacity => 100);
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
编译并执行Quotes
,您将得到:
Capacity: 16 Capacity: 100
让我们尝试扩展一下Quotes
程序,看看如何在程序执行过程中操作向量。将以下代码放在IO.Open
调用之前:
Quotes.Reserve_Capacity (Capacity => 100);
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length:" & Quotes.Length'Img);
将以下代码放在IO.Close
调用之后:
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length:" & Quotes.Length'Img);
Quotes.Reserve_Capacity (Capacity => Quotes.Length);
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length:" & Quotes.Length'Img);
输出如下:
Capacity: 100 Length: 0 Capacity: 100 Length: 10 Capacity: 10 Length: 10
如果您正在处理大型向量,那么可能值得检查一下,看是否可以通过一些精心放置的Reserve_Capacity
调用来加速代码或节省一些资源,但是,就像所有优化一样:不要过早地进行优化,并且要严格地进行测试。
Vectors.Set_Length
[edit | edit source]以下是Length
的规范:
procedure Set_Length
(Container : in out Vector;
Length : Count_Type);
有人可能会问,像Set_Length
这样的过程有什么意义,因为向量在添加新元素时会自动增长,那么为什么还要使用它呢?为了回答这个问题,重要的是要理解向量的长度永远不能超过其容量。
向量的容量是指为向量预留的内部数据结构的数量。这可能远大于向量的实际长度,但永远不会小于它(参见 Vectors.Capacity)。因此,当向向量添加新元素时,首先使用 Reserve_Capacity 扩展向量的容量(如果必要的话),然后添加新元素。因此,向向量中添加 100 个新元素,实际上会调用 Reserve_Capacity 100 次。这无疑是浪费资源,而使用Set_Length
正好可以避免这种浪费。如果我们知道要添加一堆新元素,我们可以将所有对 Reserve_Capacity 的调用累积到一个对Set_Length
的调用中。这样效率更高。
Set_Length
的另一个用途是缩短向量,我们将在下面的示例中看到。
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
Quotes.Set_Length (Length => 20);
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
Quotes.Set_Length (Length => 5);
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
输出如下:
Length of vector: 10 Length of vector: 20 Length of vector: 5
为了展示使用Set_Length
的好处,我编写了两个小程序。第一个程序使用Append
添加了 10_000_000 个新元素。第二个程序也这样做,但它使用的是Set_Length
和Replace_Element
。
with Ada.Text_IO;
with Ada.Containers.Vectors; use Ada.Containers;
procedure Quotes is
package IO renames Ada.Text_IO;
package Number_Container is new Vectors (Natural, Natural);
Numbers : Number_Container.Vector;
begin
IO.Put_Line ("Length:" & Numbers.Length'Img);
for i in 0 .. 9999999 loop
Numbers.Append (New_Item => i);
end loop;
IO.Put_Line ("Length:" & Numbers.Length'Img);
end Quotes;
以下是在我的电脑上计时后的结果:
Length: 0 Length: 10000000 real 0m0.438s user 0m0.336s sys 0m0.096s
现在,让我们借助Set_Length
重写这个小巧的程序,并重新计时。
with Ada.Text_IO;
with Ada.Containers.Vectors; use Ada.Containers;
procedure Quotes is
package IO renames Ada.Text_IO;
package Number_Container is new Vectors (Natural, Natural);
Numbers : Number_Container.Vector;
begin
IO.Put_Line ("Length:" & Numbers.Length'Img);
Numbers.Set_Length (Length => 10000000);
for i in 0 .. 9999999 loop
Numbers.Replace_Element (Index => i,
New_Item => i);
end loop;
IO.Put_Line ("Length:" & Numbers.Length'Img);
end Quotes;
以下是这个版本的结果:
Length: 0 Length: 10000000 real 0m0.109s user 0m0.064s sys 0m0.044s
这在执行时间上有了巨大的改进,因此,如果需要向向量中添加大量新元素,请务必使用Set_Length
。
以上结果是 10 次运行的平均值。
Vectors.To_Vector
[edit | edit source]To_Vector
的规范如下所示:
function To_Vector (Length : Count_Type) return Vector;
function To_Vector
(New_Item : Element_Type;
Length : Count_Type) return Vector;
这两个To_Vector
函数的作用应该很明显。我将把这两个函数的示例放在一起,因为它们非常相似。将以下代码添加到Quotes
程序的主体中:
IO.Put (Item => "1st. element: ");
SUIO.Put (Item => Quotes.Element (Index => 0));
IO.New_Line;
IO.Put (Item => "10th. element: ");
SUIO.Put_Line (Item => Quotes.Element (Index => 9));
Quotes := To_Vector (Length => 10);
IO.Put (Item => "1st. element: ");
SUIO.Put (Item => Quotes.Element (Index => 0));
IO.New_Line;
IO.Put (Item => "10th. element: ");
SUIO.Put_Line (Item => Quotes.Element (Index => 9));
Quotes := To_Vector (New_Item => To_Unbounded_String
("Put new quote here"),
Length => 10);
IO.Put (Item => "1st. element: ");
SUIO.Put (Item => Quotes.Element (Index => 0));
IO.New_Line;
IO.Put (Item => "10th. element: ");
SUIO.Put_Line (Item => Quotes.Element (Index => 9));
输出如下:
1st. element: I have an ego the size of a small planet. 10th. element: And what's the internet without the rick-roll? 1st. element: 10th. element: 1st. element: Put new quote here 10th. element: Put new quote here
那么我们学到了什么呢?第一个To_Vector
函数创建一个新的向量,其中包含Length
个空元素,而第二个To_Vector
函数创建一个新的向量,其中包含Length
个元素,所有元素的值都为New_Item
。
正如我们将在 稍后 学习的那样,读取空元素实际上是一个有界错误,因此第二个 To_Vector
可能最安全,因为你强制将一个有效的值放入每个元素。
将数据插入向量是使用向量的关键部分。空向量通常没有那么有趣,所以让我们看看如何向其中添加一些数据。
Append
的规范如下所示
procedure Append
(Container : in out Vector;
New_Item : Element_Type;
Count : Count_Type := 1);
procedure Append
(Container : in out Vector;
New_Item : Vector);
Append
所做的是将元素或其他向量添加到向量的末尾。
如果我们在程序中添加一些输出功能,我们可以看到它是如何工作的
SUIO.Put_Line (Item => Quotes.Element (9));
Element
函数将在后面详细讨论,但现在知道 Element
函数返回在给定索引 (Index_Type
) 处找到的元素 (Element_Type
) 就可以了,在本例中是索引 10。
执行程序应该得到以下输出
And what's the internet without the rick-roll?
为了完全清楚 Append
的作用,让我们将这三行添加到程序的主体中
-- Output the contents of index no. 9
SUIO.Put_Line (Item => Quotes.Element (9));
-- Append a new element to the vector
Quotes.Append (New_Item => To_Unbounded_String ("Test append"));
-- Output the contents of the index no. 10
SUIO.Put_Line (Item => Quotes.Element (10));
执行程序,现在得到以下输出
And what's the internet without the rick-roll? Test append
查看第一个 Append
过程的规范,你可能已经注意到可选的 Count
参数。此参数的功能与其名称密切相关,因为它使你能够将元素插入 Count
次。让我们看看它的实际应用。尝试将其添加到 Quotes
程序的主体中
SUIO.Put_Line (Item => Quotes.Element (9));
-- Append the 3 identical elements to the vector
Quotes.Append (New_Item => To_Unbounded_String ("Test append"),
Count => 3);
SUIO.Put_Line (Item => Quotes.Element (10));
SUIO.Put_Line (Item => Quotes.Element (11));
SUIO.Put_Line (Item => Quotes.Element (12));
输出如下:
And what's the internet without the rick-roll? Test append Test append Test append
在上面的示例中,我们使用了 Count
值 3,并且正如预期的那样,元素“Test append”被追加到向量三次。
Vectors
包中的第二个 Append
过程将一个向量追加到另一个向量。要查看它是如何工作的,我们需要创建一个新的向量,这需要对程序的声明部分进行添加。添加以下声明
My_Quotes : Vector;
我们刚刚做的是声明一个新的向量 My_Quotes
。这是我们将追加到 Quotes
向量的向量。
现在将其添加到程序的主体中
My_Quotes.Append (New_Item => To_Unbounded_String ("My 1st. quote"));
My_Quotes.Append (New_Item => To_Unbounded_String ("My 2nd. quote"));
My_Quotes.Append (New_Item => To_Unbounded_String ("My 3rd. quote"));
SUIO.Put_Line (Item => Quotes.Element (9));
SUIO.Put_Line (Item => My_Quotes.Element (0));
-- Append the My_Quotes vector to the Quotes vector
Quotes.Append (New_Item => My_Quotes);
SUIO.Put_Line (Item => Quotes.Element (12));
执行此程序的输出是
And what's the internet without the rick-roll? My 1st. quote My 3rd. quote
请注意,我们使用的方法与追加常规元素时相同,只是在追加向量的 Append
过程中没有使用 Count
参数。
关于 Append
过程没有太多可说的了,所以让我们继续学习它的兄弟:Prepend
。
Prepend
的规范如下所示
procedure Prepend
(Container : in out Vector;
New_Item : Vector);
procedure Prepend
(Container : in out Vector;
New_Item : Element_Type;
Count : Count_Type := 1);
Prepend
与 Append
非常相似,唯一的区别是数据插入到向量的开头而不是末尾。要查看它的实际应用,请将以下内容添加到基本 Quotes
程序的主体中
SUIO.Put_Line (Item => Quotes.Element (0));
-- Prepend a new element to the Quotes vector
Quotes.Prepend (New_Item => To_Unbounded_String ("Prepended!"));
SUIO.Put_Line (Item => Quotes.Element (0));
SUIO.Put_Line (Item => Quotes.Element (1));
运行程序的结果,正如预期的那样
I have an ego the size of a small planet. Prepended! I have an ego the size of a small planet.
正如你所见,“Prepended!” 文本已添加到 Linus 的自恋语录之前。
Prepend
也可以用来连接两个向量。功能相同,唯一的区别是复制的是一个向量而不是单个元素。
Insert
的规范如下所示
procedure Insert
(Container : in out Vector;
Before : Extended_Index;
New_Item : Vector);
procedure Insert
(Container : in out Vector;
Before : Cursor;
New_Item : Vector);
procedure Insert
(Container : in out Vector;
Before : Cursor;
New_Item : Vector;
Position : out Cursor);
procedure Insert
(Container : in out Vector;
Before : Extended_Index;
New_Item : Element_Type;
Count : Count_Type := 1);
procedure Insert
(Container : in out Vector;
Before : Cursor;
New_Item : Element_Type;
Count : Count_Type := 1);
procedure Insert
(Container : in out Vector;
Before : Cursor;
New_Item : Element_Type;
Position : out Cursor;
Count : Count_Type := 1);
procedure Insert
(Container : in out Vector;
Before : Extended_Index;
Count : Count_Type := 1);
procedure Insert
(Container : in out Vector;
Before : Cursor;
Position : out Cursor;
Count : Count_Type := 1);
Insert
使我们能够在列表中的任何位置插入一个或多个相同的元素。它还向我们介绍了一个新概念:游标。
但让我们先从使用索引来识别单个元素的 Insert
过程开始。由于它们非常相似,因此我将它们全部放在同一个示例中。将以下内容添加到 Quotes
程序的主体中
-- Insert an element at the second index of the vector
Quotes.Insert (Before => 1,
New_Item => To_Unbounded_String ("New second index!"));
-- Insert two identical elements at the fifth index of the vector
Quotes.Insert (Before => 4,
New_Item => To_Unbounded_String ("Two new elements @ 4 and 5!"),
Count => 2);
-- Insert two empty elements before the last index of the vector
Quotes.Insert (Before => Quotes.Last_Index,
Count => 2);
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (Integer (i)));
end loop;
此程序的输出是
0 I have an ego the size of a small planet. 1 New second index! 2 My name is Linus Torvalds and I am your god. 3 Do you pine for the days when men were men and wrote their own device drivers? 4 Two new elements @ 4 and 5! 5 Two new elements @ 4 and 5! 6 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 7 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 8 An infinite number of monkeys typing into GNU emacs would never make a good program. 9 Is "I hope you all die a painful death" too strong? 10 Most days I wake up thinking I'm the luckiest bastard alive. 11 I'm always right. This time I'm just even more right than usual. 12 13 14 And what's the internet without the rick-roll?
请注意,Insert
的最后一个调用不会插入两个空元素。相反,会插入两个向量默认值的元素。据我所知,此值目前为空,但我不会依赖它。如果你需要插入空元素,请查看下面的 Insert_Space
过程。
通过 Insert
,你可以将新元素插入向量的任何位置,只需适当地设置 Before
参数即可。向量将根据需要扩展,并且 Before
索引后的元素将被 Count
参数(其默认值为 1)的值向下移动。
剩余的 Insert
过程都使用游标。使用游标,你可以同时访问向量和元素。在下一个示例中,我将展示所有使用游标的 Insert
过程。为了使用游标,我们必须先声明它,所以将以下内容添加到 Quotes
声明中
Q_Cursor : Cursor;
并将以下内容添加到主体中
-- Move the cursor to the first position of the Quotes vector.
Q_Cursor := Quotes.First;
-- Insert an element at the first position of the vector.
Quotes.Insert (Before => Q_Cursor,
New_Item => To_Unbounded_String ("New index 0!"));
-- Move the cursor to the 5th. position of the vector
Q_Cursor := Quotes.To_Cursor (Index => 4);
-- Insert an element before the current 5th. position in the vector and
-- set the current position of the cursor.
Quotes.Insert (Before => Q_Cursor,
New_Item => To_Unbounded_String ("New index 4!"),
Position => Q_Cursor);
-- Move the cursor to the next position
Q_Cursor := Next (Position => Q_Cursor);
-- Insert two new elements before the current 6th. position in the vector.
Quotes.Insert (Before => Q_Cursor,
New_Item => To_Unbounded_String ("New index 5 and 6!"),
Count => 2);
-- Move the cursor to the 10th. position of the vector.
Q_Cursor := Quotes.To_Cursor (Index => 9);
-- Insert two elements before the 10th. position and set the current
-- position of the cursor.
Quotes.Insert (Before => Q_Cursor,
New_Item => To_Unbounded_String ("New index 9 and 10!"),
Position => Q_Cursor,
Count => 2);
-- Move the cursor to the last position of the vector.
Q_Cursor := Quotes.Last;
-- Insert two empty elements before the last position and set the position
-- of the cursor.
Quotes.Insert (Before => Q_Cursor,
Position => Q_Cursor,
Count => 2);
-- Move the cursor to the 1st. postion of the vector.
Q_Cursor := Quotes.First;
for i in Quotes.First_Index .. Quotes.Last_Index loop
-- Output the element of the current position.
SUIO.Put_Line (Item => To_Index (Position => Q_Cursor)'Img &
Element (Position => Q_Cursor));
-- Move the cursor to the next position in the vector.
Next (Position => Q_Cursor);
end loop;
运行程序的结果是
0New index 0! 1I have an ego the size of a small planet. 2My name is Linus Torvalds and I am your god. 3Do you pine for the days when men were men and wrote their own device drivers? 4New index 4! 5New index 5 and 6! 6New index 5 and 6! 7If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 8You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 9New index 9 and 10! 10New index 9 and 10! 11An infinite number of monkeys typing into GNU emacs would never make a good program. 12Is "I hope you all die a painful death" too strong? 13Most days I wake up thinking I'm the luckiest bastard alive. 14I'm always right. This time I'm just even more right than usual. 15 16 17And what's the internet without the rick-roll?
当我第一次编写这段代码时,我很难想出使用游标与 Ada.Containers.Vectors 库的充分理由 - 对我来说,使用索引似乎更自然,更符合向量,但后来我了解到,实际上至少有两个充分的理由可以使用游标
- 它们使通用算法成为可能
- 如果程序使用游标创建,则可以轻松切换到其他序列容器
所以实际上至少有两个充分的理由使用游标。当然,如果你正在计算索引值,使用游标不会给你带来任何好处。像往常一样,我们必须认真思考我们所需要的东西,并相应地选择工具。
当你需要插入一堆“空”元素时,Insert_Space
就是完成这项工作的合适工具。Insert_Space
的规范如下所示
procedure Insert_Space
(Container : in out Vector;
Before : Extended_Index;
Count : Count_Type := 1);
procedure Insert_Space
(Container : in out Vector;
Before : Cursor;
Position : out Cursor;
Count : Count_Type := 1);
在使用 Insert_Space
时,需要注意的一点是,“空”元素并不真正意味着“空”。以下是参考手册对此的说明
然后 Insert_Space 将 Before .. Last_Index (Container) 范围内的元素向上滑动 Count 个位置,然后在从 Before 开始的位置插入空元素。
以及后面的
通过调用 Element、Query_Element、Update_Element、Swap、Is_Sorted、Sort、Merge、“=”、Find 或 Reverse_Find 来读取空元素的值是一个有界错误。实现可能会将元素视为具有元素类型的任何正常值(参见 13.9.1),或者在修改向量之前引发 Constraint_Error 或 Program_Error。
从所有这一切中要带走的重要一点是,“空”实际上意味着“不可预测的值”。这一切都取决于编译器以及它如何处理新元素,因此不要指望 Insert_Space
传递干净整洁的空元素。它很可能只传递给你一堆已经存在元素的副本或其他一些值,只要它在向量的上下文中有效,在本例中意味着 Unbounded_String
。还要注意,如果你尝试对“空”元素使用 Element
函数,某些编译器可能会引发 Constraint_Error
或 Program_Error
。在我的情况下,我正在使用 GNATMAKE GPL 2008 (20080521),空元素被视为具有元素类型的任何正常值。
解决了这个问题,让我们继续看看 Insert_Space
在实际代码中的行为。首先将以下内容添加到 Quotes
程序的声明部分
Q_Cursor : Cursor; -- Declare the cursor.
现在我们也可以使用 Insert_Space
的游标版本。接下来,我们将以下内容添加到程序的主体中
-- Output all the quotes prior to any Insert_Space calls
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;
IO.New_Line (2);
-- Insert two "empty" elements at the beginning of the vector
Quotes.Insert_Space (Before => 0,
Count => 2);
-- Insert 3 "empty" elements before index 5 in the vector
Quotes.Insert_Space (Before => 5,
Count => 3);
-- Insert 3 "empty" elements before the last element, using a cursor.
Q_Cursor := Quotes.To_Cursor (Index => Quotes.Last_Index);
Quotes.Insert_Space (Before => Q_Cursor,
Position => Q_Cursor,
Count => 3);
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;
以下是得到的输出
0 I have an ego the size of a small planet. 1 My name is Linus Torvalds and I am your god. 2 Do you pine for the days when men were men and wrote their own device drivers? 3 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 4 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 5 An infinite number of monkeys typing into GNU emacs would never make a good program. 6 Is "I hope you all die a painful death" too strong? 7 Most days I wake up thinking I'm the luckiest bastard alive. 8 I'm always right. This time I'm just even more right than usual. 9 And what's the internet without the rick-roll? 0 I have an ego the size of a small planet. 1 My name is Linus Torvalds and I am your god. 2 I have an ego the size of a small planet. 3 My name is Linus Torvalds and I am your god. 4 Do you pine for the days when men were men and wrote their own device drivers? 5 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 6 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 7 An infinite number of monkeys typing into GNU emacs would never make a good program. 8 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 9 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 10 An infinite number of monkeys typing into GNU emacs would never make a good program. 11 Is "I hope you all die a painful death" too strong? 12 Most days I wake up thinking I'm the luckiest bastard alive. 13 I'm always right. This time I'm just even more right than usual. 14 15 16 17 And what's the internet without the rick-roll?
首先,我们注意到在多次调用 Insert_Space
后,向量长度为 18。这与三个 Count
参数完美匹配:2 + 3 + 3 = 8 加上原始长度 10。然后,我们注意到在某些地方 Insert_Space
插入了一些看起来是空元素(位置 14-16),而在其他地方,则插入了已存在元素的副本(位置 0-1 和 8-9)。这展示了我在开头提到的“不可预测的值”行为。
因此,当您使用 Insert_Space
时,不要将其视为一个完全按照其名称含义执行的程序。将其视为一个仅扩展向量在指定索引处的长度的程序。换句话说:Insert_Space
主要用于在 Before
位置快速扩展向量,包含 Count
个元素,并且您不应该在将这些元素填充一些有效数据之前依赖它们的实际含义。因此,我们在最终循环中进行的访问并非明智之举。正如参考手册引文中所述,我们可能会由于调用 Element
而遇到异常。
我们可以通过使用 Replace_Element 程序用一些真实数据填充空元素来避免此“问题”。
-- Output all the quotes prior to any Insert_Space calls
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;
IO.New_Line (2);
-- Insert two "empty" elements at the beginning of the vector
Quotes.Insert_Space (Before => 0,
Count => 2);
-- We now insert some actual data in the two new elements
Quotes.Replace_Element (Index => 0,
New_Item => To_Unbounded_String ("New index 0"));
Quotes.Replace_Element (Index => 1,
New_Item => To_Unbounded_String ("New index 1"));
-- Insert 3 "empty" elements before index 5 in the vector
Quotes.Insert_Space (Before => 5,
Count => 3);
-- We now insert some actual data in the three new elements
Quotes.Replace_Element (Index => 5,
New_Item => To_Unbounded_String ("New index 5"));
Quotes.Replace_Element (Index => 6,
New_Item => To_Unbounded_String ("New index 6"));
Quotes.Replace_Element (Index => 7,
New_Item => To_Unbounded_String ("New index 7"));
-- Insert 3 "empty" elements before the last element, using a cursor.
Q_Cursor := Quotes.To_Cursor (Index => Quotes.Last_Index);
Quotes.Insert_Space (Before => Q_Cursor,
Position => Q_Cursor,
Count => 3);
-- We now insert some actual data in the last three new elements
Quotes.Replace_Element (Index => 14,
New_Item => To_Unbounded_String ("New index 14"));
Quotes.Replace_Element (Index => 15,
New_Item => To_Unbounded_String ("New index 15"));
Quotes.Replace_Element (Index => 16,
New_Item => To_Unbounded_String ("New index 16"));
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;
输出如下:
0 I have an ego the size of a small planet. 1 My name is Linus Torvalds and I am your god. 2 Do you pine for the days when men were men and wrote their own device drivers? 3 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 4 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 5 An infinite number of monkeys typing into GNU emacs would never make a good program. 6 Is "I hope you all die a painful death" too strong? 7 Most days I wake up thinking I'm the luckiest bastard alive. 8 I'm always right. This time I'm just even more right than usual. 9 And what's the internet without the rick-roll? 0 New index 0 1 New index 1 2 I have an ego the size of a small planet. 3 My name is Linus Torvalds and I am your god. 4 Do you pine for the days when men were men and wrote their own device drivers? 5 New index 5 6 New index 6 7 New index 7 8 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 9 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 10 An infinite number of monkeys typing into GNU emacs would never make a good program. 11 Is "I hope you all die a painful death" too strong? 12 Most days I wake up thinking I'm the luckiest bastard alive. 13 I'm always right. This time I'm just even more right than usual. 14 New index 14 15 New index 15 16 New index 16 17 And what's the internet without the rick-roll?
有了这些 Replace_Element 调用,我们可以在最终循环中使用 Element
函数安全地读取整个向量,而无需担心会引发任何异常。
与向向量添加数据一样重要的是能够读取数据,所以让我们来看看一些可用于执行此操作的方法。
我们在回顾之前过程和函数时已经遇到了 Element
函数,因此它做什么以及它是如何工作的现在应该很清楚了。然而,这不会阻止我们像检查 Vectors 包的其他部分一样检查它。以下是两个 Element
函数的规范。
function Element
(Container : Vector;
Index : Index_Type) return Element_Type;
function Element (Position : Cursor) return Element_Type;
我们有一个 Element
函数,我们使用索引访问元素,还有一个函数,我们使用游标访问元素。两者都做同样的事情:返回向量中给定位置的 Element_Type
。以下是一个使用索引的示例。
-- Output first index 0 and then index 7
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
SUIO.Put_Line (Item => Quotes.Element (Index => 7));
并且它的输出是
I have an ego the size of a small planet. Most days I wake up thinking I'm the luckiest bastard alive.
游标示例需要对 Quotes
程序的声明部分进行添加。
Q_Cursor : Cursor;
之后,我们将此添加到 Quotes
程序的主体中。
-- Point the cursor at the first index of the vector
Q_Cursor := Quotes.To_Cursor (Index => Quotes.First_Index);
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
-- Point the cursor at index 7 of the vector
Q_Cursor := Quotes.To_Cursor (Index => 7);
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
输出如下:
I have an ego the size of a small planet. Most days I wake up thinking I'm the luckiest bastard alive.
Element
非常简单。使用索引还是游标是个人喜好问题。值得注意的是,Vectors 包中大多数与游标相关的过程和函数在内部将游标转换为索引,然后使用索引过程/函数进行实际的繁重处理。因此,我通常避免使用游标形式。我认为索引更易于使用和理解。使用游标形式的主要好处是灵活性。如果您使用游标,您的代码将在很少修改的情况下与其他基于序列的容器一起工作,例如 双向链表。
以下是 Query_Element
的规范。
procedure Query_Element
(Container : Vector;
Index : Index_Type;
Process : not null access procedure (Element : Element_Type));
procedure Query_Element
(Position : Cursor;
Process : not null access procedure (Element : Element_Type));
Query_Element
与 Element
函数既相似又不同。Element
返回给定索引的 Element_Type
,而 Query_Element
将 Element_Type
传递给 Process
参数。此参数接受一个处理 Element_Type
的过程。为了展示它是如何工作的,我们首先需要在 Quotes
程序的规范中添加一个新过程。
-- This is the "callback" procedure
procedure Add_42 (Element : Unbounded_String) is
begin
SUIO.Put_Line (Item => Element & To_Unbounded_String (" 42"));
end Add_42;
我们在这个过程中所做的只是输出元素并将 42 连接到输出。
为了使用游标,我们必须首先声明它。
Q_Cursor : Cursor; -- Declare the cursor.
接下来这部分代码应该添加到 Quotes
程序的主体中。我们将 Query_Element
的索引版本和游标版本都包含在同一个示例中。
-- Output the first index
Quotes.Query_Element (Index => 0,
Process => Add_42'Access);
-- Point the cursor at index 7 of the vector and output the element
Q_Cursor := Quotes.To_Cursor (Index => 7);
Query_Element (Position => Q_Cursor, Process => Add_42'Access);
注意 'Access
属性。它返回一个指向 Add_42
子程序的指针,对于 Process
参数来说,它绝对是必需的。仅仅写 Ada_42
是不够的,正如您从规范中看到的,规范中说 not null access procedure
。
此程序的输出是
I have an ego the size of a small planet. 42 Most days I wake up thinking I'm the luckiest bastard alive. 42
使用 Element
还是 Query_Element
是性能与混乱之间的权衡。Query_Element
比 Element
速度更快,但是使用回调过程的混乱程度可能不值得源代码变得混乱,除非性能至关重要。元素越大,使用 Element
时性能下降就越严重。
更改向量中的数据是一项常见任务。以下两种方法可以实现这一点。
如果您需要更新向量中的元素,Update_Element
过程将非常有用。它们的工作方式与 Query_Element
非常相似,即您必须创建一个回调过程来处理您试图对向量中的元素执行的操作。以下是 Update_Element
的规范。
procedure Update_Element
(Container : in out Vector;
Index : Index_Type;
Process : not null access procedure (Element : in out Element_Type));
procedure Update_Element
(Container : in out Vector;
Position : Cursor;
Process : not null access procedure (Element : in out Element_Type));
注意这两个过程看起来几乎完全像 Query_Element
过程。最关键的区别在于 Process
参数,现在参数模式为 in out
,而 Query_Element
过程的参数模式为 in
。由于它们如此相似,我们将从 Query_Element
示例 中声明相同的游标和回调过程。我们只会更改参数模式。
procedure Add_42 (Element : in out Unbounded_String) is
begin
Element := Element & To_Unbounded_String (" 42");
end Add_42;
Q_Cursor : Cursor;
与 Query_Element
一样,我将在同一个列表中显示索引和游标示例。
-- Update the first index
Quotes.Update_Element (Index => 0,
Process => Add_42'Access);
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
-- Point the cursor at the index 7 of the vector
Q_Cursor := Quotes.To_Cursor (Index => 7);
Quotes.Update_Element (Position => Q_Cursor,
Process => Add_42'Access);
SUIO.Put_Line (Item => Quotes.Element (Index => 7));
此程序的输出是
I have an ego the size of a small planet. 42 Most days I wake up thinking I'm the luckiest bastard alive. 42
Update_Element
并不是更改向量中元素内容的唯一方法。还有其他方法可以将“42”连接到我们的 Linus 引用,其中一种方法是 Replace_Element
,我们将在后面讨论它。
之前讨论的 Update_Element
过程在原地更新元素,而 Replace_Element
将一个新的 Element_Type
赋值给给定的索引或位置,而不管之前的内容是什么。以下是 Replace_Element
的规范。
procedure Replace_Element
(Container : in out Vector;
Index : Index_Type;
New_Item : Element_Type);
procedure Replace_Element
(Container : in out Vector;
Position : Cursor;
New_Item : Element_Type);
使用 Replace_Element
与使用 Insert
过程非常类似,除了您现在处理的索引已经包含一个值,而不是创建一个新的索引。保留 Update_Element
中的 Q_Cursor
声明,并将以下内容添加到 Quotes
程序的主体中。
-- Replace the first index
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
Quotes.Replace_Element (Index => 0,
New_Item => To_Unbounded_String ("New index 0"));
SUIO.Put_Line (Item => Quotes.Element (Index => 0));
-- Replace the index 7 using a cursor
SUIO.Put_Line (Item => Quotes.Element (Index => 7));
Q_Cursor := Quotes.To_Cursor (Index => 7);
Quotes.Replace_Element (Position => Q_Cursor,
New_Item => To_Unbounded_String ("New index 7"));
SUIO.Put_Line (Item => Quotes.Element (Index => 7));
此程序的输出是
I have an ego the size of a small planet. New index 0 Most days I wake up thinking I'm the luckiest bastard alive. New index 7
何时使用 Replace_Element
,何时使用 Update_Element
很大程度上取决于您想要完成的任务。请记住,Update_Element
在原地更新给定索引上的元素,而 Replace_Element
则完全替换给定索引上的元素,并且与 Element
和 Query_Element
一样,Update_Element
比 Replace_Element
速度更快,但是它会使用回调过程使源代码变得混乱。因此,与许多其他事情一样,这是干净代码与性能之间的权衡,所以一定要测试并选择最适合项目的方案。
当然,可以从向量中删除元素,并完全清除向量。我敢打赌,细心的读者已经猜到了这些过程的名称。然而,也有一些意外。
Vectors.Clear
[edit | edit source]以下是 Clear
的规范
procedure Clear (Container : in out Vector);
Clear
从向量中删除所有元素,但不改变向量的容量。因此,如果您想重置一个向量,同时保留向量的容量,那么 Clear
是您的最佳选择。尝试将其添加到 Quotes
程序的主体中
IO.Put_Line (Item => "Capacity before clear:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "No. of quotes before clear:" & Quotes.Length'Img);
Quotes.Clear;
IO.Put_Line (Item => "Capacity after clear:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "No. of quotes after clear:" & Quotes.Length'Img);
输出如下:
Capacity before clear: 16 No. of quotes before clear: 10 Capacity after clear: 16 No. of quotes after clear: 0
请注意,您的容量数字可能与上面不同。需要注意的是,容量在调用 Clear
之前和之后是相同的。改变的是向量内容的长度值。
因此,不幸的是,您不能相信 Clear
能够真正地从向量中删除数据。这意味着,如果您调整现在“空”的向量的大小,您可能会再次找到您的数据。要查看它是如何工作的,请将其添加到程序中
Quotes.Set_Length (Length => 10);
SUIO.Put_Line (Item => Quotes.First_Element);
输出并非预期那样,当然也不是想要的
Capacity before clear: 16 No. of quotes before clear: 10 Capacity after clear: 16 No. of quotes after clear: 0 I have an ego the size of a small planet.
Vectors.Delete
[edit | edit source]以下是 Delete
的规范
procedure Delete
(Container : in out Vector;
Index : Extended_Index;
Count : Count_Type := 1);
procedure Delete
(Container : in out Vector;
Position : in out Cursor;
Count : Count_Type := 1);
这两个过程从向量中删除元素。如您所见,有一个使用索引的版本和一个使用游标的版本。我将在同一个示例中展示这两个版本,因为它们在使用上非常相似。
Count
参数告诉 Delete
从向量中删除多少个元素。如果元素数量少于 Count
,则所有元素都会从向量中删除。如果您尝试删除一个不存在的索引,则会引发 CONSTRAINT_ERROR
异常。
我们需要一个游标来进行 Delete
示例,所以将其添加到 Quotes
程序的声明部分
Q_Cursor : Cursor;
以下是 Delete
示例的主体代码
-- Delete quote no. 2 and 3
Quotes.Delete (Index => 1,
Count => 2);
-- Delete quote no. 5, 6 and 7
Q_Cursor := Quotes.To_Cursor (Index => 4);
Quotes.Delete (Position => Q_Cursor,
Count => 3);
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => i'Img & " " & Quotes.Element (i));
end loop;
这是上面代码生成的输出
0 I have an ego the size of a small planet. 1 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 2 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 3 An infinite number of monkeys typing into GNU emacs would never make a good program. 4 And what's the internet without the rick-roll?
如果 Count
参数为 0,则不做任何操作。
Vectors.Delete_First
[edit | edit source]以下是 Delete_First
的规范
procedure Delete_First
(Container : in out Vector;
Count : Count_Type := 1);
Delete_First
过程的作用应该很明显。它删除向量中的前 Count
个元素。要查看示例,请将其添加到 Quotes
程序的主体中:
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Delete_First;
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Delete_First (Count => 5);
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.First_Element);
上面程序的输出是
No. of quotes: 10 I have an ego the size of a small planet. No. of quotes: 9 My name is Linus Torvalds and I am your god. No. of quotes: 4 Is "I hope you all die a painful death" too strong?
Delete_First
完全删除了向量中的元素,它不仅删除内容,还留下了空元素。如果您尝试删除比向量包含的元素更多的元素,则在整个向量被删除后会引发 CONSTRAINT_ERROR
。如果 Count
为 0,则 Delete_First
不执行任何操作。
Vectors.Delete_Last
[edit | edit source]以下是 Delete_Last
的规范
procedure Delete_Last
(Container : in out Vector;
Count : Count_Type := 1);
细心的读者会注意到,这个过程与上面提到的 Delete_First
过程非常相似,因为它们非常相似,所以以下内容几乎是 Delete_First
文本的逐字复制。
Delete_Last
过程会删除向量中的最后 Count
个元素。要查看示例,请将其添加到 Quotes
程序的主体中:
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.Last_Element);
Quotes.Delete_Last;
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.Last_Element);
Quotes.Delete_Last (Count => 5);
IO.Put_Line (Item => "No. of quotes:" & Quotes.Length'Img);
SUIO.Put_Line (Item => Quotes.Last_Element);
上面程序的输出是
No. of quotes: 10 And what's the internet without the rick-roll? No. of quotes: 9 I'm always right. This time I'm just even more right than usual. No. of quotes: 4 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
Delete_Last
已完全删除了向量中的元素。它也不会仅仅删除内容,而留下一个或多个空元素,Count
的约束与 Delete_First
相同。
移动、交换和反转
[edit | edit source]有时需要将整个向量移动到新的向量,交换向量中的值,或者可能以反向顺序重新排列向量。以下方法将有助于实现这一点。
Vectors.Move
[edit | edit source]以下是 Move
的规范
procedure Move (Target : in out Vector; source : in out Vector);
可以使用 Move
过程将数据从一个向量移动到另一个向量。将其添加到 Quotes
程序的声明部分
My_Quotes : Vector;
以及将其添加到主体中
IO.Put_Line (Item => "Quotes length:" & Quotes.Length'Img);
IO.Put_Line (Item => "My_Quotes length:" & My_Quotes.Length'Img);
My_Quotes.Move (source => Quotes);
IO.Put_Line (Item => "Quotes new length:" & Quotes.Length'Img);
IO.Put_Line (Item => "My_Quotes new length:" & My_Quotes.Length'Img);
输出是
Quotes length: 10 My_Quotes length: 0 Quotes new length: 0 My_Quotes new length: 10
Vectors.Reverse_Elements
[edit | edit source]以下是 Reverse_Elements
的规范
procedure Reverse_Elements (Container : in out Vector);
Reverse_Elements
过程是另一个执行其名称所指示操作的过程:它以反向顺序重新排列向量中的元素。让我们试一试
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Reverse_Elements;
SUIO.Put_Line (Item => Quotes.First_Element);
输出如下:
I have an ego the size of a small planet. And what's the internet without the rick-roll?
关于 Reverse_Elements
的内容基本上就是这些了。
Vectors.Swap
[edit | edit source]以下是 Swap
的规范
procedure Swap (Container : in out Vector; I, J : Index_Type);
procedure Swap (Container : in out Vector; I, J : Cursor);
两个 Swap
过程将位置 I
和 J
的值相互交换,这意味着位置 I
的值被移动到位置 J
,反之亦然。对于 Index_Type
过程和 Cursor
过程来说,功能相同,因此我将在同一个示例中展示这两个过程。首先,我们必须将两个新变量添加到 Quotes
程序的声明部分
A_Cursor : Cursor;
B_Cursor : Cursor;
然后将其添加到主体中
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Swap (I => 0,
J => 9);
SUIO.Put_Line (Item => Quotes.First_Element);
A_Cursor := Quotes.To_Cursor (Index => 0);
B_Cursor := Quotes.To_Cursor (Index => 9);
SUIO.Put_Line (Item => Quotes.First_Element);
Quotes.Swap (I => A_Cursor,
J => B_Cursor);
SUIO.Put_Line (Item => Quotes.First_Element);
此程序的输出是
I have an ego the size of a small planet. And what's the internet without the rick-roll? And what's the internet without the rick-roll? I have an ego the size of a small planet.
请注意,如果要交换的索引之一超出范围,则会引发 Constraint_Error
异常。
使用 Swap
,非常容易地将 冒泡排序 添加到 Quotes
程序中。请注意,我这里仅仅使用冒泡排序作为示例,您不应该使用这种排序方法来排序您的数据,因为它非常慢且效率低下。
可以使用以下方法获取有关给定向量的相当多的信息。
Capacity
返回向量的当前容量。重要的是要理解,容量与向量的尺寸不同;相反,您应该将其视为向量在必须为向量分配新数据结构之前可以增长的尺寸。
以下是 Capacity
的规范
function Capacity (Container : Vector) return Count_Type;
让我们看看它是如何工作的。将此添加到 Quotes
程序的正文中
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
Quotes.Clear;
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
输出如下:
Capacity: 16 Capacity: 16
Quotes
向量的容量为 16,即使我们知道它只有 10 个引号。即使我们清空 Quotes
向量,容量仍然保持在 16。让我们扩展一下 Quotes
程序,看看向量包是如何处理容量的。我将在此处发布完整的源代码
with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Containers.Vectors; use Ada.Containers;
procedure Quotes is
package IO renames Ada.Text_IO;
package SUIO renames Ada.Text_IO.Unbounded_IO;
package Quote_Container is new Vectors (Natural, Unbounded_String);
use Quote_Container;
Quotes : Vector;
Input : IO.File_Type;
begin
IO.Put_Line (Item => "Capacity (pre loop):" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length (pre loop):" & Quotes.Length'Img);
IO.Open (File => Input,
Mode => IO.In_File,
Name => "quotes.txt");
while not IO.End_Of_File (File => Input) loop
Quotes.Append (New_Item => SUIO.Get_Line (File => Input));
IO.Put_Line (Item => "Capacity:" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length:" & Quotes.Length'Img);
end loop;
IO.Close (Input);
IO.Put_Line (Item => "Capacity (post loop):" & Quotes.Capacity'Img);
IO.Put_Line (Item => "Length (post loop):" & Quotes.Length'Img);
end Quotes;
输出如下:
Capacity (pre loop): 0 Length (pre loop): 0 Capacity: 1 Length: 1 Capacity: 2 Length: 2 Capacity: 4 Length: 3 Capacity: 4 Length: 4 Capacity: 8 Length: 5 Capacity: 8 Length: 6 Capacity: 8 Length: 7 Capacity: 8 Length: 8 Capacity: 16 Length: 9 Capacity: 16 Length: 10 Capacity (post loop): 16 Length (post loop): 10
注意长度和容量并不一定匹配。向量的容量将始终与长度相同或大于长度。当我们将容量调整留给系统时,容量似乎以 2 的幂增长,而长度是实际添加数据的线性计数。
Is_Empty
的规范如下所示
function Is_Empty (Container : Vector) return Boolean;
使用 Is_Empty
函数,我们可以测试向量是否为空。将此添加到 Quotes
的正文中,以查看它是如何工作的
if Quotes.Is_Empty then
IO.Put_Line (Item => "Empty!");
else
IO.Put_Line (Item => "Not empty!");
end if;
Quotes.Clear;
if Quotes.Is_Empty then
IO.Put_Line (Item => "Empty!");
else
IO.Put_Line (Item => "Not empty!");
end if;
输出如下:
Not empty! Empty!
请注意,在本例中,空是指没有元素的向量,而不是包含一堆空元素的向量。Is_Empty
函数检查的是向量的长度。如果长度大于 0,则 Is_Empty
返回 **FALSE**,如果长度等于 0,则 Is_Empty
返回 **TRUE**。
以下是Length
的规范:
function Length (Container : Vector) return Count_Type;
我们已经在前面的示例中遇到了 Length
函数,但是如果您没有注意它们,以下是对 Length
的简要说明:它返回向量的长度(即元素的数量)。我敢打赌这是一个惊喜!
让我们看一个例子。将此添加到 Quotes
程序的正文中
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
Quotes.Clear;
IO.Put_Line (Item => "Length of vector:" & Quotes.Length'Img);
输出如下:
10 0
最后让我们看看 Length
。
遍历向量可以使用简单的循环来完成,但为什么不使用内置方法呢?这些方法不如定制循环灵活,但如果我们只需要一个简单的“数组遍历”,那么这些方法就是最好的选择。
使用索引或游标遍历向量非常容易,但还有一个更简单的方法:Iterate
过程。
以下是 Iterate
的规范
procedure Iterate
(Container : Vector;
Process : not null access procedure (Position : Cursor));
Iterate
使您能够将 Process
过程应用于向量中的每个元素。与 Query_Element
和 Update_Element
过程一样,我们必须创建一个过程来处理各个元素。在这个示例中,我们将使用一个小的过程来反转每个引号,然后将生成的乱码输出到屏幕上。将此添加到 Quotes
程序的声明部分
procedure Reverse_Quote (A_Cursor : Cursor) is
Org_Quote : constant Unbounded_String := Element (Position => A_Cursor);
New_Backwards_Quote : Unbounded_String;
begin
for i in reverse 1 .. Length (Source => Org_Quote) loop
Append (Source => New_Backwards_Quote,
New_Item => Element (Source => Org_Quote,
Index => i));
end loop;
SUIO.Put_Line (Item => New_Backwards_Quote);
end Reverse_Quote;
接下来将此添加到正文中
Quotes.Iterate (Process => Reverse_Quote'Access);
此程序的输出是
.tenalp llams a fo ezis eht oge na evah I .dog ruoy ma I dna sdlavroT suniL si eman yM ?srevird ecived nwo rieht etorw dna nem erew nem nehw syad eht rof enip uoy oD .margorp ruoy xif dluohs dna ,yawyna dewercs er'uoy ,noitatnedni fo slevel 3 naht erom deen uoy fI .won morf skeew 2 did uoy tahw dnatsrednu ot ekil d'uoy ebyam tub ,tnaillirb er'uoy wonk uoY .margorp doog a ekam reven dluow scame UNG otni gnipyt syeknom fo rebmun etinifni nA ?gnorts oot "htaed lufniap a eid lla uoy epoh I" sI .evila dratsab tseikcul eht m'I gnikniht pu ekaw I syad tsoM .lausu naht thgir erom neve tsuj m'I emit sihT .thgir syawla m'I ?llor-kcir eht tuohtiw tenretni eht s'tahw dnA
非常优雅。将 Iterate
用于向量中元素的简单操作正是它被设计出来的目的。对于非常复杂的操作,它可能不是最佳解决方案,因为您无法将任何参数传递给 Process
过程,这自然限制了它的一些用法。
以下是 Reverse_Iterate
的规范
procedure Reverse_Iterate
(Container : Vector;
Process : not null access procedure (Position : Cursor));
Iterate
从第一个元素遍历到最后一个元素,而 Reverse_Iterate
则相反。除了这个小区别之外,这两个过程完全相同,因此我将展示的关于如何使用 Reverse_Iterate
的示例几乎与 Iterate
的示例相同。
首先,我们将 Reverse_Quote
过程添加到 Quotes
程序的声明部分
procedure Reverse_Quote (A_Cursor : Cursor) is
Org_Quote : constant Unbounded_String := Element (Position => A_Cursor);
Backwards : Unbounded_String;
Index : constant Natural := To_Index (Position => A_Cursor);
begin
for i in reverse 1 .. Length (Source => Org_Quote) loop
Append (Source => Backwards,
New_Item => Element (Source => Org_Quote,
Index => i));
end loop;
IO.Put (Item => Index'Img & " ");
SUIO.Put (Item => Backwards);
IO.New_Line;
end Reverse_Quote;
/code>
And the we add this line to the body of the program:
<source lang="ada">
Quotes.Reverse_Iterate (Process => Reverse_Quote'Access);
输出结果如预期
9 ?llor-kcir eht tuohtiw tenretni eht s'tahw dnA 8 .lausu naht thgir erom neve tsuj m'I emit sihT .thgir syawla m'I 7 .evila dratsab tseikcul eht m'I gnikniht pu ekaw I syad tsoM 6 ?gnorts oot "htaed lufniap a eid lla uoy epoh I" sI 5 .margorp doog a ekam reven dluow scame UNG otni gnipyt syeknom fo rebmun etinifni nA 4 .won morf skeew 2 did uoy tahw dnatsrednu ot ekil d'uoy ebyam tub ,tnaillirb er'uoy wonk uoY 3 .margorp ruoy xif dluohs dna ,yawyna dewercs er'uoy ,noitatnedni fo slevel 3 naht erom deen uoy fI 2 ?srevird ecived nwo rieht etorw dna nem erew nem nehw syad eht rof enip uoy oD 1 .dog ruoy ma I dna sdlavroT suniL si eman yM 0 .tenalp llams a fo ezis eht oge na evah I
反转方向的反转引号。与 Iterate
一样,您无法将任何参数与 Process
过程一起使用。
无论您是使用游标还是索引,有时您都需要识别向量的开头和/或结尾。为此特定工作,我们有 4 种方法可用。让我们来看看它们。
以下是 First_Index
的规范
function First_Index (Container : Vector) return Index_Type;
使用 First_Index
的好处(除了清晰度之外)并不容易显现,除非我们创建几个具有不同 Index_Type
参数的向量。我将在此处显示完整的源代码,因为所需的更改非常多
with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Containers.Vectors; use Ada.Containers;
procedure Quotes is
package IO renames Ada.Text_IO;
package SUIO renames Ada.Text_IO.Unbounded_IO;
subtype Foo_Range is Natural range 42 .. 52;
subtype Bar_Range is Integer range -100 .. 100;
package Quote_Container is new Vectors (Natural, Unbounded_String);
package Foo_Container is new Vectors (Foo_Range, Unbounded_String);
package Bar_Container is new Vectors (Bar_Range, Unbounded_String);
Quotes : Quote_Container.Vector;
Foo : Foo_Container.Vector;
Bar : Bar_Container.Vector;
Input : IO.File_Type;
Line : Unbounded_String;
begin
IO.Open (File => Input,
Mode => IO.In_File,
Name => "quotes.txt");
while not IO.End_Of_File (File => Input) loop
Line := SUIO.Get_Line (File => Input);
Quotes.Append (New_Item => Line);
Foo.Append (New_Item => Line);
Bar.Append (New_Item => Line);
end loop;
IO.Close (Input);
IO.Put_Line (Item => Quotes.First_Index'Img);
for i in Quotes.First_Index .. Quotes.Last_Index loop
IO.Put (Item => i'Img & " ");
SUIO.Put (Item => Quotes.Element (Index => i));
IO.New_Line;
end loop;
IO.New_Line;
IO.Put_Line (Item => Foo.First_Index'Img);
for i in Foo.First_Index .. Foo.Last_Index loop
IO.Put (Item => i'Img & " ");
SUIO.Put (Item => Foo.Element (Index => i));
IO.New_Line;
end loop;
IO.New_Line;
IO.Put_Line (Item => Bar.First_Index'Img);
for i in Bar.First_Index .. Bar.Last_Index loop
IO.Put (Item => i'Img & " ");
SUIO.Put (Item => Bar.Element (Index => i));
IO.New_Line;
end loop;
end Quotes;
此程序的输出是
0 0 I have an ego the size of a small planet. 1 My name is Linus Torvalds and I am your god. 2 Do you pine for the days when men were men and wrote their own device drivers? 3 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 4 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 5 An infinite number of monkeys typing into GNU emacs would never make a good program. 6 Is "I hope you all die a painful death" too strong? 7 Most days I wake up thinking I'm the luckiest bastard alive. 8 I'm always right. This time I'm just even more right than usual. 9 And what's the internet without the rick-roll? 42 42 I have an ego the size of a small planet. 43 My name is Linus Torvalds and I am your god. 44 Do you pine for the days when men were men and wrote their own device drivers? 45 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. 46 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. 47 An infinite number of monkeys typing into GNU emacs would never make a good program. 48 Is "I hope you all die a painful death" too strong? 49 Most days I wake up thinking I'm the luckiest bastard alive. 50 I'm always right. This time I'm just even more right than usual. 51 And what's the internet without the rick-roll? -100 -100 I have an ego the size of a small planet. -99 My name is Linus Torvalds and I am your god. -98 Do you pine for the days when men were men and wrote their own device drivers? -97 If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. -96 You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. -95 An infinite number of monkeys typing into GNU emacs would never make a good program. -94 Is "I hope you all die a painful death" too strong? -93 Most days I wake up thinking I'm the luckiest bastard alive. -92 I'm always right. This time I'm just even more right than usual. -91 And what's the internet without the rick-roll?
注意所有引号前面的数字是如何根据用作向量 Index_Type
的子类型来设置的。您应该始终使用 First_Index
函数而不是实际的数值,主要是因为它使您能够以后更改 Index_Type
,而无需搜索所有代码以查找对硬编码数值的引用。使用 First_Index
更加安全。
以下是 Last_Index
的规范
function Last_Index (Container : Vector) return Extended_Index;
Last_Index
有点奇怪,因为它返回一个 Extended_Index
类型的
让我们看看它是如何工作的。将此添加到程序的声明部分
Empty : Vector;
将此添加到程序的主体
IO.Put_Line (Item => Quotes.Last_Index'Img);
IO.Put_Line (Item => Empty.Last_Index'Img);
输出如下:
9 -1
如您所见,Last_Index
函数对于 Empty
向量返回 -1
。我们用 Natural
作为 Index_Type
实例化了此向量,如您所知,Natural
类型具有 0 .. Integer'Last
的范围,而 0 减 1 等于 -1
。
为了进一步证明这一点,尝试将这两个 if
块添加到主体中
if Empty.Last_Index = No_Index then
IO.Put_Line ("No_Index");
end if;
if Empty.Last_Index = Empty.First_Index - 1 then
IO.Put_Line ("No_Index");
end if;
现在输出是
9 -1 No_Index No_Index
在他的优秀著作《Ada 2005 编程》 (ISBN 0321340787) 中,John Barnes 这样说
请注意,必须引入令人厌烦的子类型Extended_Index
来处理结束值。
我不知道是否会称 Extended_Index
为令人厌烦,但它是一个应该意识到并理解的怪事。
以下是 First
的规范
function First (Container : Vector) return Cursor;
First
函数返回一个指向向量中第一个元素的游标。与 First_Index
一样,使用 First
的主要原因是避免在程序中硬编码 Index_Type
的第一个值。
将此添加到 Quotes
程序的声明部分
Q_Cursor : Cursor;
以及将其添加到主体中
-- This is bad. If we change the Index_Type from Natural to Positive,
-- 0 would no longer be the first position in the vector.
Q_Cursor := Quotes.To_Cursor (Index => 0);
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
-- This is good. We always get the first position, no matter what
-- Index_Type we're using.
Q_Cursor := Quotes.First;
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
输出是
I have an ego the size of a small planet. I have an ego the size of a small planet.
您应该始终使用 First_Index
或 First
来识别向量中的第一个位置。您**绝不**应该硬编码第一个位置的数值,因为这种方法在您更改向量的 Index_Type
时有失败的风险。
以下是 Last
的规范
function Last (Container : Vector) return Cursor;
Last
比它的兄弟 Last_Index
简单得多,仅仅因为它不需要处理“令人厌烦”的 Extended_Index
类型。Last
函数只是返回一个指向向量中最后一个元素的游标,或者在空向量的情况下,它返回 No_Element
。让我们看看它是如何工作的。
将此添加到 Quotes
程序的声明部分
Q_Cursor : Cursor;
以及将其添加到主体中
Q_Cursor := Quotes.Last;
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
Quotes.Clear;
Q_Cursor := Quotes.Last;
if Q_Cursor /= No_Element then
SUIO.Put_Line (Item => Element (Position => Q_Cursor));
else
IO.Put_Line (Item => "No Elements in vector");
end if;
此程序的输出是
And what's the internet without the rick-roll? No Elements in vector
如您所见,当向量为空时,游标的值将设置为 No_Element
。如果您尝试“读取”No_Element
游标,将引发 CONSTRAINT_ERROR
。
我们已经了解了如何使用 Iterate
、Reverse_Iterate
和使用索引的常规循环来迭代向量,因此我认为现在该学习如何使用游标来完成它了。为此,我们提供了 Next
和 Previous
方法。
以下是 Next
的规范
function Next (Position : Cursor) return Cursor;
procedure Next (Position : in out Cursor);
根据您的口味和偏好,您可以在使用 Next
函数或 Next
过程之间进行选择。它们都执行相同的操作:将游标移动到向量中的下一个元素。我将在下面的示例中展示这两个版本。
首先将此添加到程序的声明部分
A_Cursor : Cursor;
以及将此添加到主体
A_Cursor := Quotes.First;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Move to the next element in the vector using the Next procedure.
Next (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Move to the next Element in the vector using the Next function.
A_Cursor := Next (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Move the cursor to the last position, and then call Next again.
-- Note how Next assigns the No_Element value to the cursor.
A_Cursor := Quotes.Last;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
Next (Position => A_Cursor);
if A_Cursor = No_Element then
IO.Put_Line (Item => "No_Element");
end if;
输出如下:
I have an ego the size of a small planet. My name is Linus Torvalds and I am your god. Do you pine for the days when men were men and wrote their own device drivers? No_Element
希望进一步解释是不必要的。
以下是 Previous
的规范
function Previous (Position : Cursor) return Cursor;
procedure Previous (Position : in out Cursor);
在 Next
方法向前移动游标的情况下,Previous
方法向后移动游标。我相信这一点大家都清楚,因此,让我们看看 Previous
在实际代码中是如何工作的。
将此添加到声明部分
A_Cursor : Cursor;
以及将其添加到主体中
A_Cursor := Quotes.Last;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Move to the previous element in the vector using the Previous procedure.
Previous (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Move to the previous Element in the vector using the Previous function.
A_Cursor := Previous (Position => A_Cursor);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Move the cursor to the first position, and then call Previous again.
-- Note how Previous assigns the No_Element value to the cursor.
A_Cursor := Quotes.First;
SUIO.Put_Line (Item => Element (Position => A_Cursor));
Previous (Position => A_Cursor);
if A_Cursor = No_Element then
IO.Put_Line (Item => "No_Element");
end if;
输出如下:
And what's the internet without the rick-roll? I'm always right. This time I'm just even more right than usual. Most days I wake up thinking I'm the luckiest bastard alive. I have an ego the size of a small planet. No_Element
这里应该没有惊喜。
找出特定元素是否存在于向量中,或者给定位置是否包含值,这是在使用向量时相当常见的任务,因此,我们提供了一些有价值的工具。
以下是 Find_Index
的规范
function Find_Index
(Container : Vector;
Item : Element_Type;
Index : Index_Type := Index_Type'First) return Extended_Index;
Find_Index
函数实际上只是围绕一个简单循环的包装器,其中向量中的每个元素都与 Item
进行比较。它没有任何“神奇”之处。它不会比自制循环快,但可以为您节省几行代码。Find_Index
从 Index
开始向前搜索向量。Index
默认为 Index_Type'First
,在本例中为 0。
要了解它是如何工作的,我们首先必须在程序的声明部分添加一些变量
Needle : Unbounded_String;
Pos : Extended_Index;
然后将此添加到主体
-- First search. This will fail because we have no FooBar quote.
Needle := To_Unbounded_String ("FooBar");
Pos := Quotes.Find_Index (Item => Needle);
if Pos = No_Index then
IO.Put_Line ("No_Index");
else
IO.Put_Line (Pos'Img);
end if;
-- Second search. This will succeed, finding the quote at index 7.
Needle := To_Unbounded_String
("Most days I wake up thinking I'm the luckiest bastard alive.");
Pos := Quotes.Find_Index (Item => Needle);
if Pos = No_Index then
IO.Put_Line ("No_Index");
else
IO.Put_Line (Pos'Img);
end if;
-- Third search. This will fail, because we start the search at index 8.
Needle := To_Unbounded_String
("Most days I wake up thinking I'm the luckiest bastard alive.");
Pos := Quotes.Find_Index (Item => Needle,
Index => 8);
if Pos = No_Index then
IO.Put_Line ("No_Index");
else
IO.Put_Line (Pos'Img);
end if;
最后输出
No_Index 7 No_Index
本例中有三个有趣的地方
Pos
的类型为Extended_Index
。这是为了处理No_Index
值。- 使用
Find_Index
的Index
参数可以让我们从指定索引开始搜索。 - 如果未找到匹配的元素,则返回
No_Index
值。
除了这三件事之外,关于 Find_Index
真的没什么好说的了。
在这个示例中,您将找到上面的 Find_Index
示例,它被修改为使用 Find
。这两个函数在使用和功能方面非常相似,因此,进一步解释完全是浪费空间。所以您只得到规范和示例。
以下是 Find
的规范
function Find
(Container : Vector;
Item : Element_Type;
Position : Cursor := No_Element) return Cursor;
Reverse_Find_Index
与 Find_Index
类似,区别在于它从 Index
开始反向搜索,直到到达向量开头或找到匹配项。在示例源代码页面上,您会发现 Find_Index
示例被修改以反映这一点。
Reverse_Find_Index
的规范与 Find_Index
惊人地相似
function Reverse_Find_Index
(Container : Vector;
Item : Element_Type;
Index : Index_Type := Index_Type'Last) return Extended_Index;
Reverse_Find
与 Find
类似,区别在于它从 Position
开始反向搜索,直到到达向量开头或找到匹配项。在示例源代码页面上,您会发现 Find
示例被修改以反映这一点。
Reverse_Find
的规范与 Find
惊人地相似
function Reverse_Find
(Container : Vector;
Item : Element_Type;
Position : Cursor := No_Element) return Cursor;
这是 Contains
的规范
function Contains
(Container : Vector;
Item : Element_Type) return Boolean;
使用 Contains
,您基本上获得了 Find_Index
,但返回的是一个 Boolean
值,而不是 Extended_Index
,实际上 Contains
函数只是 Find_Index
函数的薄包装器。如果您正在处理非常大的向量,并且知道您要查找的元素位于向量的末尾附近,从性能的角度来看,您可能最好使用自制的解决方案。我稍后会向您展示一个这样的解决方案。
但首先让我们尝试一下 Contains
示例。将其添加到声明中
Needle : Unbounded_String;
以及主体
Needle := To_Unbounded_String ("FooBar");
if Quotes.Contains (Item => Needle) then
IO.Put_Line (Item => "Found!");
else
IO.Put_Line (Item => "Not found!");
end if;
Needle := To_Unbounded_String
("Most days I wake up thinking I'm the luckiest bastard alive.");
if Quotes.Contains (Item => Needle) then
IO.Put_Line (Item => "Found!");
else
IO.Put_Line (Item => "Not found!");
end if;
最后输出
Not found! Found!
如前所述,Contains
从头到尾搜索向量,但这可能不是最佳解决方案。有时您知道一段数据位于向量的末尾附近,如果它在那里的话。在这种情况下,我们需要一个反向搜索的 Contains
函数。这样的函数很容易实现
. 由于两个 Find
函数都提供了 Reverse_
版本,我发现没有提供 Reverse_Contains
函数很奇怪。幸运的是,如前面的源代码所示,很容易自己添加它。
这是 Has_Element
的规范
function Has_Element (Position : Cursor) return Boolean;
Has_Element
使您能够检查游标是否标识了一个元素,或者如 ARM 所说
如果 Position 指定了一个元素,则返回 True,否则返回 False。
因此 Has_Element
使您能够检查游标是否仍然指向向量中的有效元素。请记住,游标指定了容器和容器中的位置。这意味着游标可以指向向量中不再存在的某个位置。让我们看看它是如何使用的。将其添加到程序的声明部分
A_Cursor : Cursor;
现在将其添加到主体中
-- First check. Cursor is not yet pointing at any specific index
if not Has_Element (Position => A_Cursor) then
IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
IO.Put_Line (Item => "No Element!");
end if;
-- Point cursor at the last index
A_Cursor := Quotes.To_Cursor (Index => Quotes.Last_Index);
if Has_Element (Position => A_Cursor) then
IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
IO.Put_Line (Item => "Element!");
end if;
-- Delete the first position. Now the cursor is pointing at a non-
-- existant position, represented by the numeric value -1
Quotes.Delete_First;
if not Has_Element (Position => A_Cursor) then
IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
IO.Put_Line (Item => "No Element!");
end if;
-- Move the cursor on position backwards, from 9 to 8.
Previous (Position => A_Cursor);
-- Now check if the current position designates an element
if Has_Element (Position => A_Cursor) then
IO.Put (Item => To_Index (Position => A_Cursor)'Img & " ");
IO.Put_Line (Item => "Element!");
end if;
输出如下:
-1 No Element! 9 Element! -1 No Element! 8 Element!
说实话,在使用向量时,我从来没有使用过 Has_Element
,但这可能是因为我很少在向量中使用游标。使用Ada.Containers.Doubly_Linked_Lists,情况就不同了,因为导航双向链表的唯一方法是使用游标。
有时需要将索引转换为游标,反之亦然。这可以使用名为 To_Cursor
和 To_Index
的函数来完成。
这是 To_Cursor
的规范
function To_Cursor
(Container : Vector;
Index : Extended_Index) return Cursor;
我将向您展示上述函数是如何工作的,即使它应该非常明显。首先将其添加到程序的声明部分
A_Cursor : Cursor;
以及将其添加到主体中
-- Point the cursor to index 1
A_Cursor := Quotes.To_Cursor (Index => 1);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Point the cursor to index 5
A_Cursor := Quotes.To_Cursor (Index => 5);
SUIO.Put_Line (Item => Element (Position => A_Cursor));
-- Point the cursor to a non-existant index
A_Cursor := Quotes.To_Cursor (Index => 42);
if A_Cursor = No_Element then
IO.Put_Line (Item => "No_Element");
end if;
该程序产生的输出结果为
My name is Linus Torvalds and I am your god. An infinite number of monkeys typing into GNU emacs would never make a good program. No_Element
从这个例子中,需要了解的重要一点是 No_Element
部分。如果给定的 Index
超出了范围,则返回 No_Element
。
这是 To_Index
的规范
function To_Index (Position : Cursor) return Extended_Index;
上面我们简要了解了如何将 Index_Type
转换为游标;现在我们将执行相反的操作:从游标转换为 Index_Type
。这可以使用 To_Index
函数来完成。
您可以保留 To_Cursor
示例的声明部分,但将主体替换为以下内容
-- Point the cursor to the first index and output the index value
A_Cursor := Quotes.To_Cursor (Index => Quotes.First_Index);
IO.Put_Line (Item => To_Index (Position => A_Cursor)'Img);
-- Point the cursor to the 5th. index and output the index value
A_Cursor := Quotes.To_Cursor (Index => 5);
IO.Put_Line (Item => To_Index (Position => A_Cursor)'Img);
-- Point the cursor to a non-existant index, convert to an index and
-- check for No_Index
A_Cursor := Quotes.To_Cursor (Index => 42);
if To_Index (Position => A_Cursor) = No_Index then
IO.Put_Line (Item => "No_Index");
end if;
这并不是什么难事,但值得注意的是,当对指向 No_Element
的游标调用 To_Index
时,No_Element
将被“转换为” No_Index
。
向量包还包含用于对向量进行排序的工具,以泛型包 Generic_Sorting
的形式,因此无需使用自制的排序例程。排序以升序进行。Generic_Sorting
的规范如下所示
generic
with function "<" (Left, Right : Element_Type) return Boolean is <>;
package Generic_Sorting is
function Is_Sorted (Container : Vector) return Boolean;
procedure Sort (Container : in out Vector);
procedure Merge (Target : in out Vector; Source : in out Vector);
end Generic_Sorting;
为了使用这些过程,我们首先需要实例化 Generic_Sorting
泛型。这是以显而易见的方式完成的
package A_Sorter is new Generic_Sorting;
只需将以上内容添加到 Quotes
程序的声明部分,您就可以开始使用。在下文中,我们将了解如何使用泛型过程来对向量进行排序。
这是 Sort
的规范
procedure Sort (Container : in out Vector);
除了实例化 Generic_Sorting
泛型(参见 Vectors.Generic_Sorting)之外,我们无需在 Quotes
程序的声明部分指定任何其他内容。相反,我们将直接跳转到主体,在那里我们将添加几行以了解排序是如何工作的
A_Sorter.Sort (Container => Quotes);
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => Quotes.Element (Index => i));
end loop;
程序的输出结果为
An infinite number of monkeys typing into GNU emacs would never make a good program. And what's the internet without the rick-roll? Do you pine for the days when men were men and wrote their own device drivers? I have an ego the size of a small planet. I'm always right. This time I'm just even more right than usual. If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. Is "I hope you all die a painful death" too strong? Most days I wake up thinking I'm the luckiest bastard alive. My name is Linus Torvalds and I am your god. You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now.
就这样,您已经拥有了一个完美排序的 quotes.txt
文件。比这更简单的排序方式没有了吧。
以下是 Is_Sorted
的规格
function Is_Sorted (Container : Vector) return Boolean;
使用这个方便的功能来判断向量是否已排序。如果已排序,它将返回 Boolean
TRUE,否则返回 Boolean
FALSE。以下是它的工作原理
if not A_Sorter.Is_Sorted (Container => Quotes) then
IO.Put_Line ("We have not yet sorted the Quotes vector");
end if;
A_Sorter.Sort (Container => Quotes);
if A_Sorter.Is_Sorted (Container => Quotes) then
IO.Put_Line ("Now we have sorted the Quotes vector");
end if;
程序的输出结果为
We have no yet sorted the Quotes vector Now we have sorted the Quotes vector
我相信以上结果对你来说并不意外。
但是,如果向量已经排序,然后添加一个新元素呢?Is_Sorted
还会返回 Boolean
TRUE 吗?将以下代码添加到上面的代码中,然后看看结果
Quotes.Append (New_Item => To_Unbounded_String ("New stuff!"));
if not A_Sorter.Is_Sorted (Container => Quotes) then
IO.Put_Line ("The vector is no longer sorted");
end if;
现在输出看起来像这样
We have no yet sorted the Quotes vector Now we have sorted the Quotes vector The vector is no longer sorted
正如预期的那样,如果对向量进行了更改,Is_Sorted
函数将返回 Boolean
FALSE,但这并不是通过某种内部“标志”来实现的。不,FALSE 值是通过简单地遍历整个向量来发现的,同时检查位置 i + 1 是否小于位置 i。如果是这种情况,则 Is_Sorted
返回 Boolean
FALSE。
对于大型向量来说,这效率并不高。因此,当然,你应该只在绝对必要时使用 Is_Sorted
。
以下是 Merge
的规格
procedure Merge (Target : in out Vector; Source : in out Vector);
Merge
很特别。它将 Target
与 Source
合并,并将 Source
设为空。特别之处在于它不会对结果向量进行排序,而这正是我们对 Merge
的预期,因为它是 Generic_Sorting
的一部分。如果你希望 Merge
的结果是一个排序的向量,那么在调用 Merge
之前,你必须对 Target
和 Source
进行排序。
让我们看看它是如何工作的。将此添加到 Quotes
程序的声明部分
Foo_Bar : Vector;
package A_Sorter is new Generic_Sorting;
以及将其添加到主体中
Foo_Bar.Append (New_Item => To_Unbounded_String ("Foo"));
Foo_Bar.Append (New_Item => To_Unbounded_String ("Bar"));
A_Sorter.Sort (Container => Foo_Bar);
A_Sorter.Sort (Container => Quotes);
A_Sorter.Merge (Target => Foo_Bar,
Source => Quotes);
for i in Foo_Bar.First_Index .. Foo_Bar.Last_Index loop
SUIO.Put_Line (Item => Foo_Bar.Element (Index => i));
end loop;
IO.Put_Line (Item => "Length of Quotes:" & Quotes.Length'Img);
此程序的输出是
An infinite number of monkeys typing into GNU emacs would never make a good program. And what's the internet without the rick-roll? Bar Do you pine for the days when men were men and wrote their own device drivers? Foo I have an ego the size of a small planet. I'm always right. This time I'm just even more right than usual. If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. Is "I hope you all die a painful death" too strong? Most days I wake up thinking I'm the luckiest bastard alive. My name is Linus Torvalds and I am your god. You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. Length of Quotes: 0
请注意,两个向量的内容是如何合并到 Target
向量中的,以及 Quotes
向量在合并后是如何变为空的。让我们删除 A_Sorter.Sort (Container => Foo_Bar);
行,看看会发生什么
An infinite number of monkeys typing into GNU emacs would never make a good program. And what's the internet without the rick-roll? Foo Bar Do you pine for the days when men were men and wrote their own device drivers? I have an ego the size of a small planet. I'm always right. This time I'm just even more right than usual. If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. Is "I hope you all die a painful death" too strong? Most days I wake up thinking I'm the luckiest bastard alive. My name is Linus Torvalds and I am your god. You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. Length of Quotes: 0
正如你所看到的,Target
向量不再是排序的。
在使用 Merge
时,务必记住这一点:Target
和 Source
向量在调用 Merge
之前都必须排序,否则你将得到一个未排序的向量作为结果。(如果你想要的只是这个结果,那么使用以下过程之一可能更快: Append, Insert, Prepend。或者你可以简单地使用 &
函数来完成任务。)
如前所述,可以使用多种不同的技术来连接两个向量/向量元素,例如 append, prepend, insert 和 merge,但还有“&”运算符。& 函数的规格如下
function "&" (Left, Right : Vector) return Vector;
function "&" (Left : Vector; Right : Element_Type) return Vector;
function "&" (Left : Element_Type; Right : Vector) return Vector;
function "&" (Left, Right : Element_Type) return Vector;
你可以将一个向量连接到另一个向量,将元素连接到向量,将向量连接到元素,最后将两个元素连接起来。所有 4 个函数都返回一个向量。
让我们看看它是如何工作的。将此添加到程序的声明部分
Foo : Vector;
Bar : Vector;
以及将其添加到主体中
-- This is similar to:
-- Foo.Append (To_Unbounded_String ("Foo 1"));
Foo := Foo & To_Unbounded_String ("Foo 1");
for i in Foo.First_Index .. Foo.Last_Index loop
SUIO.Put_Line (Item => Foo.Element (Index => i));
end loop;
IO.New_Line;
-- This is similar to:
-- Quotes.Append (Foo);
Quotes := Quotes & Foo;
for i in Quotes.First_Index .. Quotes.Last_Index loop
SUIO.Put_Line (Item => Quotes.Element (Index => i));
end loop;
IO.New_Line;
-- This is similar to:
-- Bar.Append (To_Unbounded_String ("Bar 1"));
-- Bar.Append (To_Unbounded_String ("Bar 2"));
Bar := To_Unbounded_String ("Bar 1") & To_Unbounded_String ("Bar 2");
for i in Bar.First_Index .. Bar.Last_Index loop
SUIO.Put_Line (Item => Bar.Element (Index => i));
end loop;
IO.New_Line;
-- This is similar to:
-- Foo.Prepend (To_Unbounded_String ("Foo 0"));
Foo := To_Unbounded_String ("Foo 0") & Foo;
for i in Foo.First_Index .. Foo.Last_Index loop
SUIO.Put_Line (Item => Foo.Element (Index => i));
end loop;
输出如下:
Foo 1 I have an ego the size of a small planet. My name is Linus Torvalds and I am your god. Do you pine for the days when men were men and wrote their own device drivers? If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. An infinite number of monkeys typing into GNU emacs would never make a good program. Is "I hope you all die a painful death" too strong? Most days I wake up thinking I'm the luckiest bastard alive. I'm always right. This time I'm just even more right than usual. And what's the internet without the rick-roll? Foo 1 Bar 1 Bar 2 Foo 0 Foo 1
虽然“&”运算符按预期工作,但有一个需要注意的地方,John Barnes 在他的 Ada 2005 书中对此进行了说明
结果相同,但由于涉及额外的复制,使用“&”效率较低。
让我们尝试一个简单的例子,我们将 append 和 & 运算符进行比较。首先,我将完整地展示 & 的例子
with Ada.Containers.Vectors; use Ada.Containers;
procedure Somenumbers is
package Container is new Vectors (Natural, Natural);
use Container;
Numbers : Vector;
begin
for i in 0 .. 10000 loop
Numbers := Numbers & i;
end loop;
Numbers := Numbers & Numbers;
for i in 0 .. 10000 loop
Numbers := i & Numbers;
end loop;
end Somenumbers;
在我的计算机上对它进行计时,得到以下结果(10 次运行的平均值)
real 0m1.183s user 0m1.028s sys 0m0.156s
with Ada.Containers.Vectors; use Ada.Containers;
procedure Somenumbers is
package Container is new Vectors (Natural, Natural);
use Container;
Numbers : Vector;
begin
for i in 0 .. 10000 loop
Numbers.Append (New_Item => i);
end loop;
Numbers.Append (New_Item => Numbers);
for i in 0 .. 10000 loop
Numbers.Prepend (New_Item => i);
end loop;
end Somenumbers;
计时结果如下(10 次运行的平均值)
real 0m0.247s user 0m0.244s sys 0m0.002s
如果你关心性能,最好避免使用“&”函数。正如这个简单的基准测试所显示的,与使用“&”相比,有更快的连接向量和/或向量元素的方法。
使用“=”函数检查两个向量是否相等。只有当两个向量长度相等,并且每个索引的元素完全相同时,它们才被视为相等。
要了解它是如何工作的,我们必须将此添加到程序的声明部分
Foo : Vector;
以及将其添加到主体中
if Quotes = Foo then
IO.Put_Line (Item => "Quotes and Foo are equal");
else
IO.Put_Line (Item => "Quotes and Foo are NOT equal");
end if;
Foo.Append (New_Item => Quotes);
if Quotes = Foo then
IO.Put_Line (Item => "Quotes and Foo are equal");
else
IO.Put_Line (Item => "Quotes and Foo are NOT equal");
end if;
结果是
Quotes and Foo are NOT equal Quotes and Foo are equal
这当然是我们预期的结果。
-- Standard Ada library specification -- Copyright (c) 2003-2018 Maxim Reznik <[email protected]> -- Copyright (c) 2004-2016 AXE Consultants -- Copyright (c) 2004, 2005, 2006 Ada-Europe -- Copyright (c) 2000 The MITRE Corporation, Inc. -- Copyright (c) 1992, 1993, 1994, 1995 Intermetrics, Inc. -- SPDX-License-Identifier: BSD-3-Clause and LicenseRef-AdaReferenceManual -- -------------------------------------------------------------------------generic
type
Index_Typeis
range
<>;type
Element_Typeis
private
;with
function
"=" (Left :in
Element_Type; Right :in
Element_Type)return
Booleanis
<>;package
Ada.Containers.Vectorsis
pragma
Preelaborate (Vectors);subtype
Extended_Indexis
Index_Type'Baserange
Index_Type'First - 1 .. Index_Type'Min (Index_Type'Base'Last - 1, Index_Type'Last) + 1; No_Index :constant
Extended_Index := Extended_Index'First;type
Vectoris
tagged
private
;pragma
Preelaborable_Initialization (Vector);type
Cursoris
private
;pragma
Preelaborable_Initialization (Cursor); Empty_Vector :constant
Vector; No_Element :constant
Cursor;function
"=" (Left :in
Vector; Right :in
Vector)return
Boolean;function
To_Vector (Length :in
Count_Type)return
Vector;function
To_Vector (New_Item :in
Element_Type; Length :in
Count_Type)return
Vector;function
"&" (Left :in
Vector; Right :in
Vector)return
Vector;function
"&" (Left :in
Vector; Right :in
Element_Type)return
Vector;function
"&" (Left :in
Element_Type; Right :in
Vector)return
Vector;function
"&" (Left :in
Element_Type; Right :in
Element_Type)return
Vector;function
Capacity (Container :in
Vector)return
Count_Type;procedure
Reserve_Capacity (Container :in
out
Vector; Capacity :in
Count_Type);function
Length (Container :in
Vector)return
Count_Type;procedure
Set_Length (Container :in
out
Vector; Length :in
Count_Type);function
Is_Empty (Container :in
Vector)return
Boolean;procedure
Clear (Container :in
out
Vector);function
To_Cursor (Container : Vector; Index : Extended_Index)return
Cursor;function
To_Index (Position :in
Cursor)return
Extended_Index;function
Element (Container :in
Vector; Index :in
Index_Type)return
Element_Type;function
Element (Position :in
Cursor)return
Element_Type;procedure
Replace_Element (Container :in
out
Vector; Index :in
Index_Type; New_Item :in
Element_Type);procedure
Replace_Element (Container :in
out
Vector; Position :in
Cursor; New_item :in
Element_Type);procedure
Query_Element (Container :in
Vector; Index :in
Index_Type; Process :not
null
access
procedure
(Element :in
Element_Type));procedure
Query_Element (Position :in
Cursor; Process :not
null
access
procedure
(Element :in
Element_Type));procedure
Update_Element (Container :in
out
Vector; Index :in
Index_Type; Process :not
null
access
procedure
(Element :in
out
Element_Type));procedure
Update_Element (Container :in
out
Vector; Position :in
Cursor; Process :not
null
access
procedure
(Element :in
out
Element_Type));procedure
Move (Target :in
out
Vector; Source :in
out
Vector);procedure
Insert (Container :in
out
Vector; Before :in
Extended_Index; New_Item :in
Vector);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Vector);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Vector; Position :out
Cursor);procedure
Insert (Container :in
out
Vector; Before :in
Extended_Index; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; New_Item :in
Element_Type; Position :out
Cursor; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Extended_Index; Count :in
Count_Type := 1);procedure
Insert (Container :in
out
Vector; Before :in
Cursor; Position :out
Cursor; Count :in
Count_Type := 1);procedure
Prepend (Container :in
out
Vector; New_Item :in
Vector);procedure
Prepend (Container :in
out
Vector; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Append (Container :in
out
Vector; New_Item :in
Vector);procedure
Append (Container :in
out
Vector; New_Item :in
Element_Type; Count :in
Count_Type := 1);procedure
Insert_Space (Container :in
out
Vector; Before :in
Extended_Index; Count :in
Count_Type := 1);procedure
Insert_Space (Container :in
out
Vector; Before :in
Cursor; Position :out
Cursor; Count :in
Count_Type := 1);procedure
Delete (Container :in
out
Vector; Index :in
Extended_Index; Count :in
Count_Type := 1);procedure
Delete (Container :in
out
Vector; Position :in
out
Cursor; Count :in
Count_Type := 1);procedure
Delete_First (Container :in
out
Vector; Count :in
Count_Type := 1);procedure
Delete_Last (Container :in
out
Vector; Count :in
Count_Type := 1);procedure
Reverse_Elements (Container :in
out
Vector);procedure
Swap (Container :in
out
Vector; I :in
Index_Type; J :in
Index_Type);procedure
Swap (Container :in
out
Vector; I :in
Cursor; J :in
Cursor);function
First_Index (Container :in
Vector)return
Index_Type;function
First (Container :in
Vector)return
Cursor;function
First_Element (Container :in
Vector)return
Element_Type;function
Last_Index (Container :in
Vector)return
Extended_Index;function
Last (Container :in
Vector)return
Cursor;function
Last_Element (Container :in
Vector)return
Element_Type;function
Next (Position :in
Cursor)return
Cursor;procedure
Next (Position :in
out
Cursor);function
Previous (Position :in
Cursor)return
Cursor;procedure
Previous (Position :in
out
Cursor);function
Find_Index (Container :in
Vector; Item :in
Element_Type; Index :in
Index_Type := Index_Type'First)return
Extended_Index;function
Find (Container :in
Vector; Item :in
Element_Type; Position :in
Cursor := No_Element)return
Cursor;function
Reverse_Find_Index (Container :in
Vector; Item :in
Element_Type; Index :in
Index_Type := Index_Type'Last)return
Extended_Index;function
Reverse_Find (Container :in
Vector; Item :in
Element_Type; Position :in
Cursor := No_Element)return
Cursor;function
Contains (Container :in
Vector; Item :in
Element_Type)return
Boolean;function
Has_Element (Position :in
Cursor)return
Boolean;procedure
Iterate (Container :in
Vector; Process :not
null
access
procedure
(Position :in
Cursor));procedure
Reverse_Iterate (Container :in
Vector; Process :not
null
access
procedure
(Position :in
Cursor));generic
with
function
"<" (Left :in
Element_Type; Right :in
Element_Type)return
Booleanis
<>;package
Generic_Sortingis
function
Is_Sorted (Container :in
Vector)return
Boolean;procedure
Sort (Container :in
out
Vector);procedure
Merge (Target :in
out
Vector; Source :in
out
Vector);end
Generic_Sorting;private
type
Vectoris
tagged
null
record
; Empty_Vector :constant
Vector := (null
record
);type
Cursoris
null
record
; No_Element :constant
Cursor := (null
record
);end
Ada.Containers.Vectors;
外部示例
[编辑源代码]- 在以下位置搜索
Ada.Containers.Vectors
的示例:Rosetta Code,GitHub (gists),任何 Alire 包 或者 本维基教科书。 - 在以下位置搜索与
Ada.Containers.Vectors
相关的帖子:Stack Overflow,comp.lang.ada 或 任何与 Ada 相关的页面。
FSF GNAT
- 规格:a-convec.ads
- 主体:a-convec.adb
drake