第五章 Caché 命令大全 DO 命令

第五章 Caché 命令大全 DO 命令

调用例程。

重点

  1. DO的后置表达式。
  2. 重置$TEST导致IF ELSE都执行DO语句的错误语法。
  3. 点语法的DO为过时语法。

大纲

DO:pc doargument,...
D:pc doargument,...

其中doargument是:

entryref(param,...):pc

参数

  • pc 可选-后置条件表达式。
  • entryref 要调用的例程的名称。
  • param 可选-要传递给被调用例程的参数值。

描述

DO命令和DO WHILE命令是独立且不相关的命令。在DO WHILE命令中,DO关键字和WHILE关键字可能由几行代码分隔;但是,可以立即标识DO WHILE命令,因为DO关键字后面跟着一个左花括号。

DO命令有两种形式:

  • 无参数

注意:不带参数的DO使用较旧的块结构语法(使用句点(.)每行的前缀),它已被DO WHILD大括号块结构所取代。因此,不带参数的DO被认为是过时的。它应该只用于维护现有的应用程序,而不应该在新的代码中使用。

  • 带参数
    使用参数调用指定的对象方法、子例程、函数或过程。Caché执行调用的例程,然后执行do命令之后的下一个命令。可以在传递或不传递参数的情况下调用例程。

DO命令无法接受来自被调用例程的返回值。如果调用的例程以带参数的QUIT结束,则DO命令将成功完成,但会忽略QUIT参数值。

每次调用do都会在流程的调用堆栈上放置一个新的上下文框架。$STACK特殊变量包含调用堆栈上当前上下文帧的数量。此上下文帧建立新的执行级别,递增$STACK$ESTACK,并为DO操作期间发出的新操作和设置$ZTRAP操作提供作用域。成功完成后,DO递减$STACK$ESTACK,并恢复NEWSET$ZTRAP操作。

参数

pc

可选的后置条件表达式。如果后置条件表达式附加到DO命令关键字,则如果后置条件表达式为TRUE(计算结果为非零数值),则Caché将执行DO命令。如果后置条件表达式为假(计算结果为零),则Caché不执行DO命令。

如果后置条件表达式附加到参数后,如果后置条件表达式为TRUE(计算结果为非零数值),则Caché将执行该参数。如果后置条件表达式为假(计算结果为零),则Caché跳过该参数并继续计算下一个参数(如果有)或下一个命令。请注意,由于Caché从左到右处理表达式,因此将计算包含表达式的参数的任何部分(如参数值或对象引用),并可能在计算后置条件表达式之前导致错误。如果do调用附加了后置条件的对象方法,则对象方法参数的最大数量为253。

entryref

要调用的例程(对象方法、子例程、过程或外部函数)的名称。可以将多个例程指定为逗号分隔的列表。

entryref可以采用以下任何形式。

代码 描述
label+offset 指定当前例程内的行标签。仅当调用未传递参数的子例程时,才能使用可选的+offset;在调用过程或将参数传递给子例程时不能使用它。offset是一个非负整数,它指定子例程开始执行的标签之后的行数。
label+offset^routine 指定驻留在磁盘上的命名例程内的行标签。Caché从磁盘加载例程,并从指定的标签开始执行。+offset是可选的。
^routine 驻留在磁盘上的例程的名称。系统从磁盘加载例程,并从例程的第一个可执行行开始执行。必须是文字值;变量不能用于指定例程。(请注意,^字符是分隔符,不是例程名称的一部分。)如果例程已修改,则当DO调用例程时,Caché加载例程的更新版本。如果例程不在当前命名空间中,则可以使用扩展例程引用指定包含该例程的命名空间,如下所示:^|"namespace"|routine.
oref.Method() 指定对象方法。系统访问对象并执行指定的方法,传递方法的参数列表param中指定的参数(如果有)。对象调用使用点语法:OREF(对象引用)和method()用点分隔;不允许使用空格。即使没有参数参数,方法也必须指定其左括号和右括号。支持以下语法形式:DO oref.Method(), DO (oref).Method(), DO ..Method(), DO ##class(cname).Method().

调用CACHESYS%例程时不能指定偏移量。如果尝试这样做,Caché会发出<NOLINE>错误。

  • 如果指定的标签不存在,则Caché会发出<NOLINE>错误。
  • 如果指定的例程不存在,则Caché会发出<NOROUTINE>错误。
  • 如果指定的方法不存在,则caché会发出<method is not exist>错误。
  • 如果使用扩展引用(例如, ^|"%SYS"|MyPro)并指定不存在的名称空间,则Caché会发出<NAMESPACE>错误。
  • 如果使用扩展引用并指定您没有权限的命名空间,则Caché将发出一个错误,后跟数据库路径,如下所示:<PROTECT> *^|^^c:\intersystems\cache\mgr\|MyRoutine.

param

要传递给子例程、过程、外部函数或对象方法的参数值。可以指定单个参数值,也可以指定逗号分隔的参数值列表。括号中有一个参数列表。如果未指定参数,则在调用过程或外部函数时需要使用括号,而在调用子例程时则是可选的。参数可以通过值传递,也可以通过引用传递。同一调用可以混合通过值传递的参数和通过引用传递的参数。通过值传递时,可以将参数指定为值常量、表达式或无下标的局部变量名。按引用传递时,参数必须引用.name形式的局部变量或无下标数组的名称

DO入口点的最大总参数值为382;DO方法或使用间接方式的DO的最大总参数值为380。这个总数最多可以包括254个实际参数和128个条件参数。可以使用…语法指定可变数量的参数。

无参数 Do命令。

无参数do命令执行同一程序中紧随其后的代码块。这段代码的每一行都用句点(.)表示前缀。然后,Caché执行该代码块之后的下一个命令。可以嵌套无参数DO块。后置条件表达式可以附加到无参数DO命令关键字,以指定是应该执行还是跳过紧跟在DO命令后面的代码块。

IF、FOR、DO WHILE和WHILE命令提供的块结构是执行相同操作的优选手段。不带参数的do命令继续受到支持,但是不鼓励在新的编码中使用它。请注意,do建立了一个新的执行级别;do While和其他块结构命令不会更改执行级别。

/// d ##class(PHA.TEST.Command).TestDoWithout()
ClassMethod TestDoWithout()
{
	DO
	. WRITE "进入 DO",!
	. DO
	.. WRITE "内部 DO",!
	.. QUIT
	.. WRITE "没有执行write",!
	. WRITE "返回外部 DO",!
	. QUIT
	. WRITE "没有执行write",!
	WRITE "执行完毕退出do"
}
DHC-APP>d ##class(PHA.TEST.Command).TestDoWithout()
进入 DO
内部 DO
返回外部 DO
执行完毕退出do

带参数的do命令

带有entryref参数的do命令调用在其他地方定义的一个或多个代码块的执行。要执行的每个代码块由其entryref指定。do命令可以指定多个代码块作为逗号分隔的列表执行。do命令的执行和逗号分隔列表中每个entryref的执行可以由可选的后置条件表达式管理。

DO可以调用子例程(带或不带参数传递)、过程或外部函数的执行。在完成代码块的执行后,在DO命令之后的下一个命令处继续执行。do命令调用的代码块不能向do命令返回值;返回的任何值都将被忽略。因此,DO可以执行外部函数,但不能接收该函数的返回值。

注意:DO不能调用大多数Caché内部(系统提供的)函数。尝试执行此操作会导致<SYNTAX>错误。DO可以调用一些内部函数。这些函数包括$CASE、$CLASSMETHOD、$METHOD、$METHOD和不推荐使用的$ZUTIL函数。DO无法接收函数的返回值。

可以将$CASE函数指定为DO命令参数。

不带参数传递的DO命令

不带参数传递的DO命令仅用于子例程。 使用不传递参数的do entryref(即,不指定param选项)利用了调用例程及其被调用子例程共享相同变量环境的事实。在do命令之后,子例程所做的任何变量更新都会自动对代码可用。

在不传递参数的情况下使用DO时,必须确保调用例程和被调用子例程引用相同的变量。

在下面的示例中,start(调用例程)和Exponent(被调用子例程)共享对三个变量的访问:num、powerr和resultSTARTnumpowerr设置为用户提供的值。当由do命令调用Exponent时,这些值自动可用于ExponentExponent引用numpowerr,并将计算值放入RESULT中。当Exponent执行RETURN命令时,控制在DO之后立即返回到WRITE命令。WRITE命令通过参考结果输出计算值:

/// d ##class(PHA.TEST.Command).TestDoStart()
ClassMethod TestDoStart()
{
Start  ; 将整数乘以指定的幂.
	READ !,"Integer= ",num QUIT:num="" 
	READ !,"Power= ",powr QUIT:powr=""
	DO Exponent()
	WRITE !,"Result= ",result,!
	RETURN
Exponent()
	SET result=num
	FOR i=1:1:powr-1 { SET result=result*num }
	RETURN
}
DHC-APP>d ##class(PHA.TEST.Command).TestDoStart()
 
Integer= 3
Power= 5
Result= 243

在下面的示例中,dopat引用的对象调用Admit()方法。该方法不接收参数或返回值。

 DO pat.Admit()

在下面的示例中,DO依次调用当前例程中的子例程InitRead1以及例程Test的子例程Convert

   DO Init,Read1,Convert^Test

在下面的示例中,Do使用扩展引用调用不同命名空间(Samples命名空间)中的例程Fibonacci

   ZNSPACE "USER"
   DO ^|"SAMPLES"|fibonacci

DO 和 GOTO

do命令可用于调用子例程(带或不带参数传递)、过程或外部函数。调用完成后,Caché将执行do命令之后的下一个命令。GOTO命令只能用于在不传递参数的情况下调用子例程。调用结束时,Caché发出一个退出命令,结束执行。

DO传递参数

当与参数传递一起使用时,do entryref将一个或多个值显式传递给被调用的子例程、过程、外部函数或对象方法。使用param选项将传递的值指定为逗号分隔的列表。使用参数传递时,必须确保使用参数列表定义了被调用的子例程。子例程定义采用以下形式:

>label( param)

其中,Label是子例程、过程、外部函数或对象方法的标签名,param是一个或多个无下标局部变量名的逗号分隔列表。

/// d ##class(PHA.TEST.Command).TestDoMain()
ClassMethod TestDoMain()
{
Main
	SET x=1,y=2,z=3
	WRITE !,"In Main ",x,y,z
	DO Sub1(x,y,z)
	WRITE !,"Back in Main ",x,y,z
	QUIT
Sub1(a,b,c)
	WRITE !,"In Sub1 ",a,b,c
	QUIT
}
DHC-APP>d ##class(PHA.TEST.Command).TestDoMain()
 
In Main 123
In Sub1 123
Back in Main 123

do命令传递的参数列表称为实际参数列表。定义为编码例程标签一部分的参数变量列表称为形式参数列表。当DO调用例程时,实际参数列表中的参数按位置映射到形式参数列表中的相应变量。在上面的示例中,第一个实际参数(X)的值放在子例程的形参列表的第一个变量(A中;第二个实际参数(Y)的值放在第二个变量(B)中;依此类推。然后,子例程可以通过使用其形参列表中的适当变量来访问传递的值。

如果实际参数列表中的变量比形式参数列表中的参数多,则Caché会发出<PARAMETER>错误。

如果形式参数列表中的变量比实际参数列表中的参数多,则不定义额外的变量。在下面的示例中,形参c未定义:

/// d ##class(PHA.TEST.Command).TestDoPara()
ClassMethod TestDoPara()
{
Main
	SET x=1,y=2,z=3
	WRITE !,"In Main ",x,y,z
	DO Sub1(x,y)
	WRITE !,"Back in Main ",x,y,z
	QUIT
Sub1(a,b,c)
	WRITE !,"In Sub1 "
	IF $DATA(a) {
		WRITE !,"a=",a
	}
	ELSE {
		WRITE !,"a is undefined"
	}
	IF $DATA(b) {
		WRITE !,"b=",b
	}
	ELSE {
		WRITE !,"b is undefined"
	}
	IF $DATA(c) {
		WRITE !,"c=",c
	}
	ELSE {
		WRITE !,"c is undefined"
	}
	QUIT
}
DHC-APP>d ##class(PHA.TEST.Command).TestDoPara()
 
In Main 123
In Sub1
a=1
b=2
c is undefined
Back in Main 123

可以为形式参数指定默认值,以便在未指定实际参数值时使用。

通过从do命令的实际参数列表中省略相应的参数,可以不定义任何变量。但是,对于每个省略的实际参数,必须包括一个逗号作为占位符。
在下面的示例中,形参b未定义:

/// d ##class(PHA.TEST.Command).TestDoParaB()
ClassMethod TestDoParaB()
{
Main
	SET x=1,y=2,z=3
	WRITE !,"In Main ",x,y,z
	DO Sub1(x,,z)
	WRITE !,"Back in Main ",x,y,z
	QUIT
Sub1(a,b,c)
	WRITE !,"In Sub1 "
	IF $DATA(a) {
		WRITE !,"a=",a
	}
	ELSE {
		WRITE !,"a is undefined"
	}
	IF $DATA(b) {
		WRITE !,"b=",b
	}
	ELSE {
		WRITE !,"b is undefined"
	}
	IF $DATA(c) {
		WRITE !,"c=",c
	}
	ELSE {
		WRITE !,"c is undefined"
	}
	QUIT
}

DHC-APP>d ##class(PHA.TEST.Command).TestDoParaB()
 
In Main 123
In Sub1
a=1
b is undefined
c=3
Back in Main 123

可以使用.指定可变数量的参数。语法:

/// d ##class(PHA.TEST.Command).TestDoMutPara()
ClassMethod TestDoMutPara()
{
Main
	SET x=3,x(1)=10,x(2)=20,x(3)=30
	DO Sub1(x...)
	QUIT
Sub1(a,b,c)
	WRITE a," ",b," ",c
	QUIT
}
DHC-APP>d ##class(PHA.TEST.Command).TestDoMutPara()
10 20 30

DO命令可以按值传递参数(例如,DO Sub1(x,y,z)),也可以通过引用传递参数(例如,DO Sub1(.x,.y,.z))。可以在同一DO命令中混合使用按值传递和按引用传递。下面的示例显示了通过值传递和通过引用传递之间的区别:

/// d ##class(PHA.TEST.Command).TestDoValue()
ClassMethod TestDoValue()
{
Main /* 值传递 */
	SET x=1,y=2,z=3
	WRITE !,"In Main ",x,y,z
	DO Sub1(x,y,z)
	WRITE !,"Back in Main ",x,y,z
	QUIT
Sub1(a,b,c)
	SET a=a+1,b=b+1,c=c+1
	WRITE !,"In Sub1 ",a,b,c
	QUIT
}
DHC-APP> d ##class(PHA.TEST.Command).TestDoValue()
 
In Main 123
In Sub1 234
Back in Main 123
/// d ##class(PHA.TEST.Command).TestDoPassValue()
ClassMethod TestDoPassValue()
{
Main /* 引用传递 */
	SET x=1,y=2,z=3
	WRITE !,"In Main ",x,y,z
	DO Sub1(.x,.y,.z)
	WRITE !,"Back in Main ",x,y,z
	QUIT
Sub1(&a,&b,&c)   /* 前缀(&P)是可选的按引用标记 */
	SET a=a+1,b=b+1,c=c+1
	WRITE !,"In Sub1 ",a,b,c
	QUIT
}
DHC-APP>d ##class(PHA.TEST.Command).TestDoPassValue()
 
In Main 123
In Sub1 234
Back in Main 234

Do 间接引用

可以使用INDIRECT为DO提供目标子例程位置。例如,可以通过在单独的例程中将各种菜单功能存储在不同位置来实现通用菜单程序。在主程序代码中,可以使用name indirect为do命令提供与每个菜单选项相对应的函数位置。

不能将INDIRECT与Cachéobject点语法一起使用。这是因为点语法是在编译时解析的,而不是在运行时解析的。

在NAME INDIRECTION中,间接运算符(@)右侧的表达式的值必须是名称(即行标签或例程名称)。在下面的代码段中,name indirect为DO提供了例程菜单中目标函数的位置。

  READ !,"Enter the number for your choice: ",num QUIT:num=""
  DO @("Item"_num)^Menu

do命令调用菜单中的子例程,其标签为Item与用户提供的Num值连接(例如,Item1Item2等)。

还可以使用间接的参数形式来用表达式的值替换完整的DO参数。例如,考虑以下DO命令:

   DO @(eref_":fstr>0")

如果fstr的值大于0,则此命令调用eref的值指定的子例程。

使用参数后置条件句

可以使用参数后置条件表达式为DO命令选择目标子例程。如果后置条件表达式的计算结果为FALSE(0),则Caché将忽略相关联的子例程调用。如果后置条件表达式的计算结果为true(1),则Caché执行相关联的子例程调用,然后返回do命令。可以在do命令及其参数上使用后置条件句。

DO:F>0 A:F=1,B:F=2,C

DO命令有一个后置条件表达式;如果F不大于0,则不执行DO的任何部分。do命令的参数也有后置条件表达式。DO使用这些参数后置条件来选择要执行的子例程(A、B或C)。满足真值条件的所有子例程都将按给出的顺序执行。因此,在上面的示例中,始终执行没有后置条件的C:如果F=1,则同时执行AC;如果F=2,则执行B和C;如果F=3(或任何其他数),则执行C。要将C确立为真正的默认值,请执行以下操作:

 DO:F>0 A:F=1,B:F=2,C:((F'=1)&&(F'=2))

在此示例中,执行一个且仅执行一个子例程。

在下面的示例中,do命令采用后置条件,其每个参数也采用后置条件。在这种情况下,不执行第一个参数,因为它的后置条件为0。执行第二个参数是因为其后置条件为1。


/// d ##class(PHA.TEST.Command).TestDoPost()
ClassMethod TestDoPost()
{
Main
	SET x=1,y=2,z=3
	WRITE !,"In Main ",x,y,z
	DO:y Sub1(x,y,z):0,Sub2(x,y,z):1
	WRITE !,"Back in Main ",x,y,z
	QUIT
Sub1(a,b,c)
	WRITE !,"In Sub1 ",a,b,c
	QUIT
Sub2(d,e,f)
	WRITE !,"In Sub2 ",d,e,f
	QUIT
}

DHC-APP>d ##class(PHA.TEST.Command).TestDoPost()
 
In Main 123
In Sub2 123
Back in Main 123
/// d ##class(PHA.TEST.Command).TestDoCondi(1)
ClassMethod TestDoCondi(y)
{
	d:y>1 ##class(PHA.TEST.Command).TestCatch():y=2,##class(PHA.TEST.Command).TestCatchStack():y=3
}
DHC-APP>d ##class(PHA.TEST.Command).TestDoCondi(2)
In the CATCH block
Status exception
 
错误 #5002: Cache错误: My Status Error
DHC-APP>d ##class(PHA.TEST.Command).TestDoCondi(3)
In the TRY block
In the CATCH block
&lt;DIVIDE&gt;zTestCatchStack+3^PHA.TEST.Command.1
In the nested TRY block
In the nested CATCH block
&lt;UNDEFINED&gt;zTestCatchStack+11^PHA.TEST.Command.1 *fred
 
The Execution Stack
stk=3
stk(1)="DO"
stk(1,"PLACE")=" 0"
stk(2)="DO"
stk(2,"PLACE")="zTestDoCondi+1^PHA.TEST.Command.1 2"
stk(3)=""
stk(3,"PLACE")="zTestCatchStack+11^PHA.TEST.Command.1 1"

DO调用的大多数对象(OREF)方法都可以采用后置条件参数。但是,$System对象方法不能采用后置条件参数。尝试这样做会生成<syntax>错误。

请注意,因为Caché严格按照从左到右的顺序计算表达式,所以在Caché计算后置条件参数之前,会计算包含表达式的参数(并且可能会生成错误)。

使用参数后置条件句时,请确保没有不必要的副作用。例如,考虑以下命令:

DO @^Control(i):z=1

在本例中,^Control(I)包含后置条件z=1测试为真时要调用的子例程的名称。无论z是否=1,Caché都会计算^Control(I)的值,并相应地重置当前的global naked indicator。如果z=1为FALSE,则Caché不执行DO。但是,它确实重置了global naked indicator,就好像它已经执行了DO一样。

$TESTDO

无参数DO始终保留$test特殊变量的值。

使用do调用过程时,Caché通过在退出过程时将$test恢复到调用时的状态来保留$test的值。但是,当使用do调用子例程(无论是否传递参数)时,Caché不会在整个调用过程中保留$test的值。

要在do调用中保存$test值,可以在调用之前将其显式赋值给一个变量。然后,可以在调用之后的代码中引用该变量。

以下代码说明了将do与遗留的if命令(设置$test)一起使用时可能导致的一些意外的$test行为。此行为不会发生在标准(代码块)IF命令中,因为标准IF没有设置$TEST

/// d ##class(PHA.TEST.Command).TestDoTest()
ClassMethod TestDoTest()
{
Start ; 此例程使用旧的IF命令语法
	SET x=1
	w "Start:"_$test,!
	IF x=1 w "Start IF:"_$test,! DO Sub1(x) ; sets $TEST to TRUE (1)
	ELSE  w "Start ELSE:"_$test,! DO Sub2(x)
	QUIT
Sub1(y)  ; a subroutine that evaluates a FALSE IF
	WRITE !,"hello from subroutine 1",!
	IF y=2 WRITE " - IF in Sub1 was TRUE",! ; Set $TEST to FALSE (0)
	ELSE  WRITE " - IF in Sub1 was FALSE",!
	w "Sub1:"_$test,!
	QUIT
Sub2(z)  ; another subroutine
	WRITE !,"hello from subroutine 2",!
	QUIT
}

乍一看,可能会认为Start将只调用Sub1,然后退出。
实际上,执行此代码会产生以下结果:

DHC-APP>d ##class(PHA.TEST.Command).TestDoTest()
Start:0
Start IF:1
 
hello from subroutine 1
 - IF in Sub1 was FALSE
Sub1:0
Start ELSE:0
 
hello from subroutine 2

这种意外行为是因为在Sub1中重置了$test值,导致Caché在Start中执行Else命令。处理顺序如下:

  1. Caché将start中的if命令表达式求值为true。它将$test设置为true并调用Sub1
  2. Caché将Sub1中的IF命令表达式求值为FALSE。它将$test设置为false,然后执行以下ELSE命令。
  3. Caché在遇到退出时返回启动。
  4. Caché执行start中的Else,并执行对Sub2DO调用。它执行Else是因为在Sub1中将$test设置为false,替换了start中的if命令设置的TRUE值。

要产生预期的行为,可以用附加的if替换StartSub1中的Else。例如,可以按如下方式:

Start
  SET x=1
  IF x=1 DO Sub1(x)
  IF x'=1 DO Sub2(x)
  QUIT

猜你喜欢

转载自blog.csdn.net/yaoxin521123/article/details/107150194