跳至内容

Ada 编程/常量

来自维基教科书,为开放世界提供开放书籍

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

变量 和常量都存储数据。它们之间的区别在于,存储在变量中的数据可以在程序执行期间更改,而存储在常量中的数据则不能更改。两者都必须声明为特定类型,但只有常量需要在声明时分配(初始化)值(数据)。

在 Ada 中,变量和常量实际上被称为对象(Ada RM 3.3 [注释])。这与面向对象编程无关。Ada 中的变量是一个对象,它被声明为给定类型,并具有可选的初始值。常量是相同的,只是声明现在包含保留字 **constant**,并且初始值不再是可选的,而是必需的。

变量和常量都在块的声明部分声明。

变量和常量的声明方式如下

X, Y: T := Some_Value;  -- Initialization optional
C: constant T := Some_Value;

X 和 Y 的初始化值是分别针对每个值计算的——因此,如果这是一个函数调用,它们可能具有不同的值。

最好将不更改的本地对象声明为常量。

有关如何命名变量和常量的通用建议,请参阅 此处

常量是帮助使程序更可靠和可维护的重要工具。常量与变量一样,是对特定 type 数据的引用,但它们与变量的不同之处在于它们的常量性质。变量是变化的,常量是恒定的。当声明和初始化时,它们引用的数据是静态的,不能再更改。如果您尝试更改常量,编译器将大声抱怨。

让我们看看如何声明和初始化常量

Name : constant String (1 .. 12) := "Thomas Løcke";

Name : constant String := "Thomas Løcke";

与变量唯一的区别在于保留 constant 关键字,以及常量 **必须** 在声明时初始化的事实。除此之外,声明和初始化常量与变量完全相同,并且适用于所有 types

A_Array : constant array (1 .. 3) of Integer := (1, 2, 3);
An_Positive : constant Positive := 10;

type Colors is (Red, Blue, Green);
Color : constant Colors := Red;
type Person is record
   Name : String (1 .. 12);
   Age : Natural;
end record;
B_Person : constant Person := (Name => "Thomas Løcke", Age => 37);

如果您尝试在程序中更改常量,编译器将使用类似以下消息进行抱怨

 constants.adb:15:04: left hand side of assignment must be a variable
 gnatmake: "/path/to/constants.adb" compilation error

当数据应该保持静态时,您应该使用常量。不要低估人类犯错的能力,例如用以下方式声明 Pi

Pi : Float := 3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511;

Pi 应该在程序执行期间更改吗?可能不会。那么为什么要允许这样做呢?为什么要冒错误发生这种风险呢?相反,请执行以下操作

Pi : constant := 3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511;

好了。现在您可以放心,无论发生什么,Pi 都将是 Pi。

顺便说一句,对于 Pi 本身(以及最著名的常量),您无需自己声明它:它已经在 Ada.Numerics 包中为您完成了。

命名数字

[编辑 | 编辑源代码]

细心的读者会注意到,上面使用的 Pi 常量是在没有 type 的情况下声明的。这种常量被称为命名数字 (3.3 [注释]),它们的 type 被称为通用类型 (3.4.1 [注释])。有四种 通用类型

  • universal_integer
  • universal_real
  • universal_fixed
  • universal_access

您不能显式地声明一个对象为 universal_integer 类型。但您可以做的是声明一个对象为常量 命名数字。与从这些类型派生的类型(例如 IntegerFloat)不同,这些类型可以采用任何大小或精度的值。通用类型不受从它们派生的类型所绑定的相同约束

Large_Int : Integer := 4_294_967_296;        --  Value not in range on a 32 bit machine
Named_Large_Int : constant := 4_294_967_296; --  OK on a 32 bit machine

Named_Large_Int 常量是 universal_integer 类型,因为文字 4_294_967_296 是一个整数。如果我们写

Named_Real : constant := 4.294_967_296;

那么 Named_Realuniversal_real 类型,因为文字 4.294_967_296 包含一个点 (2.4 [注释])。

高级主题:常量是常量吗?

[编辑 | 编辑源代码]

愚蠢的问题:什么是“常量”?

procedure Swap (X, Y: in out T) is
  Temp: constant T := X;
begin
  X := Y;
  Y := Temp;
end Swap;

在 Swap 示例中,X 的临时副本当然是一个常量——我们没有触碰它。但是(你会说这微不足道),在 Swap 结束时的构建期间或销毁期间,它并不是一个常量。

等等!

在以下内容中,您将了解有关“常量”(带有关键字 constant 的对象)的一些惊人事实。

首次疑问

[编辑 | 编辑源代码]

让我们先对常量性产生怀疑。

受控类型(RM 7.6)允许程序员通过三种操作访问构建、复制和销毁的过程:Initialize、Adjust 和 Finalize。

给定受控类型 T。

X: constant T := F;

在构建 X 并使用 F 的副本进行初始化后,操作 Adjust 被调用,参数模式为 in out,用于常量对象 X。(由于给定了初始值,因此不会调用 Initialize。)

在 Ada.*_IO(RM A.6-A.10)中,Create 和 Close 的文件参数的模式为 in out,而 Read 和 Write(分别是 Put 和 Get)的模式为 in。但这些操作中的每一个都会改变文件参数的状态。

这反映了一种设计,其中(内部)File 对象的“状态”表示 File 对象的打开状态,而不是(外部)文件的内容。同意这种选择是可以争论的,但它在整个 I/O 包中是一致的。

假定的实现模型隐含了一定程度的间接性,其中 File 参数表示一个引用,当文件关闭时该引用本质上是“null”,而当文件打开时则是非空。其他一切都被埋在某种神秘的国度中,其状态没有反映在 File 参数的模式中。

这是任何具有指针参数的语言的症状,这些参数的模式对更新指向的对象的能力没有影响。

所以

对象不是常量,至少在构建和销毁期间不是。

但正如您在 Ada.*_IO 中看到的那样,常量对象确实也可以在生命周期内改变其状态。(in 参数就像子程序中的一个常量。)

**“constant 关键字表示值永远不会改变”——这对 Ada 来说是一个错误的陈述**(细节很痛苦);它对基本类型和其他一些类型是正确的,但对大多数复合类型不正确。

您可以在 Ada RM 中找到一些关于此的有趣陈述:RM 3.3(13/3, 25.1/3), 13.9.1(13/3)。相应的术语是 *不可变限制* 和 *固有可变*。

指针示例

[编辑 | 编辑源代码]
Pointer:
declare
  type Rec is record
    I: access Integer;
  end record;
  Ob: constant Rec := (I => new Integer'(42));
  -- Ob.I is constant, but not Ob.I.all!
begin
  Ob.I.all := 53;
end Pointer;

在这里,您一定会同意我的观点,这很清楚明了。

但如果内部指针访问整个对象本身呢?

Reflexive:
declare
  type Rec is limited record  -- immutably limited
    -- Rosen technique (only possible on immutably limited types);
    -- Ref is a variable view (Rec is aliased since it is limited).
    Ref: access Rec := Rec'Unchecked_Access;
    I  : Integer;
  end record;
  Ob: constant Rec := (I => 42, Ref => <>);
begin
  Ob.I := 53;  -- illegal
  Ob.Ref.I := 53;  -- erroneous until Ada 2005, legal for Ada 2012
end Reflexive;

该对象必须是有限的,因为复制会破坏引用。

Ada 2012 AARM 13.9.1(14.e/3):当实际对象是常量时,Rosen 技巧(以 Jean Pierre Rosen 命名)不再是错误的,但这是最佳实践。

这适用于在对象生命周期的某个点上一定存在一个变量视图,您可以将其“保存起来”以供以后使用。这当然不会“错误地”发生,程序员是故意这么做的。

package Controlled is
  type Rec is new Ada.Finalization.Controlled with record
    Ref: access Rec;  -- variable view
    I  : Integer;
  end record;
  overriding procedure Initialize (T: in out Rec);
  overriding procedure Adjust     (T: in out Rec);
  -- "in out" means they see a variable view of the object
  -- (even if it's a constant)
end Controlled;

您可以像这样使用它

declare
  function Create return Controlled.Rec isend Create;
  Ob: constant Controlled.Rec := Create;
  -- here, Adjust works on a constant on an assignment operation
begin
  Ob.Ref.I := 53;  -- Ob.I := 53;  illegal
end;

将返回对象 Create 的副本赋值给 Ob,对象 Create 已完成。现在,组件 Ref 指向了一个不再存在的对象。(注意赋值语句赋值操作之间的区别。)Adjust 必须纠正错误的目标。

Create 可能看起来像这样

function Create return Controlled.Rec is
  X: Controlled.Rec;  -- Initialize called
begin
  return X;
end Create;

变量 X 在声明时没有初始值,因此调用 Initialize。Create 返回此自引用对象。

这是包体

package body Controlled is
  procedure Initialize (T: in out Rec) is
  begin
    T := (Ada.Finalization.Controlled with
          Ref => T'Unchecked_Access,
          I   => 42);
  end Initialize;
  procedure Adjust (T: in out Rec) is
  begin
    T.Ref := T'Unchecked_Access;
  end Adjust;
end Controlled;

如果在对象声明中没有给出初始值,则仅调用 Initiallize。T 被视为别名,以便组件 Ref 是对象本身的变量视图。

示例任务和受保护对象

[编辑 | 编辑源代码]

任务是主动对象,即每个任务都有自己的控制线程:所有任务并发运行。任务通过调用其入口点之一(大致对应于子程序调用)与其他任务进行通信。

由于 Ada 是一种面向块的语言,因此任务可以定义为数组和记录的组件,而这些组件可以是常量。

task type TT is
  entry Set (I: Integer);
end TT;
task body TT is      -- local objects are not
  I: Integer := 42;  -- considered part of constant
begin
  accept Set (I: Integer) do
    TT.I := I;
  end Set;
end TT;
type Rec is record
  T: TT;  -- active object that changes state (even if "constant")
end record;
Ob: constant Rec := (T => <>);
Ob.T.Set (53);  -- execution of an entry of a constant task

受保护对象与任务非常相似。

protected type Prot is
  procedure Set (I: Integer);
private
  I: Integer := 42;  -- *
end Prot;
protected body Prot is
  procedure Set (I: Integer) is
  begin
    Prot.I := I;  -- Prot.I is *
  end Set;
end Prot;
type Rec is limited record
  Ref: access Rec := Rec'Unchecked_Access;  -- reflexive
  P  : Prot;
end record;
Ob: constant Rec := (others => <>);
Ob.P.Set (53);      -- illegal
Ob.Ref.P.Set (53);  -- variable view required

常量是这样

[编辑 | 编辑源代码]

如果声明一个对象为“常量”,其含义只有两方面

  • 该对象不能用于赋值(如果它尚未受限)。
  • 该对象只能用作输入参数。

在任何情况下,这都不意味着对象的逻辑状态是不可变的。事实上,这种类型应该是私有的,这意味着客户端对对象内部发生的事情没有任何影响。提供者负责正确的行为。不可变受限受控对象固有可变的。

另请参阅

[编辑 | 编辑源代码]

维基教科书

[编辑 | 编辑源代码]

Ada 参考手册

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