第八章 瘋狂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
- 如果實際參數列表中的參數多於形式參數列表中的參數,則會出現
<PARAMETER>
錯誤,如下例所示:
Main
/* Passes 4 parameters to a procedure that takes 3 parameters.
This results in a <PARAMETER> 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
^
<NAMESPACE>
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
<PARAMETER>Main^PHA.MOB.TEST
W $$Main^|"dhc-app"|PHA.MOB.TEST
^
<COMMAND> *Function must return a value at Main+10^PHA.MOB.TEST
DHC-APP>