跳转到内容

Ada 编程/技巧

来自 Wikibooks,开放世界中的开放书籍

Ada. Time-tested, safe and secure.
Ada。经久耐用、安全可靠。

类型的完整声明可以延迟到单元体中

[编辑 | 编辑源代码]

通常,您可能希望更改私有类型的内部结构。这反过来又需要修改作用于它的算法。如果类型在单元规范中完成,则即使使用IDE,编辑和重新编译这两个文件也很麻烦,但这却是某些程序员学会忍受的事情。

事实证明,您不必这样做。在ARM中漫不经心地提到,并且通常在教程中跳过,事实上私有类型可以在单元体本身中完成,这使得它们更接近相关的代码,并节省了规范的重新编译,以及每个依赖它的单元。这看起来可能是一件小事,对于小型项目来说确实如此。但是,如果您有一个需要数十次调整的难以合作的类型,或者您的依赖关系图深度很大,那么节省的时间和烦恼就会迅速累积。

此外,这种结构在编写共享库时非常有用,因为它允许更改类型的实现,同时仍然提供兼容的ABI

代码示例

package Private_And_Body is

  type Private_Type is limited private;

  -- Operations...

private
  type Body_Type;   -- incomplete type declaration completed in the body
  type Private_Type is access Body_Type;
end Private_And_Body;

公共部分中的类型是隐藏类型的访问。这有一个缺点,即内存管理必须由包实现提供。这就是为什么Private_Type是一个受限类型,客户端将不允许复制访问值,以防止悬空引用。

通常,必须在规范中给出完整的类型定义,因为编译器必须知道要为对象分配多少空间才能生成使用此类型的代码。而敏锐的读者会注意到,在这种情况下也为 Private_Type 给出了完整的类型定义:它是对某些其他(尽管不完整)类型的访问,并且访问值的尺寸是已知的。这就是为什么 Body_Type 的完整类型定义可以移动到主体中的原因。

围绕Private_Type构建的结构有时被称为不透明指针

通过泛型实现 Lambda 演算

[编辑 | 编辑源代码]

假设您已决定自己编写集合类型。您可以向其中添加内容,从中删除内容,并且您希望允许用户将其中的所有成员应用于某些任意函数。但是作用域规则似乎与您作对,迫使几乎所有内容都成为全局的。

心理障碍是,给出的泛型大多数示例都是包,而 Set 包已经是泛型的。在这种情况下,解决方案是使 Apply_To_All 过程也成为泛型的;也就是说,嵌套泛型。包内的泛型过程存在于一个奇怪的作用域模糊状态中,其中实例化时作用域内的任何内容都可以由实例化使用,并且正式时通常作用域内的任何内容都可以由正式访问。最终结果是相关的范围障碍不再适用。它不是完整的 Lambda 演算,只是其中最有用的一部分。

generic
  type Element is private;
package Sets is
  type Set is private;
   [..]
  generic
    with procedure Apply_To_One (The_Element : in out Element);
  procedure Apply_To_All (The_Set : in out Set);
end Sets;

有关 Ada 中函数式编程的视图,请参阅。[1]

编译器消息

[编辑 | 编辑源代码]

不同的编译器可以以不同的方式诊断不同的内容,或者使用不同的消息诊断相同的内容,等等。拥有两个编译器在手可能很有用。

选定的组件
当源程序包含诸如 Foo.Bar 之类的构造时,您可能会看到类似于“选定的组件“Bar””或可能类似于“选定的组件“Foo””的消息。这些短语可能令人困惑,因为一个指的是 Foo,而另一个指的是 Bar。但它们都是正确的。原因是selected_component是 Ada 语法中的一个项目(4.1.3 选定的组件 (带注释的))。它表示所有内容:前缀、点和选择器名称。在 Foo.Bar 示例中,它们分别对应于 Foo、'.' 和 Bar。在编译器消息中查找更多语法单词,例如“前缀”,并将它们与消息中引用的标识符关联起来。
例如,如果您将以下代码提交给编译器,
with Pak;
package Foo is
   type T is new Pak.Bar;  --  Oops, Pak is generic!
end Foo;
编译器可能会打印有关前缀组件的诊断消息:Foo 的作者认为 Pak 表示一个包,但实际上它是一个泛型包的名称。(它需要首先实例化;然后实例名称是一个合适的前缀。)

通用整数

[编辑 | 编辑源代码]

所有整数文字以及一些属性(如 'Length)都属于匿名类型universal_integer,它包含无限的数学整数集。命名数字属于此类型,并精确计算(除了机器存储限制之外没有溢出),例如

 Very_Big: constant := 10**1_000_000 - 1;

由于universal_integer没有运算符,因此在此示例中,其值将转换为root_integer(另一种匿名类型),执行计算,并将结果再次转换回universal_integer

通常,当在某些表达式中使用universal_integer的值时,会将其隐式转换为适当的类型。因此,表达式 not A'Length 很好;A'Length 的值被解释为模整数,因为not 只能应用于模整数(当然需要上下文来确定是指哪种模整数类型)。此功能可能导致陷阱。考虑

   type Ran_6 is range 1 .. 6;
   type Mod_6 is mod 6;

然后

   --  1
   if A'Length in Ran_6 then  --  OK--  2
   if not A'Length in Ran_6 then  --  not OK--  this is the same as
   if (not A'Length) in Ran_6 then  --  not OK--  3
   if A'Length in 1 .. 6 then  --  OK--  4
   if not A'Length in 1 .. 6 then  --  not OK--  5
   if A'Length in Mod_6 then  --  OK?--  6
   if not A'Length in Mod_6 then  --  OK?
第二个条件无法编译,因为 in 左侧的表达式与右侧的类型不兼容。请注意,not 的优先级高于 in。它不会否定整个成员资格测试,而只是 A'Length
第四个条件以各种方式失败。
第六个条件可能没问题,因为 notA'Length 转换为模值,如果该值被模类型 Mod_6 覆盖,则可以。
GNAT GPL 2009 分别给出了这些诊断
error: incompatible types
error: operand of not must be enclosed in parentheses
warning: not expression should be parenthesized here
避免这些问题的一种方法是为成员资格测试使用 not in(顺便说一句,这是语言预期形式),
   if A'Length not in Ran_6 then  --  OK
参见

Text_IO 问题

[编辑 | 编辑源代码]

从文本文件读取一系列行的规范方法是使用标准过程Ada.Text_IOGet_Line。当到达输入结束时,Get_Line 将失败,并引发异常 End_Error。一些程序将使用来自Ada.Text_IO 的另一个函数来防止这种情况并测试 End_of_File。但是,这并不总是最佳选择,例如在 comp.lang.ada 上的 Get_Line 新闻组讨论中所解释的那样。

一个可行的解决方案是使用异常处理程序

  declare
     The_Line: String(1..100);
     Last: Natural;
  begin
     loop
        Text_IO.Get_Line(The_Line, Last);
        --  do something with The_Line ...
     end loop;
  exception
     when Text_IO.End_Error =>
        null;
  end;

函数 End_of_File RM A.10.1(34) 只要文件遵循 Ada 假设的文本文件的规范形式,就可以正常工作,当文件由 Ada.Text_IO 写入时,情况总是如此。这种规范形式要求在文件末尾之前,紧跟着一个 End_of_Line 标记,然后是一个 End_of_Page 标记。

如果文件是由任何其他方式生成的,它通常不会具有这种规范形式,因此对 End_of_File 的测试将失败。这就是为什么在这些情况下必须使用异常 End_Error(它始终有效)的原因。

在 Windows 上使用 GNAT 时,对来自 Ada.Real_Time 的子程序的调用可能需要特别注意。(例如,当肯定已经过了一些时间时,Real_Time.Clock 函数似乎返回的值表明两次调用之间没有经过时间。)其原因据报道是在程序中没有其他实时特性时,运行时支持的初始化缺失。[2] 作为临时解决方案,建议插入

delay 0.0;

在任何使用 Real_Time 服务之前。

栈大小

[编辑 | 编辑源代码]

对于某些实现,特别是 GNAT,了解栈大小操作将对您有利。使用 GNAT 工具和标准设置生成的的可执行文件可能会达到栈大小限制。如果是这样,操作系统可能会允许设置更高的限制。使用 GNU/Linux 和 Bash 命令行 shell,尝试

$ ulimit -s [some number]

当仅将 -s 传递给 ulimit 时,将打印当前值。

参考文献

[编辑 | 编辑源代码]
  1. Ada 中的功能编程?,作者:Chris Okasaki
  2. Vincent Celier (2010-03-08)。“计时代码块”。comp.lang.ada(网络链接)。于 2010 年 3 月 11 日检索。“现在已经了解并修正了 GNAT 开发版本中的问题。” Usenet 文章转发了来自 AdaCore 的此信息。

另请参阅

[编辑 | 编辑源代码]

维基教科书

[编辑 | 编辑源代码]

Ada 参考手册

[编辑 | 编辑源代码]
华夏公益教科书