【Lua 筆記】作用域 / 函數 - part 7 === 目錄(Table of Contents): [TOC] --- 由於有款遊戲叫做 CSO(Counter-Strike Online),內建模式創世者模式(Studio)新增使用 Lua 及其遊戲的 API,所以突發奇想製作這個筆記。 這個筆記會在一開始先著重純粹的程式設計自學,在最後的章節才會與 CSO 遊戲 API 進行應用。 作用域(scope) --- 作用域(scope),表示一個變數或函數等能夠作用的範圍,什麼意思呢?請看以下例子: ```lua= a = 10 -- 這是全域變數 local a = 50 -- 這是局域變數 ``` lua 當中的變數,沒有在前面加上 local 以外,全部都是「全域變數」,你可以把全域變數想像成是一個到哪裡都可以用的變數。 局域變數就只限縮於"目前"的範圍,什麼意思呢?也就是在變數前面宣告了 local 這個關鍵字,就只有這整個文件可以用這個變數而已,其他的文件不能存取到它的值。(這只是粗淺的解釋,實際上看的是他目前所在的作用域範圍) 具體是何種"目前"的範圍,我們在說明函數的時候稍待說明一下。 函數(function) --- 函數(function),又被稱為副程式、函式,我們在之前有稍微說明過它的概念,忘記的請至 part 1 的資料型態重看一遍。 總之函數它就是一台有功能的機器,輸入東西之後,在裡面運轉的機器(功能),運轉完後會輸出東西給我們。 以下是 lua 對函數的定義: ```lua= optional_function_scope function function_name(argument1, argument2, argument3..., argumentn) function_body return result_params_comma_separated end ``` - optional_function_scope : 可以指定這個函數是全域還是局域,全域的話就不用寫,局域請打上 local 關鍵字。 - function_name : 指定函數名稱。 - argument1, argument2, argument3..., argumentn : 表示一個函數的參數,多個參數以逗號隔開,函數也可以不帶參數。 - function_body : 函數體,函數中需要執行的程式碼區塊。 - result_params_comma_separated : 函數當中的回傳值,Lua 的函數可以回傳多個值,每個值以逗號隔開。 :::danger 註:return 一旦觸發後,函數裡面 return 後續的程式碼就不會繼續執行,所以 return 可以表示回傳後結束。 ::: ### 函數當中的變數 --- 前面我們談到作用域的部分,用函數解釋是最為明顯的,請看以下例子: ```lua= a = 10 -- 設定全域變數 function func() local a = 15 -- 設定局域變數 return a end print(func()) -- 15 print(a) -- 10 ``` 在函數外面的部分,a 被設定是全域變數,a = 10。 函數裡面又設定一個局域變數,也叫做 a = 15。 我們要使用函數呢,就直接寫它的函數名稱再加上小括號就好囉,然後這個我們稱之為呼叫(call)函數。 執行結果,發現 a 的值竟然沒有被改變,理應來說兩個都要是 15 才對啊! 為什麼?這就是作用域規則啦~因為函數裡面本身也是一種局域的作用域,函數以外的就拿不到函數內的東西囉。 或是可以這樣想:函數外的全域變數,與函數內的局域變數兩者是截然不同的變數。 換個方式,如果我們這樣寫: ```lua= a = 10 function func() a = 15 return a end print(func()) -- 15 print(a) -- 15 ``` 可以發現,輸出結果就變兩個都是 15 了。把 local 拿掉,此時的 a 就不再是兩個截然不同的變數了,就好像把散掉的分身結合再一起一樣。 此時函數內調用的就是 a 這個「全域變數」。 ### 多重回傳值 --- 範例來源:[Lua 函数 | 菜鸟教程](https://www.runoob.com/lua/lua-functions.html) ```lua= function maximum (a) local mi = 1 -- 最大值索引 local m = a[mi] -- 最大值 for i,val in ipairs(a) do if val > m then mi = i m = val end end return m, mi end print(maximum({8,10,23,12,5})) ``` 執行結果: ``` 23 3 ``` 以上的程式碼可以從一串數字當中,從陣列索引 1 開始,尋找出最大值並且回傳,還能夠回傳該最大值在陣列當中的索引值。 ### 不定長參數(可變參數) --- Lua 中使用三點 `...` 表示函數有可變的參數,意思就是可以輸入很多個參數進去。 以下是一個範例([Lua 函数 | 菜鸟教程](https://www.runoob.com/lua/lua-functions.html)): ```lua= function add(...) local s = 0 for i, v in ipairs {...} do --> {...} 表示由一個所有不定長參數組成的陣列 s = s + v end return s end print(add(3,4,5,6,7)) ---> 25 ``` 上面的意思概括來說,就是呼叫 add 函數時,能夠輸入很多不限長度的參數進去,像是上面輸入 3,4,5,6,7,進入函數裡面。 這支函數主要計算由不定長參數組成的陣列中,所有元素的總和。 先用一個 temp 變數暫存(這裡稱為 s,sum 為總和的意思),之後在透過運算 s = s + v 不斷計算求得總和。 以下的範例([Lua 函数 | 菜鸟教程](https://www.runoob.com/lua/lua-functions.html))當中,可以透過 select("#",...) 來取得不定長參數的數量: ```lua= function average(...) result = 0 local arg={...} for i,v in ipairs(arg) do result = result + v end print("總共傳入 " .. select("#",...) .. " 個數") return result/select("#",...) end print("平均值為",average(10,5,3,4,5,6)) ``` 輸出結果: ``` 總共傳入 6 個數 平均值為 5.5 ``` 有時候我們需要一些固定的參數,再來加上不定長參數,以下是一個應用範例: ```lua= function func(fixedParam, ...) -- fixedParam 為固定參數, ... 是不定長參數 print("固定參數 : " .. fixedParam) local args = { ... } for i, v in ipairs(args) do print("不定長參數 " .. i .. " : " .. v) end end func("固定值", "參數1", "參數2", "參數3") ``` 輸出結果: ``` 固定參數 : 固定值 不定長參數 1 : 參數1 不定長參數 2 : 參數2 不定長參數 3 : 參數3 ``` 然後再來介紹 select 的另一個用法: :::info `select(n, …)` 用於回傳從起點 n 開始到結束位置的所有參數列表。 ::: > 呼叫 select 時,必須傳入一個固定實參 selector (選擇開關) 和一系列變長參數。如果 selector 為數字 n,那麼 select 傳回參數列表中從索引 n 開始到結束位置的所有參數列表,否則只能為字串 #,這樣 select 會傳回變長參數的總數。 上面這段話擷取自:[Lua 函数 | 菜鸟教程](https://www.runoob.com/lua/lua-functions.html) 解釋一下: * 固定實參(Fixed Argument):固定實參是指在函數呼叫時,必須傳遞的參數。這些參數的數量和順序是固定的,函數定義時就已經確定。 * 變長參數(Variable-Length Argument):也就是不定長參數。變長參數是指在函數呼叫時,可以傳遞任意數量的參數。這些參數的數量和順序在函數定義時是不確定的。Lua 使用三個點 (...) 來表示變長參數。 看起來很複雜,很不容易懂對吧?以下這樣解釋或許會比較清楚: :::warning select 函數: 它的第一個參數是固定實參 selector,後面的參數是變長參數。select( 這裡是第一個參數 , ...) 如果 selector 是一個數字 n,則 select 會回傳從第 n 個變長參數開始到最後一個變長參數的列表。 如果 selector 是字串 #("#"),那麼 select 會返回變長參數的總數。 ::: 還是不清楚的話,讓我們看個具體的例子: ```lua= function func(...) print(select(1, ...)) -- 打印所有變長參數 print(select(2, ...)) -- 打印從第二個變長參數開始的所有參數 print(select("#", ...)) -- 打印變長參數的總數 end func("a", "b", "c", "d") ``` 輸出結果: ``` a b c d b c d 4 ``` 練習一下:請用 lua 寫出一支程式碼,運用本篇的概念(不定長參數、select 函數運用),寫出如下程式碼一樣的輸出結果。 ```lua= a = {1,2,3,4} for i, v in ipairs(a) do print("arg", v) end ``` 輸出結果: ``` arg 1 arg 2 arg 3 arg 4 ``` :::spoiler 點左邊三角形, 看解答跟詳解 ::: info 程式碼源自:[**Lua 函数 | 菜鸟教程**](https://www.runoob.com/lua/lua-functions.html) ```lua= do function foo(...) for i = 1, select('#', ...) do local arg = select(i, ...) print("arg", arg) end end foo(1, 2, 3, 4) end ``` 解釋: `do ... end` 表示創建一個局部作用域,避免影響到外部程式碼的運作。foo 函數和任何在區域內定義的局部變數在 `do ... end` 區塊結束後都不會影響到外部的程式碼。 第三行:用 for 迴圈來遍歷所有傳遞給 foo 函數的參數。select('#', ...) 回傳傳遞給函數的參數個數。 第四行:定義局域變數 arg = select(i, ...),用 select 函數來獲取第 i 個參數並將其指定給局部變量 arg。select(i, ...) 回傳第 i 個參數的值。 註:直接 print 出 arg 跟用 print(select(i, ...)) 是不一樣的,前者只會印出 select() 的第一個參數,後者從第 i 個參數到最後一個參數全印。 ::: 總結 --- 作用域(scope),表示一個變數或函數等能夠作用的範圍。 分為全域作用域、局域作用域,當然也分成全域變數、局域變數,抑或是全域函數、局域函數等。 使用 `do ... end` 可建立一個局域作用域。像這樣: ```lua= a = 10 do local a = 15 end print(a) -- 10 ``` 以下是 lua 對函數的定義: ```lua= optional_function_scope function function_name(argument1, argument2, argument3..., argumentn) function_body return result_params_comma_separated end ``` - optional_function_scope : 可以指定這個函數是全域還是局域,全域的話就不用寫,局域請打上 local 關鍵字。 - function_name : 指定函數名稱。 - argument1, argument2, argument3..., argumentn : 表示一個函數的參數,多個參數以逗號隔開,函數也可以不帶參數。 - function_body : 函數體,函數中需要執行的程式碼區塊。 - result_params_comma_separated : 函數當中的回傳值,Lua 的函數可以回傳多個值,每個值以逗號隔開。 :::danger 註:return 一旦觸發後,函數裡面 return 後續的程式碼就不會繼續執行,所以 return 可以表示回傳後結束。 ::: Lua 中使用三點 `...` 表示函數有可變的參數,意思就是可以輸入很多個參數進去。(不定長參數或稱變長參數) `select (index, ···)` :::warning select 函數: 它的第一個參數是固定實參 selector,後面的參數是變長參數。select( 這裡是第一個參數 , ...) 如果 selector 是一個數字 n,則 select 會回傳從第 n 個變長參數開始到最後一個變長參數的列表。 如果 selector 是字串 #("#"),那麼 select 會返回變長參數的總數。 ::: 參考資料 --- [【30天Lua重拾筆記09】基礎1: 類型 - 函數 | 又LAG隨性筆記](https://www.lagagain.com/post/30%E5%A4%A9lua%E9%87%8D%E6%8B%BE%E7%AD%86%E8%A8%9809%E5%9F%BA%E7%A4%8E1-%E9%A1%9E%E5%9E%8B-%E5%87%BD%E6%95%B8/) [Lua 变量 | 菜鸟教程](https://www.runoob.com/lua/lua-variables.html) [Lua 函数 | 菜鸟教程](https://www.runoob.com/lua/lua-functions.html)