軟體工程師手冊/語言詞典/PLI/過程
注意: 本文需要您對 PL/I 有基本的瞭解,相關內容可以在 軟體工程師手冊/語言詞典/PLI 中找到。
PL/I 程式可以包含使用者定義的子程式。
- (內部) 子程式的定義從以下內容開始:
- 子程式名稱後跟冒號 (:)
- 關鍵字 PROCEDURE(縮寫:PROC)
- 可選的方括號內包含引數描述符列表
- 子程式將透過 CALL 語句呼叫。
- 可以使用可選的 RETURN 語句離開子程式。
example: proc options ( main );
call proc_1 ( 0 ); /* output: 'zero' */
call proc_1 ( 7 ); /* output: 'the number is 7' */
proc_1: proc ( parm );
dcl parm bin fixed (15);
if parm = 0 then
do;
put skip list ( 'zero' );
return;
end;
put skip list ( 'the number is' || parm );
end proc_1;
end example;
PL/I 程式可以包含使用者定義的函式。
- (內部) 函式的定義從以下內容開始:
- 函式名稱後跟冒號 (:)
- 關鍵字 PROCEDURE(縮寫:PROC)
- 可選的方括號內包含引數描述符列表
- 關鍵字 RETURNS 後跟方括號內的返回值型別
- 函式的結果可以在表示式中使用。
- 子程式必須至少包含一個 RETURN 語句,用於返回結果。
example: proc options ( main ); dcl number bin fixed (31); put skip list ( hundred ( 0 ) ) ; /* output: 0 */ put skip list ( hundred ( 3 ) ) ; /* output: 300 */ number = hundred ( 5 ) + 55; put skip list ( number ); /* output: 555 */ hundred: proc ( parm ) returns ( bin fixed (31) ); dcl parm bin fixed (15); if parm = 0 then return ( 0 ); return ( parm * 100 ); end hundred; end example;
當使用變數作為引數呼叫過程時,這些引數可以透過以下幾種方式傳遞:
- "按引用"(也稱為"按名稱"),這意味著過程內部的更改會改變原始變數。
- "按值"(或"作為虛擬"),這意味著過程內部的更改只會改變原始變數的副本。
在 PL/I 中,如果變數:
- 是簡單型別,例如 char、number 等
- 是簡單型別變數的陣列
- 是簡單型別變數的結構 [1]
如果並且只有當該變數與過程內部的引數定義完全匹配時,才會按引用傳遞。
example: proc options ( main ); dcl bifi_15 bin fixed (15); dcl bifi_31 bin fixed (31); bifi_15 = 15; call change ( bifi_15 ); put skip list ( bifi_15 ); /* output: 99 */ bifi_31 = 31; call change ( bifi_31 ); put skip list ( bifi_31 ); /* output: 31 */ change: proc ( parm_15 ); dcl parm_15 bin fixed (15); parm_15 = 99; end change; end example;
- 如果引數定義的大小屬性為星號,則任何大小的匹配變數都將按引用傳遞。
- 如果引數定義的維度為星號,則任何維度的匹配變數都將按引用傳遞。
example: proc options ( main ); dcl a_ch (2) char (10); dcl b_ch (5) char (20); dcl v_ch (5) char (20) varying; a_ch (1) = 'BEFORE' call change ( a_ch ); put skip list ( a_ch (1) ); /* output: 'AFTER ' */ b_ch (1) = 'BEFORE' call change ( b_ch ); put skip list ( b_ch (1) ); /* output: 'AFTER ' */ v_ch (1) = 'BEFORE' call change ( v_ch ); put skip list ( v_ch (1) ); /* output: 'BEFORE' because v_ch-strings are varying */ change: proc ( ch_array ); dcl ch_array (*) char (*); ch_array (1) = 'AFTER'; end change; end example;
PL/I 變數的範圍僅限於宣告它們的 procedure。
outer: proc options ( main ); dcl a char (07) init ( 'outer A' ); dcl b char (07) init ( 'outer B' ); call inner; put skip list ( a ); put skip list ( b ); inner: proc; dcl b char (07) init ( 'inner B' ); dcl c char (07) init ( 'inner C' ); put skip list ( b ); put skip list ( c ); end inner; end outer; output: 'inner B' 'inner C' 'outer A' 'outer B'
outer: proc options ( main ); call hello; /* output: 'hello from outer' */ call inner; /* output: 'hello from inner' */ hello: proc; put skip list ( 'hello from outer' ); end hello; inner: proc; call hello; hello: proc; put skip list ( 'hello from inner' ); end hello; end inner; end outer;
區域性變數可以具有兩種儲存類別。 [2]
自動儲存
- 它的值在 procedure 的連續呼叫之間會丟失。
- 如果該變數具有 INIT 屬性,則每次呼叫 procedure 時都會進行初始化。
- 關鍵字 AUTOMATIC 可以縮寫為 AUTO。
- 預設情況下,區域性變數是自動的 [3]。
靜態儲存
- 它的值在 procedure 的連續呼叫之間會儲存。
- 如果該變數具有 INIT 屬性,則只在程式啟動時進行一次初始化。
example: proc options ( main );
call static_memory ( 7 );
call static_memory ( 0 ); /* output: 7 */
call auto_memory ( 7 );
call auto_memory ( 0 ); /* output: 1 */
static_memory: proc ( parm );
dcl parm bin fixed (15);
dcl memory bin fixed (15) init ( 1 ) STATIC;
if parm = 0 then
put skip list ( memory );
else
memory = parm;
end static_memory;
auto_memory: proc ( parm );
dcl parm bin fixed (15);
dcl memory bin fixed (15) init ( 1 );
if parm = 0 then
put skip list ( memory );
else
memory = parm;
end auto_memory;
end example;
過程可以有多個入口。
example: proc options ( main ); dcl number bin fixed (15); call value_set ( 3 ); call value_get ( number ); put skip list ( number ); /* output: 3 */ call value_calc ( 2 , 1 ); call value_write; /* output: 7 */ value_set: PROC ( parm_1 ); dcl parm_1 bin fixed (15); dcl memory bin fixed (15) static; memory = parm_1; return; value_get: ENTRY ( parm_1 ); /* do NOT declare parm_1 and memory once again ! */ parm_1 = memory; return; value_calc: ENTRY ( parm_1 , parm_2 ); dcl parm_2 bin fixed (15); memory = memory * parm_1 + parm_2; return; value_write: ENTRY; put skip list ( memory ); end value_set; end example;
使用 GENERIC 屬性,過程可以有多個引數列表。
Syntax:
DCL procname GENERIC ( procname_1 WHEN ( parm_descriptor_1 ) ,
procname_2 WHEN ( parm_descriptor_2 ) );
Meaning at compile time:
WHENEVER a call of procname is inside the source
IF the calling parameters are like parm_descriptor_1 THEN replace procname with procname_1
ELSE IF the calling parameters are like parm_descriptor_2 THEN replace procname with procname_2
ELSE throw compile error
引數描述符可以包含呼叫引數的屬性
example: proc options ( main );
dcl twice generic ( twice_C when ( char ) ,
twice_F when ( fixed ) );
put skip list ( twice ( 'hello' ) ); /* output: 'hello hello' */
put skip list ( twice ( 1234321 ) ); /* output: 2468642 */
twice_C: proc ( parm ) returns ( char (11) );
dcl parm char (05);
return ( parm || ' ' || parm );
end twice_C;
twice_F: proc ( parm ) returns ( bin fixed (31) );
dcl parm bin fixed (31);
return ( parm + parm );
end twice_F;
end example;
引數描述符可以包含呼叫引數的數量
dcl calc generic ( calc_with_3_parameters when ( * , * , * ) ,
calc_with_2_parameters when ( , ) ,
calc_with_1_parameter when ( * ) );
calc_with_3_parameters: proc ( a , b , c );
...
calc_with_2_parameters: proc ( a , b );
...
calc_with_1_parameter: proc ( a );
...
引數描述符可以包含呼叫引數的維度數量
dcl calc generic ( calc_value when ( ( * ) ) ,
calc_vector when ( ( * , * ) ) ,
calc_matrix when ( ( * , * , * ) ) );
calc_value: proc ( v );
dcl v char (12);
...
calc_vector: proc ( v ):
dcl v (5) bit (1);
...
calc_matrix: proc ( v ):
dcl v (5,8) bin fixed (15);
...
如果指定了 RECURSIVE 選項,則 procedure 可以遞迴呼叫。
- procedure 的每次呼叫都有其自身呼叫引數的值。
- procedure 的每次呼叫都有其自身自動區域性變數的值。
- procedure 的所有呼叫都具有公共的靜態區域性變數值。
example: proc options ( main );
dcl number bin fixed (15);
call test ( 1 );
test: proc ( level ) RECURSIVE;
dcl level pic '9';
dcl loc_a pic '99';
dcl loc_s pic '999' static;
dcl pre char (09);
loc_a = level * 11;
loc_s = level * 111;
pre = substr ( ' ' , 1 , 3 * level );
put skip list ( pre || 'Level ' || level || ' started' );
put skip list ( pre || 'loc_a = ' || loc_a );
put skip list ( pre || 'loc_s = ' || loc_s );
if level < 3 call test ( level + 1 );
put skip list ( pre || 'loc_a = ' || loc_a );
put skip list ( pre || 'loc_s = ' || loc_s );
put skip list ( pre || 'Level ' || level || ' ended' );
end test;
end example;
Output:
Level 1 started
loc_a = 11
loc_s = 111
Level 2 started
loc_a = 22
loc_s = 222
Level 3 started
loc_a = 33
loc_s = 333
loc_a = 33
loc_s = 333
Level 3 ended
loc_a = 22
loc_s = 333
Level 2 ended
loc_a = 11
loc_s = 333
Level 1 ended
與其他資料一樣,procedure 可以用作變數和引數。
example: proc options ( main ); dcl e_var ENTRY; e_var = proc_1; call e_var; /* output: 'proc 1' */ e_var = proc_2; call e_var; /* output: 'proc 2' */ proc_1: proc; put skip list ( 'proc 1' ); end proc_1; proc_2: proc; put skip list ( 'proc 2' ); end proc_2; end example;
example: proc options ( main );
call list ( 'square' , square_proc );
call list ( 'cube' , cube_proc );
list: proc ( title , calc );
dcl title char (*);
dcl calc entry ( bin fixed (15) ) returns ( bin fixed (15) );
dcl i bin fixed (15);
put skip list ( title || '-table:' );
do i = 1 to 5;
put skip list ( i || ':' || calc ( i ) );
end;
end list;
square_proc: proc ( v ) returns ( bin fixed (15) );
dcl v bin fixed (15);
return ( v * v );
end square_proc;
cube_proc: proc ( v ) returns ( bin fixed (15) );
dcl v bin fixed (15);
return ( v * square_proc ( v ) );
end cube_proc;
end example;
Output:
square-table:
1: 1
2: 4
3: 9
4: 16
5: 25
cube-table:
1: 1
2: 8
3: 27
4: 64
5: 125
procedure 可以是外部的,即原始碼可以包含在單獨的檔案中。
這種方法提供了在多個主程式中使用該 procedure 的可能性。
每個使用外部 procedure 的程式或 procedure 必須宣告該 procedure。
Source File of external procedure EXT1: ext1: proc; put skip list ( 'Hello from EXT-1' ); end ext1; Source File of external procedure EXT2: ext2: proc ( message ); dcl message char (*); dcl ext1 EXTERNAL ENTRY; /* declaration of used external procedure */ put skip list ( 'EXT-2:' ); put skip list ( message ); call ext1; end EXT2; Source File of MAIN: example: proc options ( main ); dcl ext2 EXT ENTRY ( CHAR (*) ); /* keyword EXTERNAL may be abbreviated */ call ext2 ( 'Called by Example' ); end example; Output running example EXT-2: Called by Example Hello from EXT-1
使程式可執行需要兩個步驟
- 以任何順序編譯程式和所有使用的外部過程。
- 使用連結編輯器將編譯後的物件合併到一個可執行程式中。
可執行程式現在包含其過程在連結時的二進位制程式碼,
即,如果在連結時間之後更改了外部過程並重新編譯,這不會改變可執行程式的行為。
PL/I 允許對外部過程進行動態連結,
即,主程式可以在執行時載入過程的最新編譯版本。
語句FETCH ABC 將過程 ABC 的二進位制程式碼從輔助儲存載入到主儲存(除非 ABC 已經在主儲存中存在)。
語句RELEASE ABC 釋放 ABC 佔用的主儲存[4]。
example: proc options ( main );
dcl abc EXT ENTRY;
dcl dfg EXT ENTRY;
dcl ( b1 , b2 ) bit (1);
.....
if b1 then
do;
fetch abc;
call abc;
release abc;
end;
.....
if b2 then
do;
fetch abc, dfg;
call abc;
call dfg;
release abc, dfg;
end;
end example;
如果在上面的示例中 b1 和 b2 都為真 ...
- abc 將在第一次呼叫後被釋放
- abc 將在第二次呼叫之前再次載入
... 這不是很有效。
PL/I 具有以下屬性
- 如果程式中存在 fetch 或 release 語句,則一個過程將被識別為動態載入,而不需要該語句在任何時候被執行。
- 如果在執行時呼叫了被識別為動態載入的過程,則將根據需要自動獲取該過程。
利用這兩個屬性,上面的程式可以改寫為
example: proc options ( main );
dcl abc EXT ENTRY;
dcl dfg EXT ENTRY;
dcl ( b1 , b2 ) bit (1);
if 1 > 2 then /* this will never be true */
fetch abc, dfg; /* but abc and dfg will be MARKED AS TO_BE_LOADED_DYNAMICLY */
.....
if b1 then
do;
/* COMMENT: IF ABC IS NOT YET IN MAIN STORAGE THEN LOAD IT AUTOMATICALLY */
call abc;
end;
.....
if b2 then
do;
/* COMMENT: IF ABC IS NOT YET IN MAIN STORAGE THEN LOAD IT AUTOMATICALLY */
call abc;
/* COMMENT: IF DFG IS NOT YET IN MAIN STORAGE THEN LOAD IT AUTOMATICALLY */
call dfg;
end;
end example;
- [1] 或簡單型別變數的結構陣列 ...
- [2] 還有兩種儲存類別(基於和受控,用於動態分配的儲存),將在另一個華夏公益教科書 PL/I 專輯中解釋。
- [3] 在 PL/I 中,可以使用 DEFAULT 語句更改預設值。
- [4] 獲取過程的 FREE 語句也將釋放該過程的本地靜態變數。