網頁標題: 14 陣列 (1): 基礎篇

Warning: fopen(/home/crazy/www/cmsb/bcj/has_read.php): failed to open stream: Permission denied in /home/crazy/www/compose/reading.php on line 2077

Warning: fputs() expects parameter 1 to be resource, bool given in /home/crazy/www/compose/reading.php on line 2079

Warning: fclose() expects parameter 1 to be resource, bool given in /home/crazy/www/compose/reading.php on line 2080
 
﹗﹗﹗觀看留言:此文章已經有3則留言 ﹗﹗﹗


 前面曾經提及最基本的陣列宣告、初始化,迴圈可用 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 的比較而言,將得到如下的連續印出結果:

  1. "" (空字串)
  2. "[0]"
  3. "[0][0]"
  4. "[0][0][0]"
  5. "[0][0][1]"
  6. "[0][1]"
  7. "[0][1][0]"
  8. "[0][1][1]"
  9. "[1]"
  10. "[1][0]"
  11. "[1][0][0]"
  12. "[1][0][1]"
  13. "[1][1]"
  14. "[1][1][0]"
  15. "[1][1][1]"

 當 $index 內有三組中括號時,$dim 也是 3, 呼叫 UBound 就會因為超出維度而引起設置 @error, 根據文件描述 UBound 此時將傳回 0, 造成迴圈的 $i 範圍 0 到 -1, 然而之前提過預設的 Step 是 1, 因此迴圈將毫無動作直接被越過,且 UBound 設置的 @error 因為中間沒有呼叫別的函式而被保留,啟動邊界條件。

 邊界條件由 SetError 設置 @error 及 @extended 為 0, 以及它的第三個參數是一個 == 的比較,回傳比較所得 True/False 結果。因為剛才透過 UBound 設置了 @error, 所以這裡明確使用 SetError 把它的值變回 0.

 比較元素時用了 Execute 內建函式,其功用是計算表達式 (expression) 的值,如 $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 學習筆記 這一篇文章封面


本文張貼者:Bo-Cheng Jhan〔張貼時間:民國105年6月7日(星期二)1點33分 | 更新次數 #1 | 最後更新:民國105年7月10日(星期天)19點21分〕

部落格首頁


學習的故鄉首頁
本站公告:〔您越需要我們,我們就越有創意〕 本站說明書:〔發現故鄉還有改進的地方,請來信告訴原丁們〕
觀察應用學習點數 :〔咱的故鄉有您的參與,會使我們有更大的發揮空間,展現更豐富精彩的學習畫面〕 〔期待藉由無障礙網頁設計,能讓視障小朋友更愛看書、更愛寫作且更愛學習〕:盲用電腦「心得分享」
〔為了讓我們有乾淨的學習環境,請勿任意在本站散播商業廣告與不合法文件或聯結〕:本站宣示