Ada 编程/技巧
通常,您可能希望更改私有类型的内部结构。这反过来又需要修改作用于它的算法。如果类型在单元规范中完成,则即使使用IDE,编辑和重新编译这两个文件也很麻烦,但这却是某些程序员学会忍受的事情。
事实证明,您不必这样做。在ARM中漫不经心地提到,并且通常在教程中跳过,事实上私有类型可以在单元体本身中完成,这使得它们更接近相关的代码,并节省了规范的重新编译,以及每个依赖它的单元。这看起来可能是一件小事,对于小型项目来说确实如此。但是,如果您有一个需要数十次调整的难以合作的类型,或者您的依赖关系图深度很大,那么节省的时间和烦恼就会迅速累积。
此外,这种结构在编写共享库时非常有用,因为它允许更改类型的实现,同时仍然提供兼容的ABI。
代码示例
packagePrivate_And_BodyistypePrivate_Typeislimitedprivate; -- Operations...privatetypeBody_Type; -- incomplete type declaration completed in the bodytypePrivate_TypeisaccessBody_Type;endPrivate_And_Body;
公共部分中的类型是隐藏类型的访问。这有一个缺点,即内存管理必须由包实现提供。这就是为什么Private_Type是一个受限类型,客户端将不允许复制访问值,以防止悬空引用。
通常,必须在规范中给出完整的类型定义,因为编译器必须知道要为对象分配多少空间才能生成使用此类型的代码。而敏锐的读者会注意到,在这种情况下也为 Private_Type 给出了完整的类型定义:它是对某些其他(尽管不完整)类型的访问,并且访问值的尺寸是已知的。这就是为什么 Body_Type 的完整类型定义可以移动到主体中的原因。
围绕Private_Type构建的结构有时被称为不透明指针。
假设您已决定自己编写集合类型。您可以向其中添加内容,从中删除内容,并且您希望允许用户将其中的所有成员应用于某些任意函数。但是作用域规则似乎与您作对,迫使几乎所有内容都成为全局的。
心理障碍是,给出的泛型大多数示例都是包,而 Set 包已经是泛型的。在这种情况下,解决方案是使 Apply_To_All 过程也成为泛型的;也就是说,嵌套泛型。包内的泛型过程存在于一个奇怪的作用域模糊状态中,其中实例化时作用域内的任何内容都可以由实例化使用,并且正式时通常作用域内的任何内容都可以由正式访问。最终结果是相关的范围障碍不再适用。它不是完整的 Lambda 演算,只是其中最有用的一部分。
generictypeElementisprivate;packageSetsistypeSetisprivate; [..]genericwithprocedureApply_To_One (The_Element :inoutElement);procedureApply_To_All (The_Set :inoutSet);endSets;
有关 Ada 中函数式编程的视图,请参阅。[1]
不同的编译器可以以不同的方式诊断不同的内容,或者使用不同的消息诊断相同的内容,等等。拥有两个编译器在手可能很有用。
选定的组件- 当源程序包含诸如
Foo.Bar之类的构造时,您可能会看到类似于“选定的组件“Bar””或可能类似于“选定的组件“Foo””的消息。这些短语可能令人困惑,因为一个指的是Foo,而另一个指的是Bar。但它们都是正确的。原因是selected_component是 Ada 语法中的一个项目(4.1.3 选定的组件 (带注释的))。它表示所有内容:前缀、点和选择器名称。在Foo.Bar示例中,它们分别对应于Foo、'.' 和Bar。在编译器消息中查找更多语法单词,例如“前缀”,并将它们与消息中引用的标识符关联起来。
- 例如,如果您将以下代码提交给编译器,
withPak;packageFooistypeTisnewPak.Bar; -- Oops, Pak is generic!endFoo;
- 编译器可能会打印有关前缀组件的诊断消息:
Foo的作者认为Pak表示一个包,但实际上它是一个泛型包的名称。(它需要首先实例化;然后实例名称是一个合适的前缀。)
所有整数文字以及一些属性(如 'Length)都属于匿名类型universal_integer,它包含无限的数学整数集。命名数字属于此类型,并精确计算(除了机器存储限制之外没有溢出),例如
Very_Big: constant := 10**1_000_000 - 1;
由于universal_integer没有运算符,因此在此示例中,其值将转换为root_integer(另一种匿名类型),执行计算,并将结果再次转换回universal_integer。
通常,当在某些表达式中使用universal_integer的值时,会将其隐式转换为适当的类型。因此,表达式 很好;not A'LengthA'Length 的值被解释为模整数,因为not 只能应用于模整数(当然需要上下文来确定是指哪种模整数类型)。此功能可能导致陷阱。考虑
typeRan_6isrange1 .. 6;typeMod_6ismod6;
然后
-- 1ifA'LengthinRan_6then-- OK … -- 2ifnotA'LengthinRan_6then-- not OK … -- this is the same asif(notA'Length)inRan_6then-- not OK … -- 3ifA'Lengthin1 .. 6then-- OK … -- 4ifnotA'Lengthin1 .. 6then-- not OK … -- 5ifA'LengthinMod_6then-- OK? … -- 6ifnotA'LengthinMod_6then-- OK? …
- 第四个条件以各种方式失败。
- GNAT GPL 2009 分别给出了这些诊断
error: incompatible types error: operand of not must be enclosed in parentheses warning: not expression should be parenthesized here
ifA'LengthnotinRan_6then-- OK …
- 参见
- 2.4 数值字面量 (带注释的),
- 3.6.2 数组类型的操作 (带注释的)), 和
- 4.5 运算符和表达式求值 (带注释的),
- 4.5.2 关系运算符和成员资格测试 (带注释的),
- 成员资格测试
从文本文件读取一系列行的规范方法是使用标准过程Ada.Text_IO。Get_Line。当到达输入结束时,Get_Line 将失败,并引发异常 End_Error。一些程序将使用来自Ada.Text_IO 的另一个函数来防止这种情况并测试 End_of_File。但是,这并不总是最佳选择,例如在 comp.lang.ada 上的 Get_Line 新闻组讨论中所解释的那样。
一个可行的解决方案是使用异常处理程序
declareThe_Line: String(1..100); Last: Natural;beginloopText_IO.Get_Line(The_Line, Last); -- do something with The_Line ...endloop;exceptionwhenText_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 时,将打印当前值。
- ↑ Ada 中的功能编程?,作者:Chris Okasaki
- ↑ Vincent Celier (2010-03-08)。“计时代码块”。comp.lang.ada。 (网络链接)。于 2010 年 3 月 11 日检索。“现在已经了解并修正了 GNAT 开发版本中的问题。” Usenet 文章转发了来自 AdaCore 的此信息。
