跳至内容

软件工程师手册/语言词典/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 语句也将释放该过程的本地静态变量。
华夏公益教科书