Ada 程式設計/輸入輸出/文字示例
我們中那些有美國朋友的人經常不得不處理華氏溫度標度。雖然世界其他地方已經採用攝氏溫度標度,但美國和其他一些國家仍然忠於華氏度。
雖然在與朋友打交道時,這是一個既小又無關緊要的問題,但它仍然是一個值得擁有自己的程式的問題:華氏度到攝氏度的轉換器。
在我們開始之前,我們需要做一些準備。首先在您的計算機上建立以下目錄/檔案結構
$ mkdir FtoC
$ cd FtoC
$ mkdir exe objects
$ touch ftoc.adb ftoc.gpr
上面應該在 FtoC 目錄中留下以下內容
exe/ objects/ ftoc.adb ftoc.gpr
我們將以以下方式使用這兩個目錄和檔案
- exe/ 這是編譯程式時
ftoc可執行檔案放置的目錄 - objects/ 編譯程式時,編譯器會建立 ALI 檔案、目標檔案和樹檔案。這些檔案將放置在此目錄中。
- ftoc.adb ftoc 程式的主要 Ada 原始檔。
- ftoc.gpr 這是一個 Ada 專案檔案。此檔案控制程式的各種屬性,例如如何編譯它,包含哪些原始碼,將檔案放在哪裡等等。
將此新增到 ftoc.gpr 檔案中
project ftoc is
for Source_Dirs use (".");
for Main use ("ftoc.adb");
for Exec_Dir use "exe";
for Object_Dir use "objects";
package Ide is
for Compiler_Command ("ada") use "/usr/gnat/bin/gnatmake";
end Ide;
package Compiler is
Common_Options := ("-gnatwa",
"-gnaty3abcdefhiklmnoprstux",
"-Wall",
"-O2");
for Default_Switches ("Ada") use Common_Options;
end Compiler;
end ftoc;
請前往 此處 瞭解有關上面專案檔案的不同部分的說明。
專案檔案已完成,我們現在將注意力轉向實際的 ftoc 程式碼。將此新增到 ftoc.adb 檔案中
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
procedure FtoC is
subtype Fahrenheit_Degree_Range is Natural range 0 .. 212;
-- Now, that is a Natural Range for Ada, but I can tell you that Americans
-- are not nearly so happy with "0" and not even an Icelander likes it at "212"!
Fahr : Fahrenheit_Degree_Range := Fahrenheit_Degree_Range'First;
Factor : constant := 5.0 / 9.0;
Offset : constant := 32;
Step : constant := 1;
begin
loop
Put (Item => Fahr, Width => Fahrenheit_Degree_Range'Width);
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 0);
New_Line;
exit when Fahr = Fahrenheit_Degree_Range'Last;
Fahr := Fahr + Step;
end loop;
end FtoC;
儲存檔案,然後我們繼續最後一步:編譯。
如果您使用優秀的 GNAT Studio IDE,編譯只需簡單地按下 F4 來編譯專案,並按下 Shift+F2 來執行它。如果您沒有使用此 IDE,則執行以下操作
$ gnatmake -P ftoc.gpr
您應該會看到一些輸出滾動
gcc -c -gnatwa -gnaty3abcdefhiklmnoprstux -Wall -O2 -I- -gnatA /home/thomas/FtoC/ftoc.adb gnatbind -I- -x /home/thomas/FtoC/objects/ftoc.ali gnatlink /home/thomas/FtoC/objects/ftoc.ali -o /home/thomas/FtoC/exe/ftoc
您現在在 exe/ 目錄中有一個可執行檔案。當您執行它時,您會得到以下內容(此處略微縮寫)
0 -17.78 1 -17.22 2 -16.67 ... 26 -3.33 27 -2.78 28 -2.22 29 -1.67 30 -1.11 31 -0.56 32 0.00 33 0.56 34 1.11 35 1.67 ... 48 8.89 49 9.44 50 10.00 51 10.56 52 11.11 ... 67 19.44 68 20.00 69 20.56 70 21.11 71 21.67 ... 84 28.89 85 29.44 86 30.00 87 30.56 ... 210 98.89 211 99.44 212 100.00
現在我們知道 0 華氏度等於 -17.78 攝氏度,在 212 華氏度時水沸騰,對於我們攝氏度使用者來說,這是 100 攝氏度。該程式可以正常工作!讓我們詳細地回顧一下,以瞭解它是如何工作的。
注意: 以下示例中的所有輸出都將大幅縮寫,因為為每個小示例列印 213 行輸出簡直是瘋狂。
讓我們從前三行開始
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
這些被稱為 with 子句和 use 宣告。Ada with 子句可以比作 C #include。當您看到 with Ada.Text_IO 時,這意味著 Ada.Text_IO 包的實用程式將提供給程式。當您接下來看到宣告 use Ada.Text_IO 時,這意味著 Ada.Text_IO 的實用程式可以直接被程式看到,即您不必編寫
Ada.Text_IO.Put ("No use clause");
來呼叫 Put 過程。相反,您只需執行
Put ("With use clause");
在 FtoC 程式中,我們利用並使其可見三個包:Ada.Text_IO、Ada.Integer_Text_IO 和 Ada.Flot_Text_IO。這些包提供了它們名稱所暗示的確切含義:不同型別的文字 IO 功能。
檢視原始碼,您可能會想知道 Ada.Text_IO 的必要性在哪裡,因為我們只輸出數字。我們會很快講到這一點,敬請關注。
接下來我們有這段程式碼
procedure FtoC is
subtype Fahrenheit_Degree_Range is Natural range 0 .. 212;
Fahr : Fahrenheit_Degree_Range := Fahrenheit_Degree_Range'First;
Factor : constant := 5.0 / 9.0;
Offset : constant := 32;
Step : constant := 1;
begin
這被稱為 Ada 程式的宣告部分。在這裡,我們宣告變數、常量、型別等等。在 FtoC 的情況下,我們有 5 個宣告。讓我們談談前兩個
subtype Fahrenheit_Degree_Range is Natural range 0 .. 212;
Fahr : Fahrenheit_Degree_Range := Fahrenheit_Degree_Range'First;
這正是 Ada 最大的賣點之一:能夠建立自己的型別,並設定自己的約束。它可能看起來並不重要,但相信我,它確實很重要。在這種情況下,我們建立了內建子型別 Natural (3.5.4 [帶註釋的]) 的一個新 子型別。我們將該型別的範圍約束為 0 .. 212,這意味著宣告為 Fahrenheit_Degree_Range 型別的物件永遠 不能 低於 0 或高於 212。如果將超出此範圍的值分配給 Fahrenheit_Degree_Range 型別 的物件,則會引發 Constraint_Error 異常。
有了 Fahrenheit_Degree_Range 型別,我們現在將注意力轉向 Fahr 變數的宣告和賦值。如果您從未見過此語法或它對您來說毫無意義,請閱讀本維基上的 常量 文章,然後返回此處。
Fahr 變數是 Fahrenheit_Degree_Range 型別的,其初始值為 0。為什麼是 0?因為我們將其賦值為 Fahrenheit_Degree_Range'First,而 'First 部分等於該類型範圍約束中的第一個值,在本例中為 0。因此,Fahrenheit_Degree_Range'Last 的值為 212。
讓我們看一下最後三個物件宣告
Factor : constant := 5.0 / 9.0;
Offset : constant := 32;
Step : constant := 1;
這裡發生了什麼?為什麼這些宣告中沒有與任何宣告相關的 type?好吧,如果您閱讀了 常量 文章,您將知道這些常量被稱為 命名數字,它們的 type 被稱為通用型別 (3.4.1 [帶註釋的])。命名數字可以是任何大小和精度。
Factor 和 Offset 常量用於華氏度到攝氏度的計算,而 Step 定義了我們在程式中執行的轉換次數。
宣告已完成,我們現在將注意力轉向程式的實體
begin
loop
Put (Item => Fahr, Width => Fahrenheit_Degree_Range'Width);
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 0);
New_Line;
exit when Fahr = Fahrenheit_Degree_Range'Last;
Fahr := Fahr + Step;
end loop;
end FtoC;
保留字 begin 表示 body 的開始,而相同的 body 以最終的 end FtoC 結束。在這兩者之間,我們有一堆語句。
第一個是 loop 語句。 迴圈 在 Ada 中以多種不同的形式出現,每種形式都遵循迴圈的基本前提
loop
-- some statements
end loop;
迴圈可以使用 exit 關鍵字終止
loop
-- some statements
if X = Y then
exit;
end if;
end loop;
這種結構非常常見,因此提供了一個簡短版本
loop
-- some statements
exit when X = Y;
end loop;
正是我們最後使用的版本在 FtoC 程式中,當 Fahr 等於 Fahrenheit_Degree_Range 型別的 'Last 值時,我們將退出迴圈。
緊隨 loop 語句之後,我們遇到了 Put 過程
Put (Item => Fahr, Width => Fahrenheit_Degree_Range'Width);
我們從宣告中知道 Fahr 是 Natural 的子型別,而 Natural 又 是 Integer 的子型別,因此這一行對 Put 的呼叫實際上呼叫了 Ada.Integer_Text_IO.Put。如您所見,我們為 Put 提供了兩個引數:Item 和 Width。Item 的含義應該是顯而易見的:它是我們想要輸出的整數,在本例中是 Fahr 變數。
另一方面,Width 並不那麼直觀。Width 引數指定了將整數型別(在本例中是子型別)作為字面量輸出所需的最小字元數,再加上可能出現的負號所需的 1 個字元。如果 Width 引數設定過高,整數字面量將用空格填充。如果設定過低,它將根據需要自動擴充套件。將 Width 引數設定為 0,會導致欄位的寬度為包含整數所需的最小寬度。
'Width 屬性返回型別的最大寬度,因此如果我們稍後更改 Fahrenheit_Degree_Range,我們就不必對這個對 Put 的呼叫做任何事情;它會自動相應調整。
讓我們使用不同的 Width 引數進行一些測試
Put (Item => Fahr); -- Default Width parameter
輸出現在看起來像這樣
0 -17.78
1 -17.22
2 -16.67
3 -16.11
4 -15.56
...
有很多浪費的空間。這是因為 Put 現在為 Integer 預留了空間,而 Fahrenheit_Degree_Range 是從該型別派生的。讓我們嘗試使用 0
Put (Item => Fahr, Width => 0); -- Minimum required characters for the integer
以及輸出
0 -17.78 1 -17.22 2 -16.67 3 -16.11 9 -12.78 10 -12.22 11 -11.67 99 37.22 100 37.78 101 38.33 ...
不幸的是,為了提高可讀性,Fahr 整數字面量不再右對齊。每個數字都將獲得包含它所需的精確寬度,不多也不少。
讓我們嘗試一個足夠容納部分 Fahr 值,但不能容納所有值的 Width
Put (Item => Fahr, Width => 2); -- Minimum width of 2. Expands if necessary
這將輸出
0 -17.78 1 -17.22 2 -16.67 9 -12.78 10 -12.22 11 -11.67 98 36.67 99 37.22 100 37.78 101 38.33 102 38.89 103 39.44 ...
如您所見,一位數的值右對齊並用 1 個空格填充,兩位數的值輸出一致,但其餘結果則擴充套件以容納第三個字元。
在處理整數型別的 Put 之後,我們繼續進行下一個 Put>
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 0);
對 Put 的這個呼叫中的 Item 引數是一個浮點數,因為 Factor 是一個包含小數點的命名數字,表示式 Fahr - Offset 使用 Float (Fahr - Offset) 表示式轉換為浮點數。因此,在呼叫這個 Put 時,我們實際上呼叫的是 Ada.Float_Text_IO.Put。
Fore 引數指定了在小數點之前輸出值所需的最小字元數。與整數型別的 Width 一樣,Fore 會在必要時自動擴充套件。Aft 設定小數點後的精度,在本例中為 2。最後,Exp 設定指數字段大小。Exp => 0 的值表示不輸出指數。非零值將輸出指數符號“E”,+/–,以及指數的數字。注意:Exp 的值不應小於零!
讓我們嘗試幾種不同的組合
Put (Item => Factor* Float (Fahr - Offset),
Fore => 10,
Aft => 4,
Exp => 0);
輸出
0 -17.7778 1 -17.2222 15 -9.4444 16 -8.8889 17 -8.3333 26 -3.3333 27 -2.7778 100 37.7778 101 38.3333 102 38.8889 103 39.4444 104 40.0000 ...
或者,試試這個
Put (Item => Factor* Float (Fahr - Offset),
Fore => 4,
Aft => 2,
Exp => 1);
以及輸出
0 -1.78E+1 1 -1.72E+1 2 -1.67E+1 8 -1.33E+1 9 -1.28E+1 10 -1.22E+1 11 -1.17E+1 18 -7.78E+0 37 2.78E+0 98 3.67E+1 99 3.72E+1 100 3.78E+1 101 3.83E+1 ...
最後
Put (Item => Factor* Float (Fahr - Offset),
Fore => 0,
Aft => 1,
Exp => 0);
這將輸出
0-17.8 8-13.3 9-12.8 10-12.2 11-11.7 31-0.6 320.0 499.4 9836.7 9937.2 10037.8 10138.3 10238.9 ...
這顯然看起來不太美觀。
FtoC 程式的最後四行完成了我們的格式化,如果我們已經完成則退出,否則將 Fahr 的下一個值設定為要轉換的值
New_Line;
exit when Fahr = Fahrenheit_Degree_Range'Last;
Fahr := Fahr + Step;
end FtoC;
對 New_Line 的唯一呼叫是我們在使用 with Ada.Text_IO 使 Ada.Text_IO 可用於 FtoC 程式的原因。New_Line 的作用是輸出一個換行符。如果沒有對 New_Line 的呼叫,程式執行後將產生如下所示的輸出
0 -17.78 1 -17.22 2 -16.67 3 -16.11 4 -15.56 5 -15.00 6 -14.44 7 -13.89 8 -13.33 9 -12.78 10 -12.22 11 -11.67 ...
New_Line 過程接受一個 Spacing 引數,這意味著您可以執行以下操作來輸出連續的換行符
New_Line (Spacing => 5);
或者,簡單地
New_Line (5);
我們已經討論了 終止迴圈 的 exit when 方法,最後一條語句只是一個簡單的計數器。Fahr 的值在每次迴圈迭代時都會以 Step 增加。當 Fahr 等於 Fahrenheit_Degree_Range'Last 時,迴圈將終止。
最後一行 end Ftoc; 表示程式的結束。沒有更多要做的,也沒有更多要看的。控制權將返回給最初呼叫程式的任何內容,生命繼續。
希望您喜歡這個關於構建華氏度到攝氏度轉換表的簡短教程。留給讀者去弄清楚如何新增開氏度。祝您玩得開心!
