跳至內容

軟體工程師手冊/語言詞典/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

靜態連結

[編輯 | 編輯原始碼]

使程式可執行需要兩個步驟

  1. 以任何順序編譯程式和所有使用的外部過程。
  2. 使用連結編輯器將編譯後的物件合併到一個可執行程式中。

可執行程式現在包含其過程在連結時的二進位制程式碼,
即,如果在連結時間之後更改了外部過程並重新編譯,這不會改變可執行程式的行為。

獲取的過程

[編輯 | 編輯原始碼]

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 語句也將釋放該過程的本地靜態變數。
華夏公益教科書