網頁標題: 08 使用者自定義函式 (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 2070

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

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



 本篇介紹 AutoIt 的使用者自定義函式 (User-Defined Function, UDF). 使用者(用 AutoIt 寫程式的人)可以將一段程式碼用 Func...EndFunc 包起來,做成一個「函式」。前面幾篇提到許多 AutoIt 的內建函式,不過現在要自己動手寫一個函式出來。

 一個函式的基本結構含有名稱、參數、函式本體及回傳值。下例將範例 7-2 裡 1 累加到 $n 寫成一個函式:

 Func 關鍵字後就是函式名稱,命名無分大小寫,名稱之後已半形小括號夾住所有參數,若參數超過一個則參數間逗點區隔。下一行起是函式本體的程式碼,範圍到 EndFunc 為止。AutoIt 會越過函式,從倒數第二行開始執行,遇見函式呼叫才找找看它的定義在何處然後跳過去執行,要是沒找到定義,腳本就會結束。值得注意的是,函式的定義也不一定要在被呼叫之前,程式碼可以為了閱讀方便而先寫主要執行段落,後面才列出函式的實作。

 上例倒數第二行呼叫 Sum1ToN, 參數指定為 100, AutoIt 找到 Sum1ToN 以後進入,此時 $n = 100. Sum1ToN 程式本體可以如使用變數般操作 $n, 執行完迴圈,Return $sum 會把算完的答案傳回來,AutoIt 離開函式回到倒數第二行,Sum1ToN(100) 此時變成算完的 5050, 然後該處執行 $ans = 5050.

 每個函式不一定有 Return 的敘述,完全仰賴設計者寫函式時的安排。沒有 Return 表示設計者不預期這函式的回傳值參與運算,如指定給變數,若硬要接住未定義的回傳值,將得到整數 0, 換言之 AutoIt 把所有函式預設傳回值是 0.

 上例跟範例 7-2 執行結果完全一樣,但是範例 7-2 因為沒有寫成函式,也不會跳來跳去執行,比範例 8-1 跑得快,那為何需要定義成函式?「函式」固然有些執行時間的代價,但是它讓程式碼可「重複利用」,當程式裡不但需要 1+...+100 的答案,也要 1+...+200 的答案,寫個 Sum1ToN 就只要呼叫 Sum1ToN(100)Sum1ToN(200) 便可得到兩個答案,不用把 For...Next 迴圈抄寫兩次。這樣有利於程式碼的管理,除了減少「抄寫」外,又可以把程式碼分工精細,不同功能的函式之間不共用變數,未來修改就輕鬆又能避免錯誤。

 以下開始示範一些更有彈性的參數寫法跟設計參數可能遇到的相關問題,好的參數設計與實作方式大大影響程式的效能、程式碼除錯跟擴充的成本。


 「參數」對函式而言,是來自外界給定的資料,然而不一定總要給足所有參數才能執行函式。如之前的 InputBox, 除了視窗標題跟輸入的提示外,其他像預設內容、視窗位置與大小等參數並不是必要,就可以用「默認參數」的技巧,把非必要參數寫上預設值,只要呼叫時確定有提供所有必要資料就能執行函式。默認參數示範如下:

 這次賦予 Sum1ToN 默認參數 $p, 可以接受呼叫時指定次方,如此它能跟範例 8-1 一樣的呼叫方法,也能隨著參數給的內容改變行為,可說擴充了 Sum1ToN 的功能。默認參數可以許多個,不過 AutoIt 規定所有默認參數必須集中在參數列的後端,不可以默認跟非默認的參數交錯。

 注意之前範例中當想要指定 InputBox 參數列排較後面的時間限制,又不能不寫 inputBox 大小跟位置,就都用 Default 填入。AutoIt 的 Default 也是「值」的一種,並不是用來告訴 AutoIt 這個參數被越過,因此上例如果使用 Sum1ToN(100, Default) 將導致未定義的行為,解決辦法如下:

 當然,許多狀況包括 Sum1ToN 在內,真的要用默認功能,呼叫時我們寧可省略參數 $p 而不會故意寫 Default, 因此設計者很有可能為了效能,選擇範例 8-2 的實作而不要檢驗 $p 是否為 Default, 並在函式的說明文件中標明請省略 $p 或明確指定 1 以執行預設功能。


 AutoIt 的變數並不綁定資料型態,參數當然也是,Sum1ToN 的參數可能被故意指定成字串或者 Null 等不預期的狀況,就算是整數也可能接到非正整數的 $n. 函式是自己實作的,自己很清楚如何使用,幹麻葯花時間處理這些莫名奇妙的例外?當效能不是絕對重要的一般狀況下,這些「防呆」設計能減少除錯的成本,再者如果跟別人合作開發系統,呼叫函式的非你本人,防呆就格外重要。因此,函式本體的實作中可以濾除不預期的參數,示範如下:

 不過,上例錯誤發生時的回傳值選用 0 和 -1 可行的原因是,這二個值不會出現在函式輸入合理時的回傳值中,呼叫後可用這種「不合理」的回傳值偵測剛才發生的錯誤。然而,很多狀況下函式合理輸出的範圍涵蓋整個「整數」或「浮點數」,我們不得不選擇其他型態的值回傳。但要是這樣做,呼叫後還必須檢查接到回傳的內容是否為預期的型態,比較繁複且費時,AutoIt 設計之前曾經出現的 @error 巨集來傳遞發生錯誤當下的資訊。

 不過本文不介紹 @error 巨集,留給專門介紹巨集的文章。


 在函式中傳參數的時候,你可曾想過,對參數的修改會不會延續到函式執行結束?用另一個主題說明,「陣列」的初始化很麻煩,如果宣告時有 100 個元素,但都要初始化為 0, 我們不會

想在中括號裡連續寫 100 個 0 吧!要是有一個函式能替我們初始化陣列該有多好,如下:

 _ArrayDisplay 輸出的結果,根本沒看到 0, 這表示 Zero1DArray 迴圈裡寫入的動作沒有影響到函式外的 $data. 參數傳遞時 AutoIt 預設會將資料整個複製一份,導致 $data 和參數的 $array 佔用不同兩個記憶體空間,修改 $array 所在的空間就不影響 $data.

 想要 100 個元素都 0 的陣列有兩種方式,一個是把陣列在函式內宣告並初始化好再傳回來,二是想辦法讓 $array 跟 $data 參考到同樣的記憶體。第一種方法非常直覺簡單,只要在範例 8-5 取消函式的參數,迴圈之前宣告陣列 Local $array[100] 以及迴圈之後加入 Return $array 即可用 Local $data = Zero1DArray() 這樣來辦到初始化。但是此法每次要「初始化」陣列其實都變成「創造」新的陣列,不但沒效率又容易在 32 位元的電腦引起記憶體不足的問題。

 因此尋求第二種方法,函式宣告參數 $array 時前面加一個 ByRef, 即可讓 $array 跟傳入的 $data 參考到相同記憶體,展示如下:

 這次 _ArrayDisplay 的 Col 0 欄位都寫 0 了。這個 ByRef 的技巧比較多在陣列上使用,不過當函式計算結果不只一個,也能用在一般基本型態的變數上,讓變數直接收下計算結果而不透過 Return. ByRef 的參數也可以被指定為一般的數值或字串,而不是變數,不過如此將失去 ByRef 的意義,因此一般不會這樣做。

 另外,將陣列當成參數傳遞,即使函式裡沒有修改它的需求,也建議使用 ByRef. 因為預設的傳遞方式,稱為「傳值」,會將變數完全複製,若佔用大量空間的陣列要「傳值」,將耗費許多時間在複製其內容,所以使用 ByRef 這種「傳參考」方式可避免無謂的資料複製。為讓沒有改值需求的 ByRef 被區別出來,參數前面可寫成 ByRef Const, 兩個字的順序可以對調,此種寫法展示如下:


回 · 我的 AutoIt 學習筆記 這一篇文章封面


本文張貼者:Bo-Cheng Jhan〔張貼時間:民國105年5月15日(星期天)0點41分 | 更新次數 #1 | 最後更新:民國105年5月16日(星期一)0點58分〕

部落格首頁


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