#

Tcl允許你使用「程序」覆寫已經存在的命令,或是新增自己定義的命令,簡單的說如果你認為Tcl裡現有的命令不夠使用,或是不好用,那你可以使用程序重新實作那些令你覺得不滿意的命令,或是定義新的命令以擴充功能。本篇文章介紹Tcl定義程序及使用程序的方式。

8.1 定義程序

程序在使用前必需先行定義,當新的程序定義好之後就可以像Tcl內建的命令一樣使用它。定義程序有兩個注意事項:

  1. 新的程序必需要有一個名稱,若名稱和其它程序或Tcl命令重複的話,最後一個定義的程序會蓋掉重覆名稱的其它程序及Tcl命令。
  2. 新定義的程序需要像Tcl的命令一樣,可以帶入參數來調整程序執行的行為。

如下是定義程序的語法:

proc name args body

name是新程序的名稱,你可以自行指定一個有意義的文字串。args稱之為參數清單,所有被帶入程序的參數都會被依序儲存在參數清單裡面,如果你不想讓程序有帶入參數的功能,那麼args必需指定為空字串。body是呼叫程序時要被執行程式碼,注意!! body內的程式碼在定義程序時並不會馬上被執行,而是等到呼叫程序時才會執行。另外,只要是在body內的程式碼都可以使用參數清單上所有的變數。以下是一個簡單的例子:



這個程式定義了一個名為add的程序,add可以接受兩個參數,接收到的參數會依序儲存在op1及op2變數裡。第2行是程序的主體,執行時expr會把op1及op2兩變數的值相加然後透過puts命令輸出。程式的第4~6行的動作稱之為呼叫程序,它就和使用Tcl內建的命令一樣。在第4行裡呼叫add程序時5及6會帶入op1及op2兩個變數,第5及6行也是以此類推。

程式執行結果:

11
5
3

不帶參數的程序可以這樣寫:



程式輸出:

Hi! 大家好

□ 覆寫已儲存的命令

事實上除非是很特殊的情況,否則真很少會去複寫Tcl內建的命令,建議大家請別這麼做。但如果你真的確定要這麼做的話,下面是一個簡單的例子,它複寫了內建的gets命令,而且把它變成輸出的命令:



程式輸出:

xxx

§ 關於程序裡的變數

一般的情況下如果沒有特別指定變數的作用範圍,所有在程序內建立的變數,都屬於區域性質的變數,它們都只能在程序內使用。

§ 為什麼要使用程序

程序有兩個很重要的特色,分別是 (1)程序只要定義一次,就可以使用任意次 (2)程序可以把較大的程式切割成許多較小的小命令。前者的特色,時常被應用來撰寫需要重複使用的功能,例如,你在設計一個文字編輯器的程式的時候,把「開檔案」的功能寫成程序就是一個好方法,因為它常常會被使用。而後者的特色在寫大程式時特別重要,因為把大的程式切割成許多小命令撰寫,會更利於程式的維護及修改,至於如何把程式漂亮的切開,這就要時間及經驗的累積了。

8.2 程序回傳值

程序可以使用return命令來回傳執行結果,被回傳的值可以用來當作命令代換的結果。下方的程式是一個計算1+2+...+N的程序,它會回傳累加後的結果。



上方的sum程序可以接受一個參數,並計算1+2+....$val的值。程式的第6行使用return把計算的結果回傳,回傳的值將被用來代換第8行的子命令。注意!! 只要在程序主體中遇到return命令,return會立刻把跟在自己後面的參數值回傳並結束程序。如果return後面沒有帶任何參數相當於回傳空字串,另外若是程序中沒有出現return命令,預設會回傳程序裡最後一個命令的回傳值。

程式輸出:


15


8.3 指定參數的預設值

Tcl允許你設定參數的預設值,被指定了預設值的參數,呼叫程序時可以選擇性的輸入,例如下方程式的第二個參數val2設定了預設值1,所以呼叫程序時第2個參數可以選擇性的附加,如果呼叫add時沒有給予第2個參數,那val2的預設值就是1。



程式執行結果:

7
6

注意!! 如果在程序的參數清單中第i個項目指定了預設值,那麼第i個項目以後的所有項目也都要指定預設值,例如下面是錯誤的語法,因為val2指定了預設值但val3沒有。



8.4 不固定數量的參數

Tcl還可以讓程序接受不固定數量的參數,只要參數清單的最後一個參數命名為args,如此一來Tcl會把多帶入的參數以清單的方式儲存在args變數中。注意!! 這裡的args是指參數清單最後一個變數名稱剛好是args,並不是proc語法中的args參數。以下是一個不定參數的例子:



我們把add改成可以接受1~N個參數,而且會把所有帶入的參數做相加的動作。例如:程式的第9行帶入6個參數,其中1會設定給參數清單中的val,其餘的2~6會以清單的格式儲存在args中。程式的3~5行使用foreach迴圈命令逐一取出儲存於args中的項目,並把它們累加到result變數中。 程式執行結果:

3
21

8.5 參數傳遞的細節

Tcl預設使用「傳值」的方式把呼叫程序時附加的參數傳遞給參數清單。「傳值」簡單的說就是複製參數值的意思,比如下方程式第6行的變數v,它的值會複製一份給參數清單中的val1,它們是獨立的個體彼此不會影響,因此就算第2行把val1的值加上100,第7行列印出來的值仍然是100。



程式執行結果:

val1 = 200
v = 100

17 個意見

GO | 2009年6月4日 下午3:48

喔 ~ 那假使共用同一個變數名稱也不會因為proc程序改變 而影響主程序吧 !
假如要影響主程序的參數值 就要用兩個":" 來讓外面的主程序受到影響嗎?

proc add {v} {
incr v 100
puts "proc_v = $v"
set ::v $v
}
set v 100
puts $v
add $v
puts $v

Dai | 2009年6月4日 下午5:01

是的~~凡是在proc裡的變數都是屬於區域的,除非有特別指定「名稱空間」像「::」,否則的話就算不同的proc有相同的變數名稱也不會互相影響。

承亦 | 2009年9月9日 上午11:10

#程式8-3 for那邊少了1個 "{"

Dai | 2009年9月10日 下午5:33

好的謝謝!!

匿名 | 2009年12月23日 下午4:46

程式 8.4

proc add {val『1』 args} {
set result $val

參數命名多了個 1

dai | 2009年12月23日 下午5:52

好的,謝謝你。

匿名 | 2011年6月17日 上午11:18

你好
想請教如果在A程序裡面需要引用B程序
那麼B程序需要做什麼設定才能被引用嗎
例如:

proc funA {} {
funB
}

proc ::funB {} {
puts "This is funB"
}

funA

是這樣嗎?

dai | 2011年6月17日 下午2:39

hi,

你的做法是可行的,如果說你的兩個funA,funB都是在同一個namespace裡,其實funB的前面不用加雙冒號,也可以。

匿名 | 2012年4月12日 下午12:08

Dai大大你好
這裡有一段 寫判斷兩線段是否相交的程序
###################################
# check intersection of two segments
#
# let pSegment1 be: [list {5 5} {0 0}]
# let pSegment2 be: [list {3 -5} {2 3}]
#
# IsIntersects return => 1
#

proc IsIntersects { pSegment1 pSegment2 } {

if {"" == $pSegment1 || "" == $pSegment2} {
puts "wrong # args: should be \"IsIntersects ?a segment of 2 points? ?a segment of 2 points?\""
return ""
}

if {2 != [llength $pSegment1] || 2 != [llength $pSegment2]} {
puts "wrong # args: should be \"IsIntersects ?a segment of 2 points? ?a segment of 2 points?\""
return ""
}
}
但我一直不了解這句的寫法...
if {"" == $pSegment1 || "" == $pSegment2} {
不知道Dai大大有沒有時間指點一下 :D

dai | 2012年4月12日 下午1:20

嗯 ~ 這一個判斷式的用意是要確保使呼叫 IsTntersects 程序的時候,$pSegment1 及 $pSegment2 都不能是空白字串。

兩個 || 是邏輯 or 的意思!!

整個if區塊的意思就是 : 如果$pSegment1是空字串 或 $pSegment2 是空字串,就顯示警告訊息,然後離開程序。

匿名 | 2012年4月12日 下午11:11

謝謝你,Dai大大
恍然大悟 :D

洪書凱 | 2013年6月26日 下午4:24

set i 0
while { $i <10 } {
set k {timecontrol}
if { $k == 1} {
puts "1111111111111111111111"
after 500
incr i
} else {
puts [clock format [clock seconds] -format "%S"]
after 1000
}
}

proc timecontrol {} {
if { [clock format [clock seconds] -format "%S"] == "08"} {
puts "11"
return 1
} else {
puts "22"
return 2
}
}
感覺我這程式碼沒進入程序中
可以請教一下 錯在哪嗎?

dai | 2013年6月26日 下午5:50


set k {timecontrol}

換成
set k [timecontrol]

不了解的話參考本站文件的:
04. Tcl - 命令模型

洪書凱 | 2013年6月27日 上午10:05

感謝老師

我還有一個大錯誤就是把 PROC 擺在下面

根本讀不到

匿名 | 2014年2月13日 上午10:28

與tcl本身無關, 內文8.1第六行 ; name是新程序的名稱,你可以自行指定一個有((((義意))))的文字串。 => 意義(?)

dai | 2014年2月13日 上午11:00

修正了,謝謝~

loxa | 2015年11月27日 上午3:55

dai大好
我目前遇到一個問題
我在用 proc 時 都無法讀取stdin的值
請問要如何解決,謝謝

留下您的意見

Theme Design by devolux.org. Converted by Wordpress To Blogger for WP Blogger Themes. Sponsored by iBlogtoBlog
This template is brought to you by : allblogtools.com | Blogger Templates