前面曾經提及最基本的陣列宣告、初始化,迴圈可用 For...To...Next 與 For...In...Next 兩種方式依序存取陣列裡的元素,以及陣列做為函式的參數時建議以 ByRef 方式傳遞。然而當時只有簡介一維陣列,本文將更有系統性的檢視「陣列」和與陣列直接相關的關鍵字、內建函式。 陣列由它的名稱、大小、元素裡存放的值所構成。「大小」又包括「維度」跟「長度」。一個變數只能用這二種方式中的一種變成陣列:一、被宣告成陣列,二、接收函式傳回的陣列,若把原本是陣列的變數 $a 做 $a = 100 之類的敘述被賦予別的值,陣列裡的資料內容將全部消失。
下例示範陣列與取得其尺寸的 UBound:
ViewAnArray 印出陣列的三個組成要素,透過 UBound 取得維度跟各維度的長度。UBound 第一個參數是陣列,第二個參數若為常數 $UBOUND_DIMENSIONS 則傳回陣列維度,也就是宣告時名稱之後有幾對中括號,觀念上無限多個維度都可以。要取得第 $i 維度的長度則 UBound 第二個參數設為 $i, 沒寫則預設成 1. 註:針對二維陣列 UBound 第二個參數也可以寫 $UBOUND_ROWS 和 $UBOUND_COLUMNS, 各代表第一、第二維度,兩者都定義在 AutoItConstants.au3 裡。 _ArrayDisplay 可印出陣列內容,不過只能接受一或二維陣列,三維以上沒有標準函式負責其輸出顯示。當傳了二維陣列給 _ArrayDisplay, 顯示陣列內容的視窗上,清單每個項目變成有 Col 0 跟 Col 1 兩欄。 二維陣列初始化時有兩層中括號,上例的二維陣列是兩列三欄,[1, 2, 3] 是第一列,[4, 5, 6] 是第二列,由 _ArrayDisplay 視窗上資料的位置也看得出來,二個項目代表兩列,各擁有三個欄位。 上例未將三維陣列也初始化,但是類推適用,其初始化有三層中括號。若三維陣列是 Local $a[3][2][2] 則初始化的形式是 [[[1, 2], [3, 4]], [[5, 6][7, 8]], [[9, 10], [11, 12]]] , 好像三個 2*2 的二維陣列初始化又用一個中括號圍住。
初始化的時候,中括號裡元素數量亦可代表長度,所以上例中 $a1 未指定長度也沒關係,而 $a2 要省略哪個長度甚至兩個長度都不寫也可以,但 $a3 的三個長度缺一不可,因為它未被初始化。要是初始化中對長度的定義發生衝突,比如 $a2 的初始化中刪去 6 這個元素,其第二維度仍會是 3, 方能容納所有初始化中的元素,只是第二列第三欄的地方就暫時沒有值。如果又發現宣告中指定的長度跟初始化數量不同,沒被初始化到的位置也暫時成空,但指定長度不可少於初始化所隱含的長度。
之前提過一維陣列取出元素時使用 $a1[$i] , 此處 $i 可以是 0 到 UBound($a1)-1 , 二維陣列則用 $a2[$i][$j] 取值,$i 是 0 到 UBound($a2, 1)-1 , $j 是 0 到 UBound($a2, 2)-1 . 三維以上直接類推適用。
以下示範把二維陣列裡的元素一一印出:
既然二維陣列要用雙重迴圈把所有值取過一遍,三維以上陣列就要用三重以上的迴圈,依此類推。 AutoIt 陣列較不一樣的地方是,它允許直接在腳本中用關鍵字 ReDim 「改變」陣列的大小,包括維度的數量及各維度的長度。下面示範 ReDim 的使用: 第二個 _ArrayDisplay 展示的結果,多出一個 [2] 的列,可是內容未初始化,其他部份保留完好。第三個 _ArrayDisplay 裡三列都少了 Col 2 的欄位,但第四個 _ArrayDisplay 裡雖然陣列大小還原成 3*3 了,Col 2 的地方都變成未初始化。第五個 _ArrayDisplay 展示六個未初始化的元素。 當 ReDim 前後陣列維度相同,資料就會盡量被保留,不過之前因為長度縮小而丟失的資料不會因為長度還原而跟著回來。當 ReDim 成維度不一樣的陣列時,所有資料就會全部丟棄。 以下再示範一個 AutoIt 內非常典型的陣列使用方法。假設輸入的資料要存入陣列,然而我們不知道使用者將輸入多少次,只好每次當陣列被填滿佑要放新資料時 ReDim. $data 陣列在上例中把 [0] 的位置當做計數器,初始化只針對它設為 0. 第一次資料輸入後,迴圈內的分支不被執行,直接將 $data[0] 變成 1 後資料放入 $data[1], 第二次、第三次和第四次資料輸入過程也如此。第五次資料輸入時,$data[0] 是 4, 滿足分支的條件而執行 ReDim, 陣列大小變成 10, 之後的程序又如往常。 當沒再取得新資料,即離開迴圈,可是因為之前每次 ReDim 都多出 5 個空間,其實一開始陣列宣告時也已經有額外空間,很可能輸入結束後有些多餘空間,這時候就要再一次 ReDim 讓陣列大小恰好容納所有輸入。上例中多餘的空間至多只會有四個,即 $iMax - 1 個。
$iMax 也可設為其他正整數,直接改都能執行正確。當 $iMax = 1 時,是最極端的狀況,每次輸入都 ReDim, 完全不浪費空間,然而改變陣列大小的操作牽涉到記憶體配置的調整,尤其陣列變大時,要花時間「找找看」哪有足夠空間容納它,因此每次輸入都 ReDim 的話程式的效能將受到影響。
然而,陣列長度可用 UBound 算,何必多一個計數用的變數在 [0] 的地方?因為 AutoIt 禁止長度 0 的陣列,以上例而言要是使用者都不輸入直接按 Cancel, 就會產生長度為 0 的 $data, 為避免 0 造成的執行失敗,需要更多序數,使程式碼變複雜,寧可多出計數變數簡化它。因此許多內建和使用者自定義函式都以這種格式來傳回陣列。
偶爾會有比較兩個陣列內容是否相同的需要,但是 AutoIt 並無內建的比較陣列方法,一般 = 的比較結果也不對,比較陣列只能從比較它們的大小跟內容下手。以下先示範二維陣列內容相等的比較: 如 ArrayEquals2D 函式所示,陣列比較前最好先確認維度、長度都相同,才將元素逐一比對。函式中先用 IsArray 內建函式檢查參數是否為陣列,是才檢查維度、長度,函式中把維度、長度的不同都當做錯誤的使用情形而設置 @error. 逐一比對元素時,就需要一個額外變數記錄目前是否還維持看過的元素都相等,一旦發現不同就直接離開迴圈。注意此處相等的比對使用 == 作用是字串的絕對相等,對整數等其他型態的效果跟 = 一樣。 不過也無法每種維度的陣列都寫一個比較版本,利用內建函式 Execute 及遞迴的技巧,可以實作任意維度的陣列比較: 此次 ArrayEquals 除了本身外還帶有一個僅供內部使用的 __ArrayEqualsInternal 函式。「僅供內部使用」其實別人也可以呼叫,AutoIt 這方面沒有限制,因此要用註解以及習慣上函式名稱加二個底線表示「內部」用途。 ArrayEquals 的參數跟之前 ArrayEquals2D 相同,設置 @error 的原因也一樣,然而因為現在陣列維度不限二維,所以將 @error 設計成錯誤的類別,把「哪個維度的長度不同」的資訊放到 @extended, 簡化對 @error 的詮釋。 ArrayEquals 在此負責檢查參數型態、大小,以及設置啟動 __ArrayEqualsInternal 遞迴的初始條件。__ArrayEqualsInternal 第一次被呼叫時,代表索引的 $index 字串是空的,代表目前被剖析維度的 $dim 就從第一個維度開始。 __ArrayEqualsInternal 會在 $dim 超出 $arr1, $arr2 的維度時達到邊界條件停止遞迴,在遞迴關係式中交給新的 $dim 參數的是舊的 $dim 加 1, 因此以範例末尾的三維陣列而言,遞迴最多持續到四層,在最深層就只比較兩個陣列裡的元素。 達到邊界條件前,__ArrayEqualsInternal 會對雙方的第 $dim 維度進行遞迴,把現在掃描到的索引連接在 $index 上,繼續往更深處遞迴。好奇的讀者可在 __ArrayEqualsInternal 的邊界條件前多加一行印出 $index, 就能見到其消長,以 $a1 跟 $a2 的比較而言,將得到如下的連續印出結果:
$x + 5 , $arr[9] 都是「表達式」。其參數由 "$arr1" 與 $index 連接而成,以上例而言就會把 "[0][0][0]" 代入 $index 展開為 Execute("$arr1[0][0][0]") == Execute("$arr2[0][0][0]") , 效力等同直接寫 $arr1[0][0][0] == $arr2[0][0][0] , 也會得到一個判斷結果。
當比較結果被傳回,會回到 For 迴圈內的判斷,要是 True 則繼續迴圈來比下一個,False 則剩下的不用比了,結果直接是 False, 可直接將 False 繼續回傳。要是一個維度都跑完沒發生 False, _ArrayEqualsInternal 會傳回 True, 告知上一層遞迴的 __ArrayEqualsInternal 繼續 For 迴圈下去。
如果所有比較皆為 True, $dim = 1 的 __ArrayEqualsInternal 執行完迴圈後就會回報最終答案為 True, 否則會把中途得到的 False 一直傳回到結束所有正在遞迴的 __ArrayEqualsInternal.
註:關於陣列比較也可參考這篇 Array Comparison 的討論。
之前提過,陣列每個元素指定任何型態的值都可以,那可以指定另一個陣列給它嗎?考慮如下的寫法: _ArrayDisplay 展示的結果,$data[0] 變成一個字串 "{Array}", 卻沒有把 $sub 內的細節交代清楚。想要透過 $data[0] 存取 $sub 裡的值,必須寫 ($data[0])[0] 與 ($data[0])[1] , 注意須以小括號將 $data[0] 圍住。
但是,根據 Arrays - AutoIt Wiki, 內嵌陣列的寫法請盡量避免,因為在函式的參數傳遞時 Const 無法檢查出不小心對內嵌陣列的改變而出現錯誤訊息提醒,容易造成除錯上的困難,使用這種寫法前請三思。
回 · 我的 AutoIt 學習筆記 這一篇文章封面 |
本站公告:〔您越需要我們,我們就越有創意〕 | 本站說明書:〔發現故鄉還有改進的地方,請來信告訴原丁們〕 |
觀察應用學習點數 :〔咱的故鄉有您的參與,會使我們有更大的發揮空間,展現更豐富精彩的學習畫面〕 | 〔期待藉由無障礙網頁設計,能讓視障小朋友更愛看書、更愛寫作且更愛學習〕:盲用電腦「心得分享」 |