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