文章目录

第五章 疯狂Caché 运算符和表达式(五)
间接寻址

CachéObjectScript间接操作符(@)允许间接为变量赋值。间接寻址是一种通过数据字段的内容提供部分或全部命令行、命令或命令参数的动态运行时替换的技术。Caché在执行相关命令之前执行替换。

尽管与其他方式相比,间接寻址方式可以更通用的编码,但它从来不是必需的。始终可以通过其他方式复制间接的效果,例如使用XECUTE命令。

只有在提供明显优势的情况下才应该使用间接寻址。间接寻址可能会影响性能,因为Caché在运行时而不是在编译阶段执行所需的求值。此外,如果使用复杂的间接寻址,请确保清楚地记录代码。间接寻址有时很难破译。

间接寻址由间接运算符(@)指定,除下标间接寻址外,采用以下形式:

@variable

其中variable标识要从中获取替换值的变量。变量可以是数组节点。

下面的例程说明了间接寻址查看其右侧的整个变量值。

IndirectionExample
 SET x = "ProcA"
 SET x(3) = "ProcB"
 ; The next line will do ProcB, NOT ProcA(3)
 DO @x(3)
 QUIT
ProcA(var)
 WRITE !,"At ProcA"
 QUIT
ProcB(var)
 WRITE !,"At ProcB"
 QUIT

At ProcB

Caché识别五种类型的间接寻址:

  • Name indirection
  • Pattern indirection
  • Argument indirection
  • Subscript indirection
  • $TEXT argument indirection
    执行哪种类型的间接寻址取决于@变量出现的上下文。下面将分别描述每种类型的间接连接。

间接寻址符不能与点语法一起使用。这是因为点语法是在编译时解析的,而不是在运行时解析的。

名称间接

在NAME INDIRECT中,间接值为变量名、行标签或例程名。在执行命令之前,Caché将变量的内容替换为预期的名称。

名称间接只能访问公共变量。

使用INDIRECT引用命名变量时,间接值必须是完整的全局或局部变量名,包括任何必要的下标。

在下面的示例中,Caché将变量B设置为值6

/// d ##class(PHA.TEST.ObjectScript).TestNameIndirection()
ClassMethod TestNameIndirection()
{
	s Y = "B"
	w Y,!
	s @Y = 6
	i Y'=@Y w "不相等!",!
	w @Y,!
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestNameIndirection()
B
不相等!
6

重要提示:如果调用尝试使用间接方式获取或设置对象属性值,可能会导致错误。不要使用此类调用,因为它们试图绕过属性访问器方法(<PropertyName>Get<PropertyName>Set)。相反,可以使用$CLASSMETHOD$METHOD$PROPERTY)函数。

使用间接线标签引用线标签时,间接线标签的值必须是语法上有效的线标签。在下面的示例中,Caché将D设置为:

  • 如果N的值为1,则为标签FIG的值。
  • 如果N的值为2,则标签的值为GO
  • Stop在所有其他情况下的值。
    Caché将控制传递给该标签,该标签的值被指定给D。
B SET D = $SELECT(N = 1:"FIG",N = 2:"GO",1:"STOP")
 ; ...
LV GOTO @D

当使用间接地址引用例程名称时,间接值必须是语法上有效的例程名称。在下面的示例中,在do命令上使用name indirect来提供适当的过程名称。在执行时,变量loc的内容将替换预期名称:

Start
 READ !,"Enter choice (1, 2, or 3): ",num
 SET loc = "Choice"_num 
 DO @loc
 RETURN
Choice1()
 ; ...
Choice2()
 ; ...
Choice3()
 ; ...
```java
名称间接只能替换Name值。以下示例中的第二个`set`命令因上下文原因返回错误消息。在计算等号右侧的表达式时,Caché将`@var1`解释为对变量名的间接引用,而不是数字值。

```java
 SET var1 = "5"
 SET x = @var1*6

可以按如下方式重新转换该示例以正确执行:

 SET var1 = "var2",var2 = 5
 SET x = @var1*6

模式间接

模式间接是间接的一种特殊形式。间接运算符替换模式匹配。间接值必须是有效模式。当希望选择多个模式匹配,然后将它们用作模式匹配时,模式匹配间接性特别有用。

在下面的示例中,间接码与模式匹配一起使用,以检查有效的美国邮政编码(ZIP)。这样的代码可以采用五位数(Nnnnn)或九位数(Nnnnnnnn)形式。

第一个set命令设置五位数表单的模式。第二个set命令设置九位数表单的模式。仅当后置条件表达式($length(Zip)=10)的计算结果为true(非零)时,才会执行第二个set命令,这仅在用户输入9位数形式时才会发生。


/// d ##class(PHA.TEST.ObjectScript).TestPattern()
ClassMethod TestPattern()
{
	d GetZip
GetZip()
 SET pat = "5N"
 READ !,"Enter your ZIP code (5 or 9 digits): ",zip
 SET:($LENGTH(zip)=10) pat = "5N1""-""4N"
 IF zip'?@pat { 
   WRITE !,"Invalid ZIP code"
   DO GetZip()
 }
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestPattern()
 
Enter your ZIP code (5 or 9 digits): 1
Invalid ZIP code
Enter your ZIP code (5 or 9 digits): 55555
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestPattern()
 
Enter your ZIP code (5 or 9 digits): 99999999
Invalid ZIP code
Enter your ZIP code (5 or 9 digits): 55555-4444

将间接与模式匹配结合使用是本地化应用程序中使用的模式的便捷方法。在这种情况下,可以将模式存储在单独的变量中,然后在实际的模式测试期间间接引用它们。(这也是名称间接的示例)。要移植这样的应用程序,只需修改模式变量本身。

参数简介

在参数间接中,间接计算结果为一个或多个命令参数。相比之下,名称间接只适用于参数的一部分。

要说明这一区别,请将下面的示例与名称间接下给出的示例进行比较。

Start
 READ !,"Enter choice (1, 2, or 3): ",num
 SET loc = "Choice"_num 
 DO @loc
 RETURN
Choice1()
 w "1",!
 ; ...
Choice2()
 w "2",!
 ; ...
Choice3()
 w "3",!
 ; ...
Enter choice (1, 2, or 3): 11
DHC-APP>d Start^PHA.MOB.TEST
 
Enter choice (1, 2, or 3): 22
DHC-APP>d Start^PHA.MOB.TEST
 
Enter choice (1, 2, or 3): 22
DHC-APP>d Start^PHA.MOB.TEST

在本例中,@loc是间接参数的一个示例,因为它提供了参数的完整形式(即,LABEL^ROUTINE)。
在名称间接示例中,@loc是名称间接的示例,因为它只提供参数的一部分(标签名称,假定其入口点在当前例程中,而不是单独的例程中)。

在下面的示例中,第二个set命令是一个名称间接示例(只是参数的一部分,变量的名称),而第三个set命令是一个参数间接示例(整个参数)。

Argument
 SET a = "var1",b = "var2 = 3*4"
 SET @a = 5*6
 SET @b
 WRITE "a = ",a,!
 WRITE "b = ",b,!
 w @a
DHC-APP>d Argument^PHA.MOB.TEST
a = var1
b = var2 = 3*4
30

下标间接

下标间接寻址是名称间接寻址的扩展形式。在下标间接中,间接值必须是局部或全局数组节点的名称。下标间接与其他形式的间接在句法上是不同的。下标间接符使用以下格式的两个间接运算符:

 @array@(subscript) 

假设有一个名为^client的全局数组,其中第一级节点包含客户的名称,第二级节点包含客户的街道地址,第三级节点包含客户的城市、州和邮政编码。要写出数组中第一条记录的三个节点,可以使用以下形式的WRITE命令:

 WRITE !,^client(1),!,^client(1,1),!,^client(1,1,1)

执行此命令时,输出以下内容:

John Jones
42 Arnold St.
Boston, MA 02745

要写出一定范围的记录(比如前10条),可以修改代码,以便在for循环中执行写操作。例如:

 FOR i = 1:1:10 {
 WRITE !,^client(i),!,^client(i,1),!,^client(i,1,1)
 }

for循环执行时,变量i递增1,并用于选择要输出的下一条记录。

虽然比前面的示例更一般化,但这仍然是非常专业的代码,因为它显式指定了数组名称和要输出的记录数。

若要将此代码转换为更通用的形式,从而允许用户列出在三个节点级别中存储名称、街道和城市信息的任何数组(全局或本地)中的一系列记录,可以使用下标间接法,如下面的示例所示:

Start
 READ !,"Output Name, Street, and City info.",!
 READ !,"Name of array to access: ",name
 READ !,"Global or local (G or L): ",gl
 READ !,"Start with record number: ",start
 READ !,"End with record number: ",end
 IF (gl["L")!(gl["l") {SET array = name}
 ELSEIF (gl["G")!(gl["g") {SET array = "^"_name}
 SET x = 1,y = 1
 FOR i = start:1:end {DO Output}
 RETURN
Output()
 WRITE !,@array@(i)
 WRITE !,@array@(i,x)
 WRITE !,@array@(i,x,y)
 QUIT

输出子例程中的写入命令使用下标间接引用请求的数组和请求的记录范围。

在计算下标间接时,如果间接实例引用无下标的全局或局部变量,则间接的值是变量名称和第二个间接运算符右侧的所有字符(包括括号)。

对于局部变量,最大下标级别数为255。对于全局变量,最大下标级别数取决于下标,并且可能大于255,如使用Caché全局参数中的全局结构中所述。试图使用间接方式填充下标级别超过255个的局部变量会导致<语法>错误。

类参数可用作下标间接寻址的基数,与局部或全局变量用作基数的方式相同。例如,可以使用具有以下语法的类参数执行下标间接寻址:

 SET @..#myparam@(x,y) = "stringval"

$TEXT参数间接

顾名思义,$TEXT参数间接只允许在$TEXT函数参数的上下文中使用。间接地址的值必须是有效的$TEXT参数。

使用$TEXT参数间接主要是为了方便,以避免产生相同结果的多种形式的间接。例如,如果局部变量LINE包含条目引用“START^MENU”,则可以对行标签和例程名称使用NAME INDIRECT来获取该行的文本,如下所示:

SET LINETEXT = $TEXT(@$PIECE(LINE,"^",1)^@$PIECE(LINE,"^",2))

可以使用$TEXT参数间接以更简单的方式产生相同的结果,如下所示:

 SET LINETEXT = $TEXT(@LINE)

注意:间接引用在例程中更加方便,在类方法中会出错。