Tcl的變數介紹

出自GaryLee
跳轉到: 導覽, 搜尋

目錄

導引

在前面的文章中,我們已經簡略地介紹Tcl的變數置換規則。本文我們將以更深入的角度來介紹Tcl的變數機制。

說明

在Tcl之中,最直接建立變數的方式就是透過set這個命令。如果詳細的看看set命令使用說明,您可看到Tcl的完整語法如下:

set varName ?value?

其中,set命令允許第二個參數是選用的。在只有給定一個參數的狀況下,Tcl直譯器會在目前的執行環境下找尋有無varName參數所指定名 稱的變數。若該變數名稱存在,則將該變數的內容傳回。反之,若該變數不存在將會產生錯誤狀態。通常,只有一個參數的用法,可以用來作為變數置換外的另一個 選擇。因為這個方法同樣的可以讀取變數的值。例如:

set a "test for set command."
puts [set a]

而第二個參數如果有被給定,且目前的執行環境中有對應第一個參數指定名稱的變數存在時,Tcl將會將該變數的值設定為第二個參數所指定的值。若第一個參數 所指定變數不存在。Tcl首先會建立該變數,接著才將變數的值設定為第二個參數所指定的字串。通常除了簡單的變數設定,都會再搭配上各種置換的使用以便達到更複雜應用。例如:

for {set i 0} {$i<10} {incr i} {
	set a$i $i
}
puts "$a0,$a1,$a2,$a3,$a4,$a5,$a6,$a8,$a9"

在這個範例中,我們運用到for迴圈的機制。目前雖然還沒有介紹到這個機制,不過我們可以大概說明一下他的用法。上面的範例中,這個迴圈一個會跑10次,每次都會將變數i的值往上加一。而變數i的初始值為0。所以,在迴圈內的set命令,實際上等於下面的命令:

set a0 0
set a1 1
set a2 2
set a3 3
set a4 4
set a5 5
set a6 6
set a7 7
set a8 8
set a9 9

puts "$a0,$a1,$a2,$a3,$a4,$a5,$a6,$a8,$a9"

透過迴圈的機制與各項置換的功能,我們可以利用簡單的幾行指令一次設定所有的變數。這項技術也會用在Tcl的陣列機制中。

Tcl也提供了讓釋放變數所佔用的記憶體的命令,這個命令就是unset。unset的語法如下:

unset name ?name name ...?

unset後面可以同時接好幾個希望釋放的變數。

舉例而言,我們需要將前面範例所建立的變數的記憶體釋放,可採用下面的方式:

for {set i 0} {$i<10} {incr i} {
	unset a$i
}


這上面命令執行後,a0 - a9這些變數都已經不存在了。在unset命令執行過後,任何想要存取這些變數,將會導致no such variable的錯誤。通常,如果程式不是很大的狀況下,我們都不需要unset任何變數。如果您覺得變數的內容會佔用很大的記憶體,例如該變數的內容 是由檔案所讀進來的,此時就可使用unset命令在變數不再需要使用時予以釋放。

Tcl陣列的陣列機制 陣列在Tcl中具有很重要的角色。例如:在Tcl中有一個稱之為tcl_paltform的陣列,這個陣列記載了目前執行平台的一些資訊。其他尚有釵h的 陣列記載了一些關於Tcl本身的重要資訊。在撰寫Tcl程式時我們也可以建立自己的陣列,以方便我們自己管理程式的資訊。因為,我們可以將資訊集中在同一 個陣列名稱下予以管理。

在Tcl中的陣列稱之為結合式陣列(Associated Array),其特點為可使用字串作為陣列的索引值,而非單純的數字作索引。在Tcl中最簡單的形式便是以下面的方式來建立並且設定一個陣列。

set a(b) c

其中,a是陣列的名稱,b為索引值,c為我們為b這個index值所設定的值。其中,在括號中間的索引值可以放入任何字串。再舉一個範例:

set staff1(id)       "00001"
set staff1(name)     "peter"
set staff1(birth)    "1970/01/01"
set staff1(sex)      "man"

我們建立了一個名為staff1的陣列。我們可說這個陣列內目前共有四個元素(element)。其索引值分別為id, name, birth,sex。各元素的內容依序為"00001", "peter", "1970/01/01"及"man"。

要特別注意的是,在Tcl陣列命名時,其索引值字串的範圍是以左括號起始,至右括號為止。只要在這中間的字串都會被當成索引值的一部份。例如,若我們為staff1這個陣列設定了一個新的索引值如下:

set staff1("addr") "unknown."

此時,新增的索引值為"addr",而非addr。也就是說,連代表字串範圍的雙引號也一併被當成陣列的索引值。上面的這個範例,可修改為下面這樣,已符合前面的設定:

set staff(addr) "unknown."

雖然,Tcl是使用字串作為索引值。但是,在使用上我們依舊能夠使用類似於C語言中數字型的陣列。例如:

set myarray(0) "abc"
set myarray(1) "def"
set myarray(2) "test" 

# 本for迴圈的用法類似於一般C/C++的for迴圈使用方法。
# 在迴圈首次執行時,會設定變數i 為0。然後,測試
# 是否$i<3的表示式結果為true。若是,則進行迴圈內部
# 的指令,反之,則結束迴圈。在每次迴圈結束時,會執
# 行incr i的指令,將變數i 加一。
for {set i 0} {$i < 3} {incr i} {
	puts $myarray($i)
}

這個範例的結果為:

abc    def    test

上面這個範例有兩個重點,首先為陣列的使用方法。陣列的使用方法與一般變數相同,只需要在陣列名稱前面加上"$"。 此外,在括號中,置換效果依舊有效。所以,上面的範例才可依據變數i 的數值來進行變動。

在C/C++中,還有所謂的多維陣列。雖然在Tcl中沒有直接支援所謂的多維陣列,但是在使用上,我們可以毫不費力的以結合式陣列模擬出多維陣列的效果。例如,我們以下面的範例製作一個3x3的二維陣列。

for {set i 0} {$i < 3} {incr i} {
	for {set j 0} {$j < 3} {incr j} {
		set arr($i,$j) "0"
	}
}

我們可以利用parray這個命令來察看陣列的內容。parray這個命令會將陣列中所有索引值及內容列印至stdout上。您不妨試試使用parray這個命令將Tcl內建的幾個陣列顯示出來看看:

parray tcl_platform

在Tcl中內建了一個標準命令--array,來支援對於陣列的處理。array命令基本語法如下:

array option arrayName ?arg arg ...?

目前所支援的選項有:anymore, donesearch, exists, get, names, nextelement, set, size, startsearch, unset。

就基本的陣列設定與存取而言,set, unset, get及exists這四個命令是最常用的選項。set選項可將一組包含陣列索引及值的串列予以製作成陣列。關於串列的詳細用法,詳見串列說明。

若我們有下面的串列:

{1 "id" 2 "name" 3 "passwd" 4 "job"}

透過set選項,我們可以將其製作成一個索引為1, 2, 3, 4的陣列。

array set myarray {1 "id" 2 "name" 3 "passwd" 4 "job"}

我們可以利用parray這個命令來察看陣列的內容:

parray myarray

myarray(1) = id
myarray(2) = name
myarray(3) = passwd
myarray(4) = job

當您已經有一個串列時,我們可以透過set這個選項,很快的將串列轉換為陣列。 Tcl的串列 在Tcl中,何謂一個串列呢?最基本且最常見的形式就是命令列。例如,在Tclsh的互動模式下,我們可以給定如下的命令:

> set a 100

對於Tcl而言,這就是一個串列。而這個串列共有三個項目,以空白作為間隔字元。第一個項目是"set",第二個項目是"a",最後一個項目自然就是"100"了。

所以,對於Tcl來說任何以空白字元作為分隔的字串,都可以當成一個串列。大部分與串列相關的Tcl命令都是以"l"為開頭字母。下面所列出的是所有與串列有關的命令:

  • list 根據給定的參數製作對應的串列。
  • lindex 從串列中取得指定的項目。
  • lappend 新增一個項目到指定的串列中。
  • linsert 插入一個項目至串列中的某個位置。
  • llength 計算串列中項目的總數。
  • lrange 取出某個範圍的子串列。
  • lreplace 將串列某個範圍的項目置換成新的項目。
  • lsearch 搜尋某個項目在串列中的位置。
  • lsort 對串列進行排序。
  • join 將串列合併為一個字串。
  • split 分割字串成為串列。

這裡要強調的是,在Tcl中,串列原則上與字串是沒有兩樣的。只是當字串要當作串列來處理時,要依據一些規則行事。

  1. 空白字元為串列項目的分隔符號。
  2. 若反斜線後緊接著空白字元,該空白字元將不被視為分隔符號。
  3. 若某項目的啟始為左大括號"{",則一直到對應的右大括號"}"為止,整個字串將被視為單一的項目。無論其中有多少分隔字元。

基本的處理規則是這樣的,不過在實際寫程式時,前面文章中所介紹的各項置換規則仍然具有原本的作用。

就讓我們透過llength這個命令來說明這些規則。首先,我們先看一下llength命令的語法:

llength list

這個命令很簡單。只有一個參數,這個參數就是要處理的串列字串。他會傳回一個整數,代表該串列中項目的總數。所以,就前面的範例來看,計算出的項目總數自然為3:

set myList "set a 100"
puts [llength $myList]

要注意的是,因為串列在Tcl中其實就是字串。所以處理時,要傳myList這個變數的內容,而非變數名稱。若給定下面的字串:

set myList "set {a 100}"

這樣所計算出來的數目是2,而非3。起因於規則三的限制,使得"a 100"成為單一的項目。不過,如果是下面的例子,將會得到項目總數為3:

set myList "set a{ 100}"

主要的原因在於第三個條件成立的一項關鍵為左大括號必需為啟始字元。然而,這個例子中並非如此。

再看一個例子:

set myList "set a\\ 100"

依據第二個規則,我們依舊得到2的結果。要特別注意的地方是,我們使用了兩個反斜線。主要的原因在於Tcl處理這個命令時,會啟動一次的反斜線置換規則。只給定一個反斜線,將會因為第一次的反斜線置換而被取代掉。因而需要兩個反斜線。

在瞭解串列的基礎用法後,只要再瞭解前面所說各項與串列有關的命令。相信您很快的就可領會串列的精神。以下就讓我們一邊瞭解這些命令,一邊再更進一步熟悉串列。

製作串列 -- list

雖然串列是一種字串,如果要處理是否需要加上大括號,或是反斜線等問題。對於辛苦工作的程式設計師而言,實在是件麻煩的事情。要製作串列的一個簡單的方式 就是利用list這個命令來製作。list命令後面接受不定長度的參數。每個參數會被當成一個串列項目,並且視需要加上大括號或是反斜線。最後的串列字串 會當成結果傳回。例如:

set myList [list 0 1 2 3 4 {5 6 7 8} one"two"]

這個命令的結果為0 1 2 3 4 {5 6 7 8} one"two"。其中,值得注意是最後一個參數中被加入了雙引號,所以list命令自動雙引號加上了反斜線,以免後面進行串列相關的運作時被誤判。

取得指定的串列項目 -- lindex

取得串列項目通常使用lindex這個命令。他的基本語法如下:

lindex list index

第一個參數是要處理的串列,第二個參數是要取得第幾個串列。考慮下面的串列:

set myList {1 {2 3 4 5} 6 {7 8} 9 0}

如果您使用llength命令,您會得到項目總數為6。所以如果您使用下面的命令:

set myItem [lindex $myList 2]

在myItem這個變數中會得到6。沒錯!正是如此,這是因為串列的索引是由0開始。也就是第一個項目索引值為0,第二個項目為1,第三個項目為2,以此類推。

若您指定索引值為1,您將會得到"2 3 4 5"的字串。要注意的是,大括號被當成分隔用,而非項目本身的一部份。

取得串列項目的總數 -- llength

要取得串列項目標準作法就是使用llength的命令進行。這個命令會傳回一個總數。在前面我們已經看到他的用法了。且讓我們看看一些比較極端的例子。

set myList1 {}
puts [llength $myList1]
set myList2 {{} {}}
puts [llength $myList2]

在上面的例子中,myList1所得到的總數為0。這是因為在大括號中間沒有任何東西,所以就某個方面來說,算是一個空集合。空集合中的元素數目自然為 0。 而第二個例子中,所得到的數目將會是2。 雖然在串列內的是兩個空集合,每個空集合也會被算成一個項目。所以我們會得到項目總數為2。

新增串列項目 lappend

在實際的撰寫程式過程中,常常遇到的狀況是需要在原有的串列後面新增項目。一個方式使用字串連結的方式自行製作串列,但是這樣又要自己處理特殊字元的問 題。正式的方式應當是使用lappend命令來達到這樣的效果。lappend命令至少有兩個參數。第一個參數您需要給一個變數的名稱。請注意,是變數的 名稱,不是變數的內容。第二個參數之後是您要新增至串列中的各項目。你所給定的項目將會被新增到第一個參數所指定的參數中。例如:

set myList [list 0 1 2 3 4 5]
lappend myList 6 7 8 9
puts $myList

我們會得到"0 1 2 3 4 5 6 7 8 9"這樣的串列。為何這個命令要使用變數的名稱來處理串列?主要的因素在於要獲得較佳的效能。如果直接使用變數內容來處理,在遇到一個極大的串列時,將是 記憶體配置及字串處理上面就會耗掉不少CPU時間。直接讓lappend處理變數本身將可避免不必要的效能損失。

插入串列項目 -- linsert

lappend只能將新的項目置於原來串列的最後面。如果要從串列的中間插入。lappend就無法達到此效果。與lappend 不同的是,linsert不直接對串列本身作用。因此,linsert 的第一個參數並非串列變數的名稱,而一個實際的串列。後面接著是要插入的位置索引,剩下的才是要插入的項目。例如:

set myList [list 0 1 5]
set myList [linsert $myList 2 2 3 4]
puts $myList

在這裡我們得到"0 1 2 3 4 5"的結果。您可看到lappend與linsert兩者對於串列的處理上有明顯的差異。因此,對於大型串列請盡量使用lappend。若是對於大型串列使用linsert,可能在效能上面表現會較差。

取得某個範圍的串列項目 -- lrange

當串列的項目相當多時,我們往往會希望能夠只取出部分的範圍來處理。在這個時候,lrange就派上用場。透過這個命令,您就可以很方面的取得從某個開始一直到您所指定的數目結束的所有項目內容。例如:

set myList [list 0 1 2 3 4 5]
set subMyList [lrange $myList 1 4]
puts $subMyList

上面的範例中,我們由第二個項目(還記得吧!第二個項目的索引值是1 )向後取出4 個項目。也就是說,我們將會得到"1 2 3 4" 這個結果。

置換一部份的項目 -- lreplace

若要置換一個串列中的一部份項目時,您應當要使用lreplace這個命令。lreplace會依據您所給的串列加以置換。lreplace所使用的是串 列本身,而非存放串列的變數。您需要給定要置換的範圍,接著指定要插入的新項目。如此將可將該範圍的項目置換成您所想要的。例如:

set myList [list 0 x x x x 5]
set myList [lreplace $myList 1 4 1 2 3 4]
puts $myList

在置換範圍指定方面的用法語lrange是相同的,先給定起始項目的索引,接著再給要置換的項目的總數。上面這個範例所得到的結果為"0 1 2 3 4 5"。

搜尋某個項目 -- lsearch

有些時候,您並不確定在串列中是否有您想要找的項目。或許這個串列是由使用者輸入而來、又或許是由檔案讀入所得。為此,您需要有個方式來為您找尋特定的項 目。這就是lsearch出現的目的。lsearch所傳回的是您所指定要搜尋的項目之索引值。當然,不見得您所要的項目會存在於串列中,此時,您將會得 到-1。所以,您可以根據判斷lsearch是否傳回-1來決定資料存在與否。例如:

set myList [list 0 1 2 3 4 5 6]
set idx [lsearch $myList 7]
if {$idx < 0} {
        puts "Not found"
} else {
        puts "Found 7 at $idx"
}

上面的範例中,我們嘗試在串列中尋找7。明顯地,該項目並不存在於串列中。因而,我們得到-1。接著藉由if敘述來判斷是否找到。實際上,lsearch 共提供3 種搜尋方式。分別為exact ,glob,及regexp搜尋。預設為glob。也就是您可以使用類似於檔案目錄的萬用字元的方式作為搜尋條件。詳細用法請您參照後面關於 lsearch的詳細說明。

排序串列 -- lsort

很多場合中,我們會有排序的需求。如學生成績的排序,通訊錄依照姓名排序等等。在串列中,尤其少不了排序。當要排序時,最快的方法就是藉助lsort命令來進行。例如:

set myList [list 5 4 3 2 1 0]
set myList [lsort $myList]
puts $myList

我們馬上可得到排序過的結果 -- "0 1 2 3 4 5"。不過,要特別注意的時,在沒有特別指定的狀況下,lsort實際上是使用所謂的字典法來進行排序。所以,上面的結果是我們所要的,僅能說是巧合。不 過,別擔心。lsort 提供相當多的搜尋方法。無論是數字,英文字都可排序,甚至您可以指定自己的比較大小的方式讓lsort使用。這方面的詳細是說明請參考後面對於lsort 的介紹。

將串列合併為字串 -- join

看到這個命令,您或許會覺得納悶。不是說過串列就是字串嗎?為何還要合併為字串呢?請仔細想想,串列的確是字串沒錯,但是他還包含了一些分別項目用的字 元,如反斜線,大括號等。這些往往是我們實際輸出結果時,未必樂意見到的。透過join這個命令,我們就可避開這個惱人的問題,並且指定我們自己想要的項 目分隔字元。例如:

set myList [list 0 1 2 3 4 5]
set myList [join $myList ":"]
puts $myList

結果我們得到"0:1:2:3:4:5"。透過這樣的方式,我們可以很輕易的將串列轉換為其它工具或是語言所需要的格式。

將字串分割為串列 -- split

既然有將串列合併為字串的命令,自然就有將字串分割為串列的命令了。其實說穿了,將字串分割為串列,不過就是將不同格式的分隔字元轉換成為Tcl 串列所相容的分隔字元。例如:

set myStr "0:1:2:3:4:5"
set myList [split $myStr ":"]
puts $myList

我們得到的輸出為"0 1 2 3 4 5"。這個範例中,我們透過split告知將字串中的":" 字元視為分隔字元。於是,split命令,就會自動將該字元轉換成Tcl相容的串列分隔字元。雖然,這個範例中僅指定冒號為分隔字元。實際上,您可一次多 指定幾個字元,則split 在處理時,會一併轉換為Tcl串列的分隔字元。

串列是Tcl下面使用非常多的一種功能,任何一個Tcl程式都一定有他的存在。因為最基本的Tcl 命令語法,便是串列。所以,對於Tcl 的程式設計師而言,對於串列的掌握度越高,則越能發揮出Tcl的威力。

個人工具