網頁標題: 09 使用者自定義函式 (2): 變數範圍、靜態變數、回呼

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
 



 本文介紹比較進階的使用者自定義函式觀念與操作方式,包含變數的範圍 (scope), 有記憶性質的函式,以及如何在執行期間才決定被呼叫函式的名稱。


 整個腳本中有許多變數,函式的參數也是變數之一,然而當程式碼多了,而且跟人合作寫大專案,我們無法擔保大家在各處用的變數命名完全不重複。當變數命名重複,到底現在讀寫的是哪個地方宣告開始啟用的變數?這就是變數的「範圍」問題。

 包括 AutoIt, 程式語言中的變數範圍主要分成全域 (global) 與區域 (local). 全域指的是該變數自從宣告之後就可在程式碼任何地方讀寫其內容,非常直覺地可以當成函式之間傳遞資料的途徑,但也會增加不同函式之間的牽連關係,讓函式不能獨立運作。區域變數只存在於一個小地方,執行過去該變數及消滅,無法在函式之間共用,也因此各函式的區域變數互不相干。有些語言如 C/C++/Java 的區域可以小到只有一個迴圈本體或分支裡,但 AutoIt 的區域就是一個函式的範圍。

 下面展示寫在函式裡全域跟區域變數的差別:

 介紹變數與資料型態時已經提到,宣告變數可用 Local, Global 或 Dim, 甚至可像上例 (1) 處直接指定。函式以外的地方,無論何種方式出現的變數,都是「全域」變數,也因此介紹變數時不強調這些關鍵字的差異。

 TestLocal 函式內以 Local 宣告同名變數,該變數只存在於 TestLocal 函式裡,不會影響外面的 $var, 因此 TestLocal 執行完後印出結果跟一開始相同。雖然本例沒有參數,但是參數也屬於函式裡的區域變數。

 TestGlobal 結束後 $var 在外面印出 20, 可知 TestGlobal 裡指定的行為影響到外面。Global 宣告就是將該變數放入全域的行列,就算外面沒有先出現 $var, TestGlobal 執行完後仍可印出 $var, 不會造成腳本因為變數未宣告而停止。

 然而,函式中出現未經宣告而直接初始化的變數,它的範圍將依照執行期間的前後文而有所不同。假如執行到此時,這個變數名稱已經存在全域的行列,那對它的操作就會影響到全域變數,但若它此時不是全域變數,就也不會因為函式裡初始化一個未宣告的變數而把它加入全域變數。有個例外是迴圈的計數變數直接寫在 For 之後初始化,都視為區域變數。這種受前後文影響的行為應當盡量避免,以保護自己的全域變數,減少除錯花費的成本!

 Local 及 Dim 表現一樣,所以沒有特別比較,不過建議多用 Local 以明確指出該變數或陣列的範圍。


 有時候我們希望函式把過去執行的東西留下印象,比如函式知道自己被第幾次呼叫。這種要求其實使用全域變數就輕鬆解決,但是用了全域變數就要面對變數命名的管理,除非函式之外有必要存取該「印象」的內容,否則應避免過度依賴全域變數。

 靜態變數可以用來解決上述問題,這種變數被宣告在函式裡,一旦執行第一次之後就永遠佔住記憶體到程式終了。靜態變數不是全域變數,一旦宣告靜態則外界無法存取,也因為它不會隨函式執行結束而消滅,所以下次執行時可保留上次結束前的值。AutoIt 未要求靜態變數必須初始化,但 C 等語言如此規定。

 以下是飯會記住執行幾次的函式:

 結果依序由 1 印到 5, 但迴圈裡並未透過參數傳給 TestStatic 不同資料,可能原因只有靜態變數每次執行都拿到不同的值。第一次進去 TestStatic 時,會執行靜態變數的宣告與初始化,但第二次以後就不會執行。值得注意的是,迴圈裡 $i 是 101 到 105, 順利執行五次表示 TestStatic 裡的靜態變數 $i 改變不會影像到其他同名變數。


 此段將介紹一個內建函式 Call, 透過它能動態決定哪個自定義函式將被呼叫。之前將呼叫函式直接寫在程式碼內,執行到該處就一定呼叫那個函式,如此就不叫「動態」的決定被呼叫的函式,就算用分支也要預先知道有哪些自定義函式以列舉在程式碼中。對 Call 而言,它可完全不預先知道有哪些自定義函式,當告訴它函式名稱才去找,就算找不到也可以用檢查錯誤的方式收尾,不會讓腳本直接停止。

 先用下例說明 Call 的用法,下例改自範例 8-1, 但呼叫函式部分使用 Call.

 Call 第一個參數就是被呼叫的函式名,第二個參數以後列出原本要給該函式的參數。這個範例執行效果與 8-1 完全一樣。其他關於 Call 使用的細節請參考這裡,本文接下來將說明「回呼」的用法。

 假設想要寫一個函式,在陣列內找元素,但每次對「符合」的條件又不大一樣,為了這樣就要每次遇到找元素都寫迴圈又很麻煩。我們可以把符合條件寫成一個函式,透過 Call 動態使用它。示範如下:

 上例中 StringIsUpper 是內建函式,判斷是否字串所有字都大寫。IsEmptyString 傳回一個「比較」結果,把 $str 跟空字串比對後的結果當答案來回傳。

 Find 函式的第二個參數,在呼叫時填入自己寫的其他自定義函式名稱,在 Find 內被交給 Call 來呼叫 $condition 所指定的函式,這種間接透過 Call 動態決定被呼叫函式的做法稱為回呼 (callback).

 回呼的意義是,將函式中部份的實作留給呼叫者決定,在 Find 裡無法知道 If 條件的實際內容,可是 Find 知道只要把 $condition 與目前的陣列元素 $array[$i] 送給 Call, 就會有人來告訴它這個元素是否合乎條件。

 當要使用回呼技巧來設計函式時,呼叫者(呼叫 Call 函式的那方)必須提供完整的指示,包含回呼函式的參數數量、會接受何種資料型態,以及回呼函式應該傳回哪些值協助呼叫者進行任務。上例中 $condition 回呼函式被規定為一個參數,資料型態不限,因為是將呼叫 Find 時用的陣列元素傳入,而陣列是外來資料,跟回呼函式一樣都來自外界。回傳值規定為真假值,因為要交給 If 分支當成判斷依據。

 面對需要提供「回呼」函式的場合,比如某些內建函式也要求提供回呼函式給它,程式設計者就要遵守規定實作符合自己使用目的的回呼函式,並且需要注意自己提供的其它輸入可能進入回呼函式,要是資料型態很多元就還得注意在回呼函式中做好型態管理。


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


本文張貼者:Bo-Cheng Jhan〔張貼時間:民國105年5月16日(星期一)1點15分〕

部落格首頁


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