---
# System prepended metadata

title: 【Lua 筆記】作用域 / 函數 - part 7
tags: [Lua, 程式語言]

---

【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)