第八章 疯狂Caché 调用自定义代码模块(二)
参数传递
程序的一个重要特性是它们支持参数传递。这是一种可以将值(或变量)作为参数传递给过程的机制。当然,参数传递不是必需的;例如,不传递参数的过程可用于生成随机数或以默认格式以外的格式返回系统日期。但是,通常情况下,程序确实使用参数传递。
要设置参数传递,请指定:
- 程序调用上的实际参数列表。
- 程序定义上的正式参数列表。
当Caché执行用户定义的程序时,它会按位置将实际列表中的参数映射到形式列表中的相应参数。因此,实际列表中第一个参数的值放在形式列表中的第一个变量中,第二个值放在第二个变量中,依此类推。这些参数的匹配是按位置进行的,而不是按名称进行的。因此,用于实际参数和形式参数的变量不需要相同的名称。该程序通过引用其正式列表中的相应变量来访问传递的值。
实际参数列表和形式参数列表的参数数量可能不同:
- 如果实际参数列表中的参数少于形式参数列表中的参数,则形式参数列表中不匹配的元素是未定义的。可以为未定义的形式参数指定默认值,如下例所示:
Main
/* Passes 2 parameters to a procedure that takes 3 parameters */
SET a="apple",b="banana",c="carrot",d="dill"
DO ListGroceries(a,b)
WRITE !,"all done"
ListGroceries(x="?",y="?",z="?") {
WRITE x," ",y," ",z,! }
apple banana ?
all done
- 如果实际参数列表中的参数多于形式参数列表中的参数,则会出现
错误,如下例所示:
Main
/* Passes 4 parameters to a procedure that takes 3 parameters.
This results in a error */
SET a="apple",b="banana",c="carrot",d="dill"
DO ListGroceries(a,b,c,d)
WRITE !,"all done"
ListGroceries(x="?",y="?",z="?") {
WRITE x," ",y," ",z,! }
如果形式列表中的变量比实际列表中的参数多,并且没有为每个变量提供默认值,则额外的变量将保持未定义状态。程序代码应该包括适当的IF
测试,以确保每个程序引用都提供可用的值。要简化实际参数和形式参数数量的匹配,可以指定可变数量的参数。
实际参数的最大数量为254个。
将参数传递给用户定义的过程时,可以使用按值传递或按引用传递。可以在同一程序调用中混合使用按值传递和按引用传递。
- 程序可以按值或按引用传递参数。
- 子例程可以按值或按引用传递参数。
- 用户定义的函数可以按值或按引用传递参数。
- 系统提供的函数只能按值传递参数。
按值传递
若要按值传递,请在实际参数列表中指定文字值、表达式或局部变量(有下标或无下标)。对于表达式,Caché首先计算表达式,然后传递结果值。对于局部变量,Caché传递变量的当前值。请注意,所有指定的变量名都必须存在,并且必须具有值。
该过程的形参列表包含无下标的局部变量名。它不能指定显式下标变量。但是,隐式指定可变数量的参数会创建下标变量。
Caché隐式创建并声明过程中使用的任何非公共变量,这样调用代码中已存在的同名变量就不会被覆盖。它将这些变量的现有值(如果有)放在程序堆栈上。当它调用QUIT
命令时,Caché会对每个形式变量执行隐式kill
命令,并从堆栈恢复它们以前的值。程序堆栈上的这些变量(如果有)。
在下面的示例中,SET
命令使用三种不同的形式将相同的值传递给引用的多维数据集程序。
DO Start()
WRITE "all done"
Start() PUBLIC {
SET var1=6
SET a=$$Cube(6)
SET b=$$Cube(2*3)
SET c=$$Cube(var1)
WRITE !,"a: ",a," b: ",b," c: ",c,!
QUIT 1
}
Cube(num) PUBLIC {
SET result = num*num*num
QUIT result
}
a: 216 b: 216 c: 216
all done
按引用传递
要通过引用传递,请使用以下形式在实际参数列表中指定局部变量名或无下标数组的名称:
.name
通过引用传递,指定的变量或数组名称不必在程序引用之前存在。
按引用传递是将数组名称传递给过程的唯一方式。请注意,不能通过引用传递下标变量。
- 实际参数列表:实际参数列表中局部变量或数组名称前面的句点是必填项。它指定变量是通过引用传递的,而不是通过值传递的。
- 形参列表:接收引用传递的变量不需要在形参列表中使用特殊语法。形式参数列表中不允许使用句点前缀。但是,在形参列表中,允许在变量名之前使用与号(
&
)前缀;按照惯例,此&
前缀用于指示此变量是通过引用传入的。前缀&
是可选的,不执行任何操作;它是使源代码更易于阅读和维护的有用约定。
通过引用传递时,实际列表中的每个变量或数组名称都绑定到函数的正式列表中相应的变量名称。通过引用传递为引用例程和函数之间的双向通信提供了一种有效的机制。函数对其形式列表中的变量所做的任何更改也会对实际列表中相应的按引用变量进行更改。这也适用于kill
命令。如果形式列表中的按引用变量被函数终止,则实际列表中的相应变量也将被终止。
如果实际列表中指定的变量或数组名称尚不存在,则函数引用会将其视为未定义。如果函数为形式列表中的相应变量赋值,则实际变量或数组也使用该值定义。
下面的示例将按引用传递与按值传递进行比较。变量a
通过值传递,变量b
通过引用传递:
Main
SET a="6",b="7"
WRITE "Initial values:",!
WRITE "a=",a," b=",b,!
DO DoubleNums(a,.b)
WRITE "Returned to Main:",!
WRITE "a=",a," b=",b
DoubleNums(foo,&bar) {
WRITE "Doubled Numbers:",!
SET foo=foo*2
SET bar=bar*2
WRITE "foo=",foo," bar=",bar,!
QUIT
}
DHC-APP>d Main1^PHA.MOB.TEST
Initial values:
a=6 b=7
Doubled Numbers:
foo=12 bar=14
Returned to Main:
a=6 b=14
DHC-APP>
注意:类方法中用ByRef来代表引用。
下面的示例使用按引用传递来通过变量Result
实现引用例程和函数之间的双向通信。句点前缀指定通过引用传递结果。执行函数时,会创建实际参数列表中的结果,并将其绑定到函数的形参列表中的z
。将计算出的值赋给z
,并将其传递回RESULT
中的引用例程。形参列表中z
的&
前缀是可选的,不起作用,但有助于澄清源代码。请注意,num
和powerr
是通过值传递的,而不是引用。这是混合按值传递和按引用传递的示例:
Start ; Raise an integer to a power.
READ !,"Integer= ",num QUIT:num=""
READ !,"Power= ",powr QUIT:powr=""
SET output=$$Expo(num,powr,.result)
WRITE !,"Result= ",output
GOTO Start
Expo(x,y,&z)
SET z=x
FOR i=1:1:y {SET z=z*x}
QUIT z
可变数量的参数
程序可以指定它接受可变数量的参数。可以通过在最后一个参数的名称后附加三个点来实现这一点;例如,VALLES...
。此参数必须是参数列表中的最后一个参数。这个...
。语法可以传递多个参数、单个参数或零个参数。
允许在列表中的参数之间以及列表中第一个参数之前和最后一个参数之后使用空格和换行符。三个点之间不允许有空格。
要使用此语法,请指定签名,其中最后一个参数的名称后跟...
。通过此机制传递给方法的多个参数可以具有来自数据类型的值,可以是对象值,也可以是两者的混合。指定使用可变数量参数的参数可以具有任何有效的标识符名称。
CachéObjectScript通过创建一个下标变量来处理传递数量可变的参数,为每个传递的变量创建一个下标。变量的顶层包含传递的参数数量。变量下标包含传递的值。
下面的示例显示了这一点。它使用了invals...
。作为形参列表中的唯一参数。ListGroceries(invals...)
。接收由值传递的可变数量的值:
Main
SET a="apple",b="banana",c="carrot",d="dill",e="endive"
DO ListGroceries(a,b,c,d,e)
WRITE !,"all done"
ListGroceries(invals...) {
WRITE invals," parameters passed",!
FOR i=1:1:invals {
WRITE invals(i),! }
}
5 parameters passed
apple
banana
carrot
dill
endive
all done
下面的示例使用morenums...
。作为最后一个参数,下面是两个定义的参数。此过程必须至少接收两个参数值,但可以接收数量可变的附加参数:
Main
SET a=7,b=8,c=9,d=100,e=2000
DO AddNumbers(a,b,c,d,e)
WRITE "all done"
AddNumbers(x,y,morenums...) {
SET sum = x+y
FOR i=1:1:$GET(morenums, 0) {
SET sum = sum + $GET(morenums(i)) }
WRITE "The sum is ",sum,!
QUIT
}
The sum is 2124
all done
下面的示例使用morenums...
。作为最后一个参数,下面是两个定义的参数。此过程正好接收两个参数值;orenums...
。附加参数的变量数为0:
Main
SET a=7,b=8,c=9,d=100,e=2000
DO AddNumbers(a,b)
WRITE "all done"
AddNumbers(x,y,morenums...) {
SET sum = x+y
FOR i=1:1:$GET(morenums, 0) {
SET sum = sum + $GET(morenums(i)) }
WRITE "The sum is ",sum,!
QUIT
}
The sum is 15
all done
按照指定 AddNumbers(x,y,morenums...)
。最少可以接收两个参数,最多可以接收255个参数。
如果为定义的参AddNumbers(x,y,morenums...)
提供默认值。此过程最少可以接收无参数,最多可以接收255个参数。
下面的示例使用nums...
。作为唯一的参数。它接收通过引用传递的可变数量的值:
Main
SET a=7,b=8,c=9,d=100,e=2000
DO AddNumbers(.a,.b,.c,.d,.e)
WRITE "all done"
AddNumbers(&nums...) {
SET sum = 0
FOR i=1:1:$GET(nums, 0) {
SET sum = sum + $GET(nums(i)) }
WRITE "The sum is ",sum,!
QUIT sum
}
The sum is 2124
all done
以下示例显示此 args...
。语法既可以在形式参数列表中使用,也可以在实际参数列表中使用。在本例中,参数的数量是可变的 (invals...
)。是按值传递给ListNums
,ListNums
会将它们的值加倍,然后将它们作为invals...至ListDoubleNum
:
Main
SET a="1",b="2",c="3",d="4"
DO ListNums(a,b,c,d)
WRITE !,"back to Main, all done"
ListNums(invals...) {
FOR i=1:1:invals {
WRITE invals(i),!
SET invals(i)=invals(i)*2 }
DO ListDoubleNums(invals...)
WRITE "back to ListNums",!
ListDoubleNums(twicevals...)
WRITE "Doubled Numbers:",!
FOR i=1:1:twicevals {
WRITE twicevals(i),! }
QUIT
}
1
2
3
4
Doubled Numbers:
2
4
6
8
back to ListNums
back to Main, all done
程序代码
大括号之间的代码体是程序代码,它在以下方面与传统的ObjectScript代码不同:
只能在程序标签处输入程序。不允许通过
“LABEL+OFFSET
”语法访问过程。该程序中的任何标签都是该程序的私有标签,并且只能从该过程内访问。虽然不是必需的,但可以在过程内的标签上使用
PRIVATE
关键字。PUBLIC
关键字不能用于过程内的标签-它会产生语法错误。即使系统函数$TEXT
也不能按名称访问私有标签,尽管$TEXT
支持使用过程标签名称的LABEL+OFFSET
。在程序中不允许重复标签,但在某些情况下,在例程中允许重复标签。具体地说,在不同的程序中允许重复标签。此外,相同的标签可以出现在一个程序中,也可以出现在定义该程序的例程中的其他地方。
例如,允许出现以下三个“Label1
”:
Rou1 // Rou1 例程
Proc1(x,y) {
Label1 // Rou1例程内的proc1过程中的label1
}
Proc2(a,b,c) {
Label1 // Proc2程序中的Label1(本地,与以前的Label1一样)
}
Label1 // Label1是Rou1的一部分,并且既不是程序
如果程序包含没有例程名称的
DO
命令或用户定义函数,则它指的是程序中的标签(如果存在)。否则,它引用例程中但在程序之外的标签。如果程序包含具有例程名称的
DO
或用户定义函数,则它始终标识程序外部的一行。即使该名称标识了包含该程序的例程,也是如此。例如:
ROU1 ;
PROC1(x,y) {
DO Label1^ROU1
Label1 ;
}
Label1 ; DO调用此标签
- 如果程序包含
GOTO
,则它必须指向该程序内的私有标签。不能退出带有GOTO
的程序。
- 程序中不支持“
LABEL+OFFSET
”语法,但有几个例外:-
$TEXT
支持过程标签的标签+偏移量。 - 在程序标签的直接模式行中,支持转到“
LABEL+OFFSET
”,作为中断或错误后返回程序的一种方式。 -
ZBREAK
命令支持指定程序标签的“LABEL+OFFSET
”。 - 调用过程时生效的
$TEST
状态在程序退出时恢复。 - 表示过程结束的“
}
”可以位于该行的任何字符位置,包括第一个字符位置。代码可以在该行的“}
”之前,但不能在该行的后面。 - 就在最后一个大括号之前,出现了一个隐含的退出。
-
INDIRECT
和XECUTE
命令的行为就像它们在程序之外一样。
-
程序中的INDIRECT
、XECUTE
命令和JOB
命令
出现在程序中的名称间接、参数间接和XECUTE
命令不在该过程的作用域内执行。因此,XECUTE
的行为类似于程序外部的子例程的隐式DO
。
INDIRECT
和XECUTE
仅访问公共变量。因此,如果INDIRECT
或XECUTE
引用变量x
,则无论过程中是否也有私有x
,它都会引用公共变量x
。例如:
SET x="set a=3" XECUTE x ; 将公共变量a设置为3
SET x="label1" DO @x ; 访问公共子例程label1
同样,引用INDIRECT
或XECUTE
内的标签也是引用程序外的标签。因此,程序内不支持GOTO@A
,因为程序内的GOTO
必须指向程序内的标签。
同样,当在程序内发出JOB
命令时,它会启动该方法之外的子进程。这意味着对于如下代码:
KILL ^MyVar
JOB MyLabel
QUIT $$$OK
MyLabel
SET ^MyVar=1
QUIT
为了使子进程能够看到标签,方法或类不能包含在程序块中。
程序内的错误陷阱
如果从过程内设置错误陷阱,则需要将其直接设置到过程中的私有标签。(这与遗留代码不同,在遗留代码中,它可以包含“+Offset
”或例程名称。此规则与执行错误陷阱实质上意味着将堆栈展开回错误陷阱,然后执行GOTO
。)
如果程序内部发生错误,$ZERROR
将被设置为程序“LABEL+OFFSET
”,而不是私有的“LABEL+OFFSET
”。
要设置错误陷阱,使用正常的$ZTRAP
或$ETRAP
,但值必须是文字。例如:
SET $ZTRAP = "abc"
// 将错误陷阱设置为此块内的专用标签“abc”
注意:调用其他命名空间例程
DHC-APP>D Main^|"dhc-app"|PHA.MOB.TEST
小狗
输入 (1, 2, 或 3)回车: 2
2
returned value is 17
DHC-APP>D Main^|"dhc-app1"|PHA.MOB.TEST
D Main^|"dhc-app1"|PHA.MOB.TEST
^
DHC-APP>
注意:只有返回值的可以用报错。
DHC-APP>D Main^|"dhc-app"|PHA.MOB.TEST
小狗
输入 (1, 2, 或 3)回车: 2
2
returned value is 17
DHC-APP>w $$Main^|"dhc-app"|PHA.MOB.TEST
Main^PHA.MOB.TEST
W $$Main^|"dhc-app"|PHA.MOB.TEST
^
*Function must return a value at Main+10^PHA.MOB.TEST
DHC-APP>