陣列是一種透過索引值來存取資料的資料結構,使用起來有一點像清單,但清單只能使用線性的整數索引值來存取對應的項目,而陣列則可以讓你自行指定索引值。基本上Tcl對陣列索引值的規定非常自由,甚至可以是不連續的整數或是字串。這個章節的內容會介紹Tcl的陣列的使用方法。
10.1 使用set命令建立陣列變數
使用陣列時必需先了解幾個簡單的觀念:- 陣列則是以「元素」做為基本的儲存單位,這就類似清單是以項目為基本的儲存單位一般。
- 若你想要存取陣列裡的元素就一定要透元素的「索引值」才辦得到。對於索引值你可以把它想像是元素的指位器,透過這個指位器你才可以把資料安置在陣列裡的某個元素裡或是把資料取出使用。
基本上Tcl可以使用2種方法建立陣列變數,第一種是之前常用的set的命令,第二種是專門用來處理陣列的array命令。現在先讓我們來看看set建立陣列的用法,使用set建立陣列是很簡單的就如同建立一般的變數,只要在變數名稱後面加上一對小括號,這樣Tcl就會把這個變數當成是陣列,例如:
上面的例子建立了一個名為arr的陣列,其中0即是索引值,而apple是正真要儲存在陣列裡的資料,換句話說arr陣列目前只有一個索引值是0且內容是apple的元素。在這個例子裡Tcl偷偷做了下面兩個動作:
- 如果陣列變數arr不存在Tcl會自動建立它。
- 把apple設定給索引值為0的元素,若元素不存在Tcl會自動建立它。
如果想要取出元素的值,方法也跟變數一樣,只要前面加上$號就可以代換出來了:
程式範例:
上面的例子示範了各種建立元素的可能情況。第2行說明索引值不需要是連續的整數,第3行說明字串也可以作為索引值,第4行說明索引值可以是一串包含空白的字串,第6行說明索引值使用變數代換一樣行得通,最後1行示範使用unset命令刪除arr陣列中索引值是99的元素。
10.2 array命令基礎操作
set命令是以精準的索引值來操作陣列的元素,這樣讓陣列使用起來有點像「只是有加上小括號及一些字串的變數」,事實上真的很容易讓人產生這樣的感覺,而且使用上也感覺不出陣列的特色。如果你想要真正體會陣列的方便之處,就要使用專為陣列設計的array命令,它有下列的特點:- 讓你可以一次建立非常多的元素
- 讓你使用搜尋的方式一次取出所有符合關鍵字的元素
- 讓你可以配合foreach迴圈逐一取出陣列中的元素
□ 建立陣列
array就像string一樣是一個多功能的命令,同樣也使用第一個參數來決定正真要執行的功能。接下來就讓我們從建立陣列開始,如下是array命令建立陣列的語法:array set arrayName list
array set以清單list的奇數項目為索引值,偶數項目為元素值來建立名為arrayName的陣列。arrayName必需是一個已經存在的陣列變數或是不存在的變數名稱,但不可以是已存在的一般變數。list必需是項目數量為偶數的清單,其中奇數索引值的項目都會被當成陣列的索引值,而偶數索引值的項目都會被當成元素值。若list為空的清單array命令會建立一個空的陣列。注意!! 這一章會一直出現arrayName這個參數,如果沒有特別說明arrayName都是指要操作的目標陣列,而且它是一個陣列變數的名稱。
程式範例:
程式的第1行建立了一個名稱為arr1的陣列,並立即建立了3個元素,索引值分別是apple、book、monkey,第2行再建立2個元素,索引值分別是cat及table,第3行再加一個索引值是duck的元素,所以最後arr1總共有6個元素。第4行的parray是專門輸出陣列的命令,它可以用人性化的格式輸出陣列裡的元素。注意!! 雖然我們是依apple、book、monkey、cat、table、 duck的順序來建立陣列元素,但事實上Tcl為了效率上的考量並不會照這個順序儲存元素。換句話說使用parray列印陣列內容時,順序可能和你建立陣列元素時不一樣。
程式輸出:
arr1(apple) = 蘋果 arr1(book) = 書本 arr1(car) = 車子 arr1(duck) = 鴨子 arr1(monkey) = 猴子 arr1(table) = 桌子
□ 取出陣列元素
接下來我們要學習取出元素的方法,在開始之前有一個必需先知道的前題,就是array命令使用搜尋的方式來找出元素,所以搜尋出來的結果可能是0~N個元素。這樣的做法雖然會比精確指定索引值慢一些,但對於統計或分析陣列中的元素則是非常有利的。如下是array命令取出元素的語法:array get arrayName ?pattern?
array get以清單的方式回傳arrayName裡索引值符合pattern的元素。若沒有指定pattern預設會回傳整個陣列的內容。pattern使用和string match一樣的比對方法。注意!! array get回傳的清單會同時包含索引值及元素值。
程式範例:
程式的第2行因為沒有指定pattern,所以取出了整個陣列的內容。由程式輸出的第2行可以發現輸出的順序不一定等於建立的順序。第3行的bo*會符合索引值由bo開頭的所有元素。第4~6行示範使用foreach迴圈列印整個陣列的內容,因為array get的回傳值是一個清單,所以清單中奇數的項目(索引值)都會被儲存在key變數,而偶數值(元素值)則會被儲存在val變數裡。
程式輸出:
boat 小船 apple 蘋果 book 書本 monkey 猴子 boat 小船 book 書本 boat : 小船 apple : 蘋果 book : 書本 monkey : 猴子
雖然array get很貼心的幫我們同時取出索引值及元素值,但有時候在元素值非常大的時候,我們會希望先取出索引就好了,而元素的值可以等到需要候再代換就好。使用下面的命令可以做到這一點:
array names arrayName ?mode? ?pattern?
array names以清單的方式回傳arrayName裡符合pattern的索引值。若不指定pattern預設會回傳所有的索引值。mode可以用來決定pattern比對的方法,如下是mode可以使用的值:
-exact | 使用精確比對。 |
-glob | 使用Unix glob-style的比對方法,這是預設值。 |
-regexp | 使用正規表示式比對。 |
程式範例:
程式輸出:
boat : 小船 apple : 蘋果 book : 書本 monkey : 猴子
□ 其它常用的功能
上面的內容介紹了建立及取出陣列元素的方法,接下來讓我們再看看幾個常用的功能: array exists arrayName
array exists判斷arrayName是否存在,若存在就回傳1,否則回傳0。
array size arrayName
array size回傳arrayName的長度,即陣列裡的元素總數。
array unset arrayName ?pattern?
array unset刪除arrayName中索引值符合pattern的元素。若不指定pattern則所有的元素都會被刪除。pattern使用和string match一樣的比對方法。
程式範例:
程式輸出:
1 0 4 0 0
程式的第6行使用array unset把arr1整個刪除了,所以第7行輸出的結果是0。
10.3 array進階操作
10.2的內容介紹了一些簡單的陣列操作,接下來我們要看幾個跟串繞陣列相關的功能,透過這些功能您可以更有效率的取出陣列的元素。在開始介紹它們之前請先思考一個問題,假設我們有一個陣列arr1,而且這個陣列擁有100000個元素,如果以之前寫過的程式來串繞陣列的話,我們可能會寫出這樣的程式:現在請考思上面的程式出了什麼問題? 想想看依代換的原則 [array get arr1]會被Tcl代換,代換的結果是100000個元素的索引和值,可想而之的Tcl必需使用一塊可觀的塊記憶體空間去儲存代換出來的內容。如果你不想要讓這種問題出現,array命令提供了另外4個專門用來串繞陣列的功能,它們可以避開因代換而使用大量記憶體的問題。
array startsearch arrayName
array startsearch會初始化arrayName的搜尋,然後回傳一個搜尋用的searchId,這個searchId會記錄目前搜尋的狀態,透過它才可以正確的串繞陣列。
array nextelement arrayName searchId
array nextelement會由arrayName中回傳下一個還沒有被找出的索引值。searchId是array startsearch回傳的值。 如果所有的索引值都已經被找出了,此命令會回傳空字串。注意!! 用空字串來判別搜尋是否已經回傳所有的索引是不保險的方法,因為Tcl的陣列允許你使用空字串當元素的索引值。
array anymore arrayName searchId
array anymore會檢查arrayName中是否還有索引值還沒被找出來。如果回傳1表示還沒找完,如果回傳0表示已經找完了。searchId是array startsearch回傳的值。
array donesearch arrayName searchId
array donesearch釋放搜尋過程所佔用的資源。searchId是array startsearch回傳的值。當不再對陣列搜尋時,記得使用這個命令來釋放搜尋過程中佔用的資源。
程式範例:
程式輸出:
boat : 小船 apple : 蘋果 book : 書本 monkey : 猴子
10.4 字典程式範例
陣列的優點是可以讓你易於維護整群的資料,但很多時候我會把它拿來當對照表來使用。接下來我們將寫一個英/漢字典程式來示範對照表的用法,它只可以查5個單字,這些單字分別是:- apple : 蘋果
- book : 書
- boat : 小船
- car : 車子
- monkey : 猴子
程式執行的畫面如圖10-1及圖10-2,使用者在第一個文字方塊輸入英文然後按下搜尋,第二個文字方塊會顯示對應的中文或是顯示找不到。
圖 10-1 英翻中程式
圖 10-2 不認識的單字
程式的第1行建立了一個全域的陣列,它的功能是用來儲存中英文對照表。
第2~13行總共建立了2個文字標籤、2個文字方塊、2個按鈕,等6個視窗元件,然後第14~15行使用grid命令把前面建立的6個視窗元件依格子狀排放在螢幕上。grid和pack都是用來排放視窗元件的命令,關於pack及grid以後會有專門的章節來說明,目前這不是我們的重點,請先不要在意。
接下來請把重點放在第7~11行,它是按下搜尋按鈕後要執行的功能。首先程式第7行中的info exists命令可以用來判斷變數或是陣列元素是否存在,若存在就回傳1,否則回傳0。透過if命令我們可以知道使用者是不是正確的輸入了中英對照片中的單字,如果是的話就執行第8行的程式,否則就執行第10行。
::varEn變數儲存了使用者輸入的單字,即第一個文字方塊的內容,換句話說按下按鈕後$::varEn總是會被代換為目前第一個文字方塊上的文字。而::varCh儲存了第二個文字方塊的內容,第8行及第10行透過設定這個變數來改變第二個文字方塊上顯示的內容。以上就是這個程式的主要邏輯,並不會太困難吧!!
§ 為什麼總是全域變數
學過程式設計的人都會知道一項鐵則「少用全域變數」, 那為什麼我的程式中總是使用全域變數? 事實上這跟Tcl的namespace機制有關係,而關於這一點會在以後的內容會做交待,目前請先不要在意它。
按右上方的「#」號切換側邊欄