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
类型。但您可以做的是声明一个对象为常量 命名数字
。与从这些类型派生的类型(例如 Integer
和 Float
)不同,这些类型可以采用任何大小或精度的值。通用类型不受从它们派生的类型所绑定的相同约束
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_Real
是 universal_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
Recis
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
Recis
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 2012end
Reflexive;
该对象必须是有限的,因为复制会破坏引用。
Ada 2012 AARM 13.9.1(14.e/3):当实际对象是常量时,Rosen 技巧(以 Jean Pierre Rosen 命名)不再是错误的,但这是最佳实践。
这适用于在对象生命周期的某个点上一定存在一个变量视图,您可以将其“保存起来”以供以后使用。这当然不会“错误地”发生,程序员是故意这么做的。
package
Controlledis
type
Recis
new
Ada.Finalization.Controlledwith
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
Createreturn
Controlled.Recis
…end
Create; Ob:constant
Controlled.Rec := Create; -- here, Adjust works on a constant on an assignment operationbegin
Ob.Ref.I := 53; -- Ob.I := 53; illegalend
;
将返回对象 Create 的副本赋值给 Ob,对象 Create 已完成。现在,组件 Ref 指向了一个不再存在的对象。(注意赋值语句和赋值操作之间的区别。)Adjust 必须纠正错误的目标。
Create 可能看起来像这样
function
Createreturn
Controlled.Recis
X: Controlled.Rec; -- Initialize calledbegin
return
X;end
Create;
变量 X 在声明时没有初始值,因此调用 Initialize。Create 返回此自引用对象。
这是包体
package
body
Controlledis
procedure
Initialize (T:in out
Rec)is
begin
T := (Ada.Finalization.Controlledwith
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
TTis
entry
Set (I: Integer);end
TT;task
body
TTis
-- local objects are not I: Integer := 42; -- considered part of constantbegin
accept
Set (I: Integer)do
TT.I := I;end
Set;end
TT;type
Recis
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
Protis
procedure
Set (I: Integer);private
I: Integer := 42; -- *end
Prot;protected
body
Protis
procedure
Set (I: Integer)is
begin
Prot.I := I; -- Prot.I is *end
Set;end
Prot;type
Recis
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
如果声明一个对象为“常量”,其含义只有两方面
- 该对象不能用于赋值(如果它尚未受限)。
- 该对象只能用作输入参数。
在任何情况下,这都不意味着对象的逻辑状态是不可变的。事实上,这种类型应该是私有的,这意味着客户端对对象内部发生的事情没有任何影响。提供者负责正确的行为。不可变受限和受控对象是固有可变的。
- 3.3:对象和命名数字 [注释]
- 13.9.1:数据有效性 [注释]