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 [註釋])。
愚蠢的問題:什麼是“常量”?
procedureSwap (X, Y:in outT)isTemp:constantT := X;beginX := Y; Y := Temp;endSwap;
在 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:declaretypeRecisrecordI:accessInteger;endrecord; Ob:constantRec := (I =>newInteger'(42)); -- Ob.I is constant, but not Ob.I.all!beginOb.I.all := 53;endPointer;
在這裡,您一定會同意我的觀點,這很清楚明瞭。
但如果內部指標訪問整個物件本身呢?
Reflexive:declaretypeRecislimitedrecord-- immutably limited -- Rosen technique (only possible on immutably limited types); -- Ref is a variable view (Rec is aliased since it is limited). Ref:accessRec := Rec'Unchecked_Access; I : Integer;endrecord; Ob:constantRec := (I => 42, Ref => <>);beginOb.I := 53; -- illegal Ob.Ref.I := 53; -- erroneous until Ada 2005, legal for Ada 2012endReflexive;
該物件必須是有限的,因為複製會破壞引用。
Ada 2012 AARM 13.9.1(14.e/3):當實際物件是常量時,Rosen 技巧(以 Jean Pierre Rosen 命名)不再是錯誤的,但這是最佳實踐。
這適用於在物件生命週期的某個點上一定存在一個變數檢視,您可以將其“儲存起來”以供以後使用。這當然不會“錯誤地”發生,程式設計師是故意這麼做的。
packageControlledistypeRecisnewAda.Finalization.ControlledwithrecordRef:accessRec; -- variable view I : Integer;endrecord;overridingprocedureInitialize (T:in outRec);overridingprocedureAdjust (T:in outRec); -- "in out" means they see a variable view of the object -- (even if it's a constant)endControlled;
您可以像這樣使用它
declarefunctionCreatereturnControlled.Recis…endCreate; Ob:constantControlled.Rec := Create; -- here, Adjust works on a constant on an assignment operationbeginOb.Ref.I := 53; -- Ob.I := 53; illegalend;
將返回物件 Create 的副本賦值給 Ob,物件 Create 已完成。現在,元件 Ref 指向了一個不再存在的物件。(注意賦值語句和賦值操作之間的區別。)Adjust 必須糾正錯誤的目標。
Create 可能看起來像這樣
functionCreatereturnControlled.RecisX: Controlled.Rec; -- Initialize calledbeginreturnX;endCreate;
變數 X 在宣告時沒有初始值,因此呼叫 Initialize。Create 返回此自引用物件。
這是包體
packagebodyControlledisprocedureInitialize (T:in outRec)isbeginT := (Ada.Finalization.ControlledwithRef => T'Unchecked_Access, I => 42);endInitialize;procedureAdjust (T:in outRec)isbeginT.Ref := T'Unchecked_Access;endAdjust;endControlled;
如果在物件宣告中沒有給出初始值,則僅呼叫 Initiallize。T 被視為別名,以便元件 Ref 是物件本身的變數檢視。
任務是主動物件,即每個任務都有自己的控制執行緒:所有任務併發執行。任務透過呼叫其入口點之一(大致對應於子程式呼叫)與其他任務進行通訊。
由於 Ada 是一種面向塊的語言,因此任務可以定義為陣列和記錄的元件,而這些元件可以是常量。
tasktypeTTisentrySet (I: Integer);endTT;taskbodyTTis-- local objects are not I: Integer := 42; -- considered part of constantbeginacceptSet (I: Integer)doTT.I := I;endSet;endTT;typeRecisrecordT: TT; -- active object that changes state (even if "constant")endrecord; Ob:constantRec := (T => <>);
Ob.T.Set (53); -- execution of an entry of a constant task
受保護物件與任務非常相似。
protectedtypeProtisprocedureSet (I: Integer);privateI: Integer := 42; -- *endProt;protectedbodyProtisprocedureSet (I: Integer)isbeginProt.I := I; -- Prot.I is *endSet;endProt;typeRecislimitedrecordRef:accessRec := Rec'Unchecked_Access; -- reflexive P : Prot;endrecord; Ob:constantRec := (others=> <>);
Ob.P.Set (53); -- illegal Ob.Ref.P.Set (53); -- variable view required
如果宣告一個物件為“常量”,其含義只有兩方面
- 該物件不能用於賦值(如果它尚未受限)。
- 該物件只能用作輸入引數。
在任何情況下,這都不意味著物件的邏輯狀態是不可變的。事實上,這種型別應該是私有的,這意味著客戶端對物件內部發生的事情沒有任何影響。提供者負責正確的行為。不可變受限和受控物件是固有可變的。
- 3.3:物件和命名數字 [註釋]
- 13.9.1:資料有效性 [註釋]
