Lua 程式設計/單頁版本
Lua(不是“LUA”,儘管很常見,但這是不正確的)是一種功能強大、快速、輕量級且可嵌入的程式語言。它被許多框架、遊戲和其他應用程式使用。雖然它可以獨立使用,但它被設計成易於嵌入到另一個應用程式中。它用 ANSI C 實現,ANSI C 是 C 程式語言的一個子集,非常便攜,這意味著它可以在許多系統和許多裝置上執行,而大多數其他指令碼語言無法執行。本書的目的是教授 Lua 程式設計,無論其之前的程式設計經驗如何。本書可以作為程式設計的入門,適用於從未程式設計過的人,也可以作為 Lua 的入門,適用於以前程式設計過但沒有使用 Lua 的人。由於有許多開發平臺和遊戲使用 Lua,因此本書還可以用於學習 Lua,然後將其用於該開發平臺。
本書旨在教授 Lua 最新版本的用法。這意味著將嘗試定期更新它,因為 Lua 的新版本釋出(Lua 的釋出頻率足夠低,所以這應該不會太難)。目前,本書已經更新到 Lua 5.2。如果您在嵌入式環境中使用 Lua,該環境使用 5.x 分支(Lua 5.0 和 Lua 5.1)中的舊版本 Lua,那麼這些材料對於您來說仍然足夠相關。
Lua 由位於巴西的里約熱內盧天主教大學設計和維護。它的建立者是 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。
“Lua”(發音為 LOO-ah)在葡萄牙語中是“月亮”的意思。因此,它既不是縮寫也不是縮寫,而是一個名詞。更具體地說,“Lua”是一個名字,地球的月亮的名字以及語言的名字。像大多數名字一樣,它應該用小寫字母寫,開頭大寫,即“Lua”。請不要寫成“LUA”,這既難看又令人困惑,因為那樣的話,它就會成為不同人有不同含義的縮寫詞。所以,請寫“Lua”吧!——Lua 作者, 關於 Lua
Lua 來自 TeCGraf(里約熱內盧天主教大學的一個實驗室)設計的兩種語言:DEL 和 Sol。DEL 代表“資料輸入語言”,而 Sol 代表“簡單物件語言”,在葡萄牙語中也代表太陽,因此選擇了 Lua 這個名字,因為它在葡萄牙語中是“月亮”的意思。它是為巴西石油公司 Petrobras 建立的,但也用於 TeCGraf 中的許多其他專案,現在已被全球範圍內的眾多專案使用。Lua 是嵌入式遊戲開發領域領先的語言之一。
Lua 的主要優勢之一是它的簡單性。一些公司之所以只使用它,就是因為這個優勢:他們認為如果員工可以使用程式語言來執行某些任務,他們會做得更好,但他們無法負擔為員工提供關於複雜程式語言的完整課程。像 Bash 或 Batch 這樣的簡單語言不足以執行這些任務,而 Lua 既強大又簡單。Lua 的另一個重要優勢是它的可嵌入性,這是它在整個開發過程中最重要的特徵之一。像魔獸世界或 ROBLOX 這樣的遊戲需要能夠在其應用程式中嵌入 Lua,以便應用程式的使用者可以使用它。
程式設計,在嵌入式應用程式中執行的程式中也被稱為指令碼,是編寫計算機程式的過程。程式語言是一種語言,用於透過計算機程式中的計算機程式碼向計算機發出指令。程式語言包含兩部分:語法,就像英語中的語法一樣,以及庫,語言提供的基本函式。這些庫可以與英語中的詞彙相比較。
Lua 可以嵌入到應用程式中,也可以獨立使用。本書不會介紹在您的計算機上安裝 Lua 的過程,但您可以使用 codepad 或 Lua 演示 執行程式碼。本書中第一個 Lua 程式碼示例將是基本且傳統的 hello world 程式。
“Hello world”程式是一個計算機程式,它在顯示裝置上輸出“Hello, world”。因為它通常是大多數程式語言中最簡單的程式之一,所以按照傳統,它通常被用來向初學者說明程式語言最基本的語法,或者驗證語言或系統是否正常執行。——維基百科, Hello world program
print("Hello, world!")
上面的程式碼將文字 Hello, world! 列印到輸出,列印是指在輸出中顯示文字,而不是在紙上列印。它透過使用字串“Hello, world!”作為引數呼叫print函式來實現。這將在關於函式的章節中解釋。
請注意,Lua 通常嵌入在更低階的應用程式中,這意味著print函式並不總是將文字顯示在使用者可見的區域。這些應用程式的程式設計介面文件通常會解釋如何將文字顯示給使用者。
註釋是程式語言忽略的程式碼註解。註釋可以用來描述一行或多行程式碼,記錄程式,臨時停用程式碼,或任何其他目的。它們需要以兩個連字元為字首才能被 Lua 識別,可以單獨佔一行,也可以放在其他行末尾
print("This is normal code.")
-- This is a comment
print("This is still normal code.") -- This is a comment at the end of a line of code.
這些註釋被稱為簡短註釋。還可以建立長註釋,它們以一個長括號開始,可以延續多行
print("This is normal code")
--[[Line 1
Line 2
]]
長括號由兩個括號組成,中間可以放置任意數量的等號。這個數量被稱為長括號的級別。長括號將一直延續到下一個相同級別的括號,如果有的話。沒有等號的長括號被稱為級別為 0 的長括號。這種方法使得可以在長註釋內部使用結束雙括號成為可能,方法是在兩個括號中間新增等號。當使用註釋停用程式碼塊時,這樣做通常很有用。
--[==[ This is a comment that contains a closing long bracket of level 0 which is here: ]] However, the closing double bracket doesn't make the comment end, because the comment was opened with an opening long bracket of level 2, and only a closing long bracket of level 2 can close it. ]==]
在上面的示例中,級別為 0 的結束長括號 (]]) 不會關閉註釋,但級別為 2 的結束長括號 (]==]) 會關閉註釋。
程式語言的語法定義了在該程式語言中如何編寫語句和表示式,就像語法定義瞭如何編寫句子和單詞一樣。語句和表示式可以分別與句子和單詞進行比較。表示式是具有值的程式碼片段,可以被求值,而語句是可執行的程式碼片段,包含指令和一個或多個用於該指令的表示式。例如,3 + 5 是一個表示式,而 variable = 3 + 5 是一個語句,它將 variable 的值設定為該表示式。
Lua 的完整語法可以在 Lua 網站上找到擴充套件的巴克斯-諾爾正規化 上,但是如果你讀它,你將無法理解任何東西。 擴充套件的巴克斯-諾爾正規化 是一種元語言,一種用於描述另一種語言的語言,就像元網站是關於網站的網站,就像元表在 Lua 中是定義其他錶行為的表一樣(你將在本書的後面學習元表和表)。但是你不需要學習本書中的擴充套件的巴克斯-諾爾正規化,因為,雖然像 Lua 這樣的語言可以使用元語言來描述,但它也可以使用英文中的單詞和句子來描述,這正是本書要做的。
既然英文可以用來描述另一種語言,那麼它本身一定是一種元語言(因為它符合元語言的定義)。事實確實如此。既然程式語言的目的是描述指令,而你也可以用英文來做,那麼英文也一定是一種程式語言。這,在某種程度上,也是事實。實際上,英語是一種可以用於多種用途的語言。但是擴充套件的巴克斯-諾爾正規化是一種專門的語言,程式語言也是專門的語言。專業化是指擅長做某一特定事情,但不能做其他事情的特點。擴充套件的巴克斯-諾爾正規化非常擅長描述其他語言,但它不能用來編寫指令或傳達資訊。程式語言非常擅長提供指令,但它們不能用來描述語言或傳達資訊。
英語能夠做任何事情:描述語言、提供指令和傳達資訊。但它並不擅長做其中的一些事情。事實上,它在提供指令方面非常糟糕,以至於如果它被用來向計算機提供指令,計算機將無法理解任何內容。這是因為計算機需要指令非常精確和明確。
Lua 可以從官方 Lua 網站上獲得,下載頁面。那裡也有說明:下載按鈕用於獲取原始碼,這可能不是你想要的。你可能在尋找二進位制檔案,因此你應該瀏覽頁面以查詢關於二進位制檔案的資訊(你究竟要找什麼取決於你正在使用的平臺)。本書的目的是教授 Lua 語言,而不是教授 Lua 工具的使用。通常假設讀者將在嵌入式環境中使用 Lua,但這對於本書有用並不需要如此,只是意味著本書不會描述 Lua 作為獨立語言的使用。
如前所述,表示式是具有值的程式碼片段,可以被求值。它們不能直接執行(函式呼叫除外),因此,僅包含以下程式碼的指令碼(它包含一個表示式)將是錯誤的
3 + 5
-- The code above is erroneous because all it contains is an expression.
-- The computer cannot execute '3 + 5', since that does not make sense.
程式碼必須由一系列語句組成。這些語句可以包含表示式,表示式將是語句必須操作或使用來執行指令的值。
本章中的一些程式碼示例並不構成有效的程式碼,因為它們僅包含表示式。在下一章中,將介紹語句,並將能夠開始編寫有效的程式碼。
求值一個表示式就是計算它以找到它的值。給定表示式求值的結果可能在不同的上下文中有所不同,因為它可能取決於環境和堆疊級別。這個值有時是一個數字,有時是文字,其他時候是許多其他資料型別中的任何一個,這就是為什麼說它具有型別。
在 Lua 中,以及在一般的程式設計中,表示式通常由一個或多個值和零個或多個運算子組成。某些運算子只能與某些型別一起使用(例如,嘗試除以文字是不合邏輯的,而除以數字是有意義的)。運算子有兩種:一元運算子和二元運算子。一元運算子是隻接受一個值的運算子。例如,一元 - 運算子只接受一個數字作為引數:-5、-3、-6 等。它接受一個數字作為引數,並對該數字取反。但是,二元 - 運算子(它不是同一個運算子)接受兩個值,並從第一個值中減去第二個值:5 - 3、8 - 6、4 - 9 等。
可以使用type函式獲取數字的型別作為字串
print(type(32425)) --> number
數字通常表示數量,但它們可以用於許多其他用途。Lua 中的數字型別的工作方式與實數基本相同。數字可以構建為整數、小數、小數指數,甚至可以構建為 十六進位制。以下是一些有效的數字
- 3
- 3.0
- 3.1416
- 314.16e-2
- 0.31416E1
- 0xff
- 0x56
Lua 中的數字運算子如下所示
| 運算 | 語法 | 描述 | 示例 | 結果 |
|---|---|---|---|---|
| 算術取反 | -a | 改變 a 的符號,並返回該值 | -3.14159 | |
| 加法 | a + b | 返回 a 和 b 的和 | 5.2 + 3.6 | 8.8 |
| 減法 | a - b | 從 a 中減去 b 並返回結果 | 6.7 - 1.2 | 5.5 |
| 乘法 | a * b | 返回 a 和 b 的乘積 | 3.2 * 1.5 | 4.8 |
| 求冪 | a ^ b | 返回 a 的 b 次方,或 a 的 b 次冪 | 5 ^ 2 | 25 |
| 除法 | a / b | 用 b 除 a 並返回結果 | 6.4 / 2 | 3.2 |
| 地板除 | a // b | 用 b 除 a 並返回結果的整數部分 | 6.4 // 2 | 3 |
| 模運算 | a % b | 返回 a 除以 b 的餘數 | 5 % 3 | 2 |
你可能已經知道所有這些運算子(它們與基本的數學運算子相同),除了最後一個。最後一個稱為模運算子,它只計算一個數字除以另一個數字的餘數。例如,5 % 3 的結果是 2,因為 2 是 5 除以 3 的餘數。模運算子不像其他運算子那麼常見,但它有很多用途。
整數
[edit | edit source]在 Lua 5.3 中添加了一種新的數字子型別,即整數。數字可以是整數或浮點數。浮點數類似於上面描述的數字,而整數是沒有任何小數部分的數字。浮點數除法 (/) 和求冪總是將其運算元轉換為浮點數,而所有其他運算子如果它們的兩個運算元是整數,則會給出整數。在其他情況下,除了地板除運算子 (//) 之外,結果是浮點數。
空值
[edit | edit source]空值是空值型別,它的主要特性是與任何其他值不同;它通常代表沒有有用的值。一些空值示例
- 在為變數賦值之前訪問變數的值
- 嘗試在變數作用域之外訪問變數時獲得的值
- 表中任何未分配鍵的值
- 如果
tonumber無法將字串轉換為數字,則返回的值
在更高階的說明中,故意分配一個空值會刪除對變數或表的引用,並允許 垃圾回收器 重新回收其記憶體。
布林值
[edit | edit source]布林值可以是真或假,但不能是其他任何值。這在 Lua 中寫為 true 和 false,它們是保留關鍵字。重要的是要注意 nil 是如前所述的不同資料型別。and、or、not() 通常與布林值相關聯,但在 Lua 中可以與任何資料型別一起使用。
| 運算 | 語法 | 描述 |
|---|---|---|
| 布林值否定 | not a | 如果 a 為假或空值,則返回真。否則,返回假。 |
| 邏輯與 | a and b | 如果第一個引數為假或空值,則返回第一個引數。否則,返回第二個引數。 |
| 邏輯或 | a or b | 如果第一個引數既不為假也不為空值,則返回第一個引數。否則,返回第二個引數。 |
本質上,not 運算子只是否定布林值(如果它是真則將其設為假,如果它是假則將其設為真),and 運算子如果兩者都為真則返回真,否則返回假,or 運算子如果任何一個引數為真則返回真,否則返回假。然而,這不是它們工作的方式,因為它們工作的確切方式如上表所示。在 Lua 中,值 false 和 nil 在邏輯表示式中都被認為是假,而其他所有東西都被認為是真(即使是 0 和空字串)。
下一章介紹的關係運算符 (<、>、<=、>=、~=、==) 不一定以布林值為運算元,但總是會給出布林值作為結果。
這可能難以協調。為了更清楚起見,這裡有一些真值表或表示式-結果對。這裡 x 可以是 nil、true 或 false
| 表示式 | 結果 |
|---|---|
true and x
|
x
|
false and x
|
false
|
nil and x
|
nil
|
true or x
|
true
|
false or x
|
x
|
nil or x
|
x
|
這有點違反直覺地意味著
| 表示式 | 結果 |
|---|---|
false and nil
|
false
|
nil and false
|
nil
|
此外,
| 表示式 | 結果 |
|---|---|
false == nilnil == false
|
false
|
nil and false
|
nil
|
| 表示式 | 結果 |
|---|---|
not(false)not(nil)
|
true
|
not(true)
|
false
|
字串
[edit | edit source]字串是字元序列,可用於表示文字。它們可以在 Lua 中透過包含在雙引號、單引號或長括號中來編寫,這些在前面的 關於註釋的部分 中已經介紹過(需要注意的是,註釋和字串除了都可以用長括號分隔外,沒有其他共同之處,註釋之前是兩個連字元)。不包含在長括號中的字串只會延續一行。因此,要建立一個包含多行字串而不用長括號的唯一方法是使用轉義序列。這也是在某些情況下插入單引號或雙引號的唯一方法。轉義序列由兩部分組成:跳脫字元,在 Lua 中總是反斜槓 ('\'),以及標識要轉義的字元的識別符號。
| 轉義序列 | 描述 |
|---|---|
| \n | 換行符 |
| \" | 雙引號 |
| \' | 單引號(或撇號) |
| \\ | 反斜槓 |
| \t | 水平製表符 |
| \### | ### 必須是 0 到 255 之間的數字。結果將是相應的 ASCII 字元。 |
當直接將字元放在字串中會導致問題時,會使用轉義序列。例如,如果你有一個用雙引號括起來的文字字串,並且必須包含雙引號,那麼你需要用不同的字元括起來該字串,或者轉義雙引號。在用長括號分隔的字串中跳脫字元是不必要的,所有字元都是這樣。用長括號分隔的字串中的所有字元都將按原樣使用。% 字元在字串模式中用於轉義魔法字元,但術語轉義是在另一個上下文中使用的。
"This is a valid string."
'This is also a valid string.'
"This is a valid \" string 'that contains unescaped single quotes and escaped double quotes."
[[
This is a line that can continue
on more than one line.
It can contain single quotes, double quotes and everything else (-- including comments). It ignores everything (including escape characters) except closing long brackets of the same level as the opening long bracket.
]]
"This is a valid string that contains tabs \t, double quotes \" and backlashes \\"
"This is " not a valid string because there is an unescaped double quote in the middle of it."
為了方便起見,如果一個開頭的長字串括號緊跟著一個換行符,那麼該換行符將被忽略。因此,以下兩個字串是等效的
[[This is a string
that can continue on many lines.]]
[[
This is a string
that can continue on many lines.]]
-- Since the opening long bracket of the second string is immediately followed by a new line, that new line is ignored.
可以使用一元長度運算子 ('#') 獲取字串的長度,它是一個數字
print(#("This is a string")) --> 16
連線
[edit | edit source]— 維基百科, 連線
Lua 中的字串連線運算子用兩個點 ('..') 表示。以下是一個將 “snow” 和 “ball” 連線起來並列印結果的連線示例
print("snow" .. "ball") --> snowball
此程式碼將連線 “snow” 和 “ball” 並在輸出視窗列印結果。
其他型別
[edit | edit source]Lua 中的四種基本型別(數字、布林值、空值和字串)已在前面各節中介紹,但還缺少四種類型:函式、表、使用者資料和執行緒。 函式 是可以呼叫的程式碼塊,可以接收值並返回值。 表 是用於資料操作的資料結構。 使用者資料 由嵌入 Lua 的應用程式內部使用,以允許 Lua 透過應用程式控制的物件與該程式通訊。最後,執行緒 由協程使用,協程允許許多函式同時執行。這些將在後面描述,所以你只需要記住還有其他資料型別。
字面量
[edit | edit source]字面量是在原始碼中表示固定值的表示法。除了執行緒和使用者資料之外,所有值都可以在 Lua 中表示為字面量。例如,字串字面量(計算結果為字串的字面量)由必須表示的字串文字組成,該文字包含在單引號、雙引號或長括號中。另一方面,數字字面量由它們代表的數字組成,這些數字使用十進位制表示法(例如:12.43)、科學記數法(例如:3.1416e-2 和 0.31416E1)或十六進位制表示法(例如:0xff)表示。
強制轉換
[edit | edit source]強制轉換是將一個數據型別的值轉換為另一個數據型別的值。Lua 在字串和數字值之間提供自動強制轉換。對字串應用的任何算術運算都將嘗試將該字串轉換為數字。相反,當需要字串而使用數字時,數字將被轉換為字串。這適用於 Lua 運算子和預設函式(隨語言提供的函式)。
print("122" + 1) --> 123
print("The number is " .. 5 .. ".") --> The number is 5.
數字到字串和字串到數字的強制轉換也可以使用tostring和tonumber函式手動完成。前者接受一個數字作為引數並將其轉換為字串,而後者接受一個字串作為引數並將其轉換為數字(可選地在第二個引數中給出與預設十進位制不同的基數)。
位運算
[edit | edit source]從 Lua 5.3 開始,提供位運算子來對二進位制數(位模式)進行運算。這些運算子不像其他運算子那樣常用,因此,如果您不需要它們,可以跳過本節。
Lua 中的位運算子始終對整數進行運算,如果需要,會將其運算元轉換為整數。它們也返回整數。
按位與運算(運算子為&)對兩個等長二進位制表示的每對位執行邏輯與運算。例如,5 & 3計算結果為 1。我們可以透過檢視這些數字的二進位制表示來解釋這一點(下標用於表示基數)
如果 5 和 3 的二進位制表示中給定位置的位都是 1(最後一個位的情況),那麼結果中該位置的位將為 1;在所有其他情況下,它將為 0。
按位或運算(運算子為|)的工作方式與按位與相同,執行邏輯或運算,而不是執行邏輯與運算。因此,5 | 3將計算結果為 7
在這裡,我們可以看到,最終結果中每個位置的位僅當兩個運算元的二進位制表示在該位置具有 0 位時才為 0。
按位異或運算(運算子為~)的工作方式與其他兩個運算子類似,但在給定位置,最終位僅當運算元中的一個位(而不是兩個)為 1 時才為 1。
這與前面的示例相同,但我們可以看到,結果中的最後一個位為 0 而不是 1,因為兩個運算元的最後一個位都是 1。
按位取反運算(運算子為~)對唯一運算元的每個位執行邏輯取反運算,這意味著每個 0 變成 1,每個 1 變成 0。因此,~7將計算結果為 -8
在這裡,第一個位在結果中變成了 1,因為它在運算元中是 0,其他位變成了 0,因為它們都是 1。
除了這些位運算子之外,Lua 5.3 還支援算術位移。 左移(運算子為<<,左側圖示)包括將所有位向左移動,移動的位數與第二個運算元對應。 右移(運算子為>>,右側圖示)執行相同的操作,但方向相反。
運算子優先順序
[edit | edit source]運算子優先順序在 Lua 中的工作方式與數學中通常相同。某些運算子將在其他運算子之前進行計算,並且可以使用括號來任意更改操作應執行的順序。運算子計算的優先順序在下面的列表中,從優先順序高到低。其中一些運算子尚未討論,但它們都將在本書的某個部分介紹。
- 指數運算:
^ - 一元運算子:
not、#、-、~ - 級別 2 數學運算子:
*、/、//、% - 級別 1 數學運算子:
+、- - 連線:
.. - 位移:
<<、>> - 按位與:
& - 按位異或:
~ - 按位或:
| - 關係運算符:
<、>、<=、>=、~=、== - 布林與:
and - 布林或:
or
測驗
[edit | edit source]你可以回答一些問題,以驗證你是否理解了本章的內容。請注意,要找到其中一些問題的答案,可能需要你具備本章未介紹的知識。這是正常的:測驗是學習體驗的一部分,它們可以介紹本書其他地方沒有介紹的資訊。
語句
[edit | edit source]語句是可以執行的程式碼片段,包含指令和用於該指令的表示式。一些語句還將在其內部包含程式碼,例如,這些程式碼可能會在特定條件下執行。與表示式不同,它們可以直接放在程式碼中並執行。Lua 的指令很少,但這些指令與其他指令以及複雜的表示式結合使用,可以為使用者提供大量的控制和靈活性。
賦值
[edit | edit source]程式設計師經常需要能夠將值儲存在記憶體中以便以後使用它們。這是使用變數來完成的。 變數是儲存在計算機記憶體中的值的引用。它們可以用來在將數字儲存在記憶體中之後訪問該數字。 賦值是用於將值分配給變數的指令。它由應儲存值的變數名稱、一個等號以及應儲存在變數中的值組成
variable = 43
print(variable) --> 43
如上面的程式碼所示,可以透過將變數名稱放在應該訪問值的 地方來訪問變數的值。
在 Lua 中,與大多數其他程式語言一樣,等號 (=) 充當二元 賦值運算子,將右手運算元表示式的值賦給左手運算元命名的變數。
以下示例展示了使用等號進行變數賦值。
fruit = "apple" -- assign a string to a variable
count = 5 -- assign a numeric value to a variable
請注意,字面量字串應該用引號括起來,以將其與變數名區分開來。
apples = 5
favourite = "apples" -- without quotes, apples would be interpreted as a variable name
請注意,數值不需要用引號括起來,也不能被誤解為變數名,因為變數名不能以數字開頭。
apples = 6 -- no quotes are necessary around a numeric parameter
pears = "5" -- quotes will cause the value to be considered a string
Lua 程式語言支援多重賦值。
apples, favorite = 5, "apples" -- assigns apples = 5, favorite = "apples"
識別符號,在 Lua 中,也稱為名稱。它們可以是任何由字母、數字和下劃線組成的文字,並且不能以數字開頭。它們用於命名變數和表格欄位,這些將在關於表格的章節中介紹。
以下是一些有效的名稱
namehello__tomatoesme41___thisIs_StillaValid23name
以下是一些無效的名稱
2hello: 以數字開頭th$i: 包含非字母、數字或下劃線的字元hel!o: 包含非字母、數字或下劃線的字元563text: 以數字開頭82_something: 以數字開頭
此外,以下關鍵字由 Lua 保留,不能用作名稱:and, break, do, else, elseif, end, false, for, function, if, in, local, nil, not, or, repeat, return, then, true, until, while.
在命名變數或表格欄位時,必須為其選擇有效的名稱。因此,它必須以字母或下劃線開頭,並且只能包含字母、下劃線和數字。請注意,Lua 區分大小寫。這意味著 Hello 和 hello 是兩個不同的名稱。
變數的作用域 是程式程式碼中該變數有意義的區域。您之前看到的變數示例都是全域性變數的示例,這些變數可以在程式的任何地方訪問。另一方面,區域性變數只能在定義它們的程式區域以及位於該程式區域內的程式區域中使用。它們以與全域性變數完全相同的方式建立,但必須以 local 關鍵字為字首。
do 語句將用於描述它們。do 語句是一個沒有其他目的的語句,只是為了建立一個新的程式碼塊,因此建立一個新的作用域。它以 end 關鍵字結束。
local variable = 13 -- This defines a local variable that can be accessed from anywhere in the script since it was defined in the main region.
do
-- This statement creates a new block and also a new scope.
variable = variable + 5 -- This adds 5 to the variable, which now equals 18.
local variable = 17 -- This creates a variable with the same name as the previous variable, but this one is local to the scope created by the do statement.
variable = variable - 1 -- This subtracts 1 from the local variable, which now equals 16.
print(variable) --> 16
end
print(variable) --> 18
當作用域結束時,其中的所有變數都會被清除。程式碼區域可以使用定義在其包含的程式碼區域中的變數,但如果它們透過定義具有相同名稱的區域性變數來“覆蓋”它們,則將使用該區域性變數,而不是定義在其他程式碼區域中的變數。這就是為什麼第一次呼叫 print 函式列印 16,而第二次呼叫在 do 語句建立的作用域之外,列印 18。
實際上,應該只使用區域性變數,因為它們可以比全域性變數更快地定義和訪問,因為它們儲存在暫存器中,而不是儲存在當前函式的環境中,就像全域性變數一樣。暫存器是 Lua 用於儲存區域性變數以快速訪問它們的區域,通常最多隻能包含 200 個區域性變數。處理器是所有計算機的重要組成部分,它也有暫存器,但這些與 Lua 的暫存器無關。每個函式(包括主執行緒,程式的核心,它也是一個函式)也有自己的環境,它是一個使用索引作為變數名稱並將這些變數的值儲存在對應於這些索引的值中的表格。
增量賦值,也稱為 複合賦值,是一種賦值型別,它賦予變數一個與其先前值相關的值,例如,遞增當前值。a += 8 程式碼的等效項,它將 a 的值遞增 8,已知存在於 C、JavaScript、Ruby、Python 中,在 Lua 中不存在,這意味著有必要編寫 a = a + 8。
鏈式賦值 是一種賦值型別,它賦予多個變數一個單一值。例如,程式碼 a = b = c = d = 0 會在 C 和 Python 中將 a、b、c 和 d 的值設定為 0。在 Lua 中,此程式碼會引發錯誤,因此有必要像這樣編寫前面的示例。
d = 0
c = d -- or c = 0
b = c -- or b = 0
a = b -- or a = 0
並行賦值,也稱為 同時賦值 和 多重賦值,是一種同時將不同的值(它們也可以是相同的值)賦予不同變數的賦值型別。與鏈式賦值和增量賦值不同,並行賦值在 Lua 中是可用的。
上一節中的示例可以改寫為使用並行賦值。
a, b, c, d = 0, 0, 0, 0
如果您提供的變數比值多,則某些變數將不會被分配任何值。如果您提供的值比變數多,則多餘的值將被忽略。更準確地說,在分配發生之前,值的列表將根據變數列表的長度進行調整,這意味著多餘的值將被刪除,並且將在其末尾新增額外的 nil 值,使其長度與變數列表相同。如果函式呼叫存在於 值的列表末尾,它返回的值將被新增到該列表的末尾,除非函式呼叫放在括號中。
此外,與大多數程式語言不同,Lua 允許透過 置換 重新分配變數的值。例如
first_variable, second_variable = 54, 87
first_variable, second_variable = second_variable, first_variable
print(first_variable, second_variable) --> 87 54
這是因為賦值語句在分配任何內容之前評估所有變數和值。賦值就像它們是真正同時執行的一樣,這意味著您可以同時將值分配給變數和分配給使用該變數的值作為索引的表格欄位,在它被分配一個新值之前。換句話說,以下程式碼將把 dictionary[2] 設定為 12,而不是 dictionary[1]。
dictionary = {}
index = 2
index, dictionary[index] = index - 1, 12
條件語句是檢查表示式是否為真的指令,如果為真則執行特定的程式碼段。如果表示式不為真,它們只會跳過該程式碼段,程式繼續執行。在 Lua 中,唯一的條件語句使用 if 指令。False 和 nil 都被認為是 false,而其他所有內容都被認為是 true。
local number = 6
if number < 10 then
print("The number " .. number .. " is smaller than ten.")
end
-- Other code can be here and it will execute regardless of whether the code in the conditional statement executed.
在上面的程式碼中,變數 number 使用賦值語句被分配了數字 6。然後,條件語句檢查儲存在變數 number 中的值是否小於十,這裡的情況就是這樣。如果是,則列印 "數字 6 小於十。"。
還可以使用 else 關鍵字來僅在表示式不為真的情況下執行特定的程式碼段,並使用 elseif 關鍵字來連結條件語句。
local number = 15
if number < 10 then
print("The number is smaller than ten.")
elseif number < 100 then
print("The number is bigger than or equal to ten, but smaller than one hundred.")
elseif number ~= 1000 and number < 3000 then
print("The number is bigger than or equal to one hundred, smaller than three thousands and is not exactly one thousand.")
else
print("The number is either 1000 or bigger than 2999.")
end
請注意,else 塊必須始終是最後一個塊。在 else 塊之後不能有 elseif 塊。只有在前面所有塊都沒有執行的情況下,elseif 塊才有意義。
用於比較兩個值的運算子稱為關係運算符,其中一些在上面的程式碼中使用。如果關係為真,則它們返回布林值 true。否則,它們返回布林值 false。
| 等於 | 不等於 | 大於 | 小於 | 大於或等於 | 小於或等於 | |
|---|---|---|---|---|---|---|
| 數學符號 | = | ≠ | > | < | ≥ | ≤ |
| Lua 運算子 | == | ~= | > | < | >= | <= |
上面的程式碼還演示瞭如何使用 and 關鍵字在條件表示式中組合多個布林表示式。
程式設計師經常需要多次執行一段特定的程式碼或類似的程式碼,或者需要執行一段特定程式碼的次數,而該次數可能取決於使用者輸入。迴圈是一組語句,這些語句只指定一次,但可以連續執行多次。
條件控制迴圈是由條件控制的迴圈。它們與條件語句非常相似,但它們不會在條件為真時執行程式碼,而在條件為假時跳過程式碼,而是會持續執行程式碼,直到條件為真,或者直到條件為假。Lua 有兩個用於條件控制迴圈的語句:while 迴圈和 repeat 迴圈。此類迴圈將執行程式碼,然後檢查條件是否為真。如果為真,則它們再次執行程式碼,並重復,直到條件為假。當條件為假時,它們停止重複程式碼,程式流程繼續。程式碼的每次執行稱為一次迭代。while 迴圈和 repeat 迴圈之間的區別在於,repeat 迴圈將在迴圈結束時檢查條件,而 while 迴圈將在迴圈開始時檢查條件。這僅對第一次迭代有所區別:repeat 迴圈始終至少執行一次程式碼,即使在第一次執行程式碼時條件為假。而 while 迴圈則不是這樣,它僅在條件實際為真時才第一次執行程式碼。
local number = 0
while number < 10 do
print(number)
number = number + 1 -- Increase the value of the number by one.
end
上面的程式碼將列印 0,然後列印 1,然後列印 2,然後列印 3,依此類推,直到列印 9。在第十次迭代之後,number 將不再小於十,因此迴圈將停止執行。有時,迴圈旨在永遠執行,在這種情況下,它們被稱為無限迴圈。渲染器(例如,在螢幕上繪製內容的軟體程序)通常會不斷迴圈以重新繪製螢幕,以更新顯示給使用者的影像。這在影片遊戲中很常見,因為遊戲檢視必須不斷更新,以確保使用者看到的內容保持最新狀態。但是,需要迴圈永遠執行的情況很少見,此類迴圈通常是錯誤的結果。無限迴圈會佔用大量的計算機資源,因此務必確保迴圈始終結束,即使使用者收到意外的輸入。
local number = 0
repeat
print(number)
number = number + 1
until number >= 10
上面的程式碼將與使用 while 迴圈的程式碼執行完全相同的事情。主要區別在於,與 while 迴圈不同,while 迴圈將條件放在 while 關鍵字和 do 關鍵字之間,而 repeat 迴圈將條件放在迴圈的末尾,在 until 關鍵字之後。repeat 迴圈是 Lua 中唯一建立一個塊且不以 end 關鍵字結尾的語句。
遞增變數是將其值增加步長,特別是增加一步長。上一節中的兩個迴圈遞增了變數 number 並使用它來執行程式碼一定的次數。這種迴圈非常常見,以至於大多數語言(包括 Lua)都為此內建了控制結構。這種控制結構稱為計數控制迴圈,在 Lua 和大多數語言中,由 for 語句定義。在這些迴圈中使用的變數稱為迴圈計數器。
for number = 0, 9, 1 do
print(number)
end
上面的程式碼與上一節中介紹的兩個迴圈執行完全相同的事情,但 number 變數只能在迴圈內部訪問,因為它對迴圈是區域性的。變數名和等號後面的第一個數字是初始化。它是迴圈計數器初始化的值。第二個數字是迴圈停止的數字。它將遞增變數並重復程式碼,直到變數達到此數字。最後,第三個數字是增量:它是每次迭代時迴圈計數器增加的值。如果沒有給出增量,Lua 將假定它為 1。因此,下面的程式碼將列印 1、1.1、1.2、1.3、1.4 和 1.5。
for n = 1, 2, 0.1 do
print(n)
if n >= 1.5 then
break -- Terminate the loop instantly and do not repeat.
end
end
上面的程式碼沒有一直上升到 2,而是隻上升到 1.5 的原因是 break 語句,它會立即終止迴圈。此語句可以與任何迴圈一起使用,包括 while 迴圈和 repeat 迴圈。請注意,這裡使用了 >= 運算子,儘管理論上 == 運算子也可以完成任務。這是因為十進位制精度誤差。Lua 使用 雙精度浮點數格式 來表示數字,該格式將數字儲存在記憶體中,作為其實際值的近似值。在某些情況下,近似值將與數字完全匹配,但在某些情況下,它將僅是近似值。通常,這些近似值足夠接近數字,以至於不會產生影響,但此係統可能會在使用等號運算子時導致錯誤。這就是為什麼在處理十進位制數時,通常更安全地避免使用等號運算子。在這個特定情況下,如果使用了等號運算子,程式碼將無法工作[1](它會繼續上升到 1.9),但使用 >= 運算子就可以工作。
塊是按順序執行的語句列表。這些語句可以包括空語句,這些語句不包含任何指令。空語句可以用於以分號開始一個塊,或者在序列中寫入兩個分號。
函式呼叫和賦值可以以括號開頭,這會導致歧義。以下片段就是一個例子
a = b + c
(print or io.write)('done')
此程式碼可以有兩種解釋
a = b + c(print or io.write)('done')
a = b + c; (print or io.write)('done')
當前解析器始終以第一種方式檢視這些結構,將左括號解釋為呼叫引數的開頭。為了避免這種歧義,最好始終以分號作為以括號開頭的語句的字首
;(print or io.write)('done')
Lua 的編譯單元稱為 塊。塊可以儲存在檔案中,也可以儲存在宿主程式中的字串中。要執行一個塊,Lua 首先將其預編譯為虛擬機器的指令,然後使用虛擬機器的直譯器執行編譯後的程式碼。塊也可以使用 luac(隨 Lua 提供的編譯程式)或 string.dump 函式(它返回包含給定函式的二進位制表示的字串)預編譯為二進位制形式(位元組碼)。
load 函式可以用來載入一個塊。如果給 load 函式的第一個引數是一個字串,則該塊就是該字串。在這種情況下,該字串可以是 Lua 程式碼或 Lua 位元組碼。如果第一個引數是一個函式,則 load 將重複呼叫該函式以獲取塊的片段,每個片段都是一個字串,將與之前的字串連線起來。然後,當什麼也不返回或返回空字串時,就認為該塊已完成。
如果 load 函式沒有語法錯誤,則它將返回已編譯的塊作為函式。否則,它將返回 nil 和錯誤訊息。
load 函式的第二個引數用於設定程式碼塊的來源。所有程式碼塊都保留其來源的副本,以便能夠給出適當的錯誤訊息和除錯資訊。預設情況下,其來源的副本將是傳遞給 load 的程式碼(如果提供了程式碼;如果改為提供函式,它將是“=(load)”。)。可以使用此引數來更改它。這在編譯程式碼時非常有用,可以防止人們獲取原始程式碼。然後有必要刪除包含在二進位制表示中的原始碼,否則可以在那裡獲取原始程式碼。
load 函式的第三個引數可以用於設定生成的函式的環境,第四個引數控制程式碼塊可以是文字還是二進位制。它可以是字串“b”(僅二進位制程式碼塊)、“t”(僅文字程式碼塊)或“bt”(二進位制程式碼塊和文字程式碼塊)。預設值為“bt”。
還有一個 loadfile 函式,它的工作方式與 load 完全相同,但它從檔案中獲取程式碼。第一個引數是從中獲取程式碼的檔名。沒有引數可以修改儲存在二進位制表示中的原始碼,load 函式的第三個和第四個引數分別對應於此函式的第二個和第三個引數。loadfile 函式也可以用於從標準輸入載入程式碼,如果沒有給出檔名,則將執行此操作。
dofile 函式類似於 loadfile 函式,但它不是將檔案中的程式碼載入為函式,而是立即將原始碼檔案中的程式碼作為 Lua 程式碼塊執行。它的唯一引數用於指定應執行其內容的檔名;如果沒有給出引數,它將執行標準輸入的內容。如果程式碼塊返回值,它們將由對 dofile 函式的呼叫提供。由於 dofile 不在受保護模式下執行,因此透過它執行的程式碼塊中的所有錯誤都會傳播。
函式
[edit | edit source]
一個 堆疊 是一個專案列表,其中可以新增(push)或刪除(pop)專案,它遵循後進先出原則,這意味著最後一個新增的專案將是第一個被刪除的專案。這就是這種列表被稱為堆疊的原因:在堆疊上,您不能在先刪除位於其頂部的專案之前刪除一個專案。因此,所有操作都在堆疊的頂部進行。如果一個專案是在另一個專案之後新增的,則它位於另一個專案之上;如果是在另一個專案之前新增的,則位於另一個專案之下。
一個 函式(也稱為子程式、過程、例程或子程式)是一系列執行特定任務的指令,這些指令可以從程式中的其他地方呼叫,只要應該執行該指令序列即可。函式還可以接收值作為輸入並在可能對輸入進行操作或根據輸入執行任務後返回輸出。函式可以從程式中的任何地方定義,包括在其他函式內部,也可以從程式中訪問它們的任何部分呼叫:函式與數字和字串一樣,都是值,因此可以儲存在變數中並具有所有變數的共同屬性。這些特點使得函式非常有用。
因為函式可以從其他函式呼叫,所以 Lua 直譯器(讀取和執行 Lua 程式碼的程式)需要能夠知道哪個函式呼叫了當前正在執行的函式,以便在函式終止時(當沒有更多的程式碼要執行時),它可以返回到正確函式的執行。這是透過一個稱為呼叫堆疊的堆疊來完成的:呼叫堆疊中的每個專案都是一個函式,它呼叫了堆疊中直接位於其上方的函式,直到堆疊中的最後一個專案,它就是當前正在執行的函式。當函式終止時,直譯器使用堆疊的彈出操作來刪除列表中的最後一個函式,然後它返回到上一個函式。
函式有兩種型別:內建函式和使用者定義函式。 內建函式 是 Lua 提供的函式,包括諸如 print 函式之類的函式,您已經知道。一些可以直接訪問,比如 print 函式,但另一些需要透過庫訪問,比如 math.random 函式,它返回一個隨機數。 使用者定義函式 是由使用者定義的函式。使用者定義的函式是使用函式構造器定義的
local func = function(first_parameter, second_parameter, third_parameter)
-- function body (a function's body is the code it contains)
end
上面的程式碼建立了一個具有三個引數的函式,並將其儲存在變數 func 中。下面的程式碼與上面的程式碼完全相同,但使用定義函式的語法糖
local function func(first_parameter, second_parameter, third_parameter)
-- function body
end
需要注意的是,當使用第二種形式時,可以在函式本身內部引用該函式,而使用第一種形式時則無法做到。這是因為 local function foo() end 翻譯為 local foo; foo = function() end 而不是 local foo = function() end。這意味著 foo 在第二種形式中是函式的環境的一部分,而在第一種形式中則不是,這解釋了為什麼第二種形式使得引用函式本身成為可能。
在這兩種情況下,都可以省略 local 關鍵字,將函式儲存在全域性變數中。引數與變數類似,允許函式接收值。當呼叫函式時,可能會向它傳遞引數。然後,函式將接收它們作為引數。引數就像在函式開頭定義的區域性變數一樣,它們將根據函式呼叫中傳遞的引數順序依次賦值;如果缺少引數,則引數將具有 nil 值。以下示例中的函式將兩個數字相加並列印結果。因此,當代碼執行時,它將列印 5。
local function add(first_number, second_number)
print(first_number + second_number)
end
add(2, 3)
函式呼叫大多數情況下采用 name(arguments) 的形式。但是,如果只有一個引數並且它是表格或字串,並且它不在變數中(這意味著它直接在函式呼叫中構造,表示為文字),則可以省略括號
print "Hello, world!"
print {4, 5}
上一個示例中的第二行程式碼將打印表格的記憶體地址。當將值轉換為字串時,print 函式會自動執行此操作,複雜型別(函式、表格、使用者資料和執行緒)會更改為它們的記憶體地址。但是,布林值、數字和 nil 值將轉換為相應的字串。
在實踐中,術語引數和引數通常可以互換使用。在這本書中,以及在它們適當的含義中,術語引數和引數分別表示一個名稱,對應引數的值將被分配給它,以及一個值,它將傳遞給函式以分配給引數。
返回值
[edit | edit source]函式可以接收輸入,對其進行操作並返回輸出。您已經知道它們如何接收輸入(引數)並對其進行操作(函式體)。它們還可以透過返回一個或多個任何型別的返回值來給出輸出,這是使用 return 語句完成的。這就是為什麼函式呼叫既是語句又是表示式的原因:它們可以被執行,但它們也可以被評估。
local function add(first_number, second_number)
return first_number + second_number
end
print(add(5, 6))
上面函式中的程式碼將首先定義函式 add。然後,它將使用 5 和 6 作為值呼叫它。函式將它們相加並返回結果,然後該結果將被列印。這就是為什麼上面的程式碼會列印 11。函式也可以透過用逗號分隔評估為這些值的表示式來返回多個值。
使用者可以定義自己的函式,並根據自己的需要對其進行定製。
除了接受引數和返回值外,使用者定義的函式還可以具有副作用,即由函式執行引起的變數或程式狀態的更改。 [1]
錯誤
[edit | edit source]錯誤有三種類型:語法錯誤、靜態語義錯誤和語義錯誤。語法錯誤發生在程式碼明顯無效時。例如,下面的程式碼將被 Lua 檢測為無效
print(5 ++ 4 return)
上面的程式碼沒有意義;不可能從它中獲得意義。類似地,在英語中,“cat dog tree”在語法上無效,因為它沒有意義。它不遵循建立句子的規則。
靜態語義錯誤發生在程式碼有意義,但仍然沒有意義時。例如,如果您嘗試將字串與數字相加,您會得到一個靜態語義錯誤,因為不可能將字串與數字相加
print("hello" + 5)
上面的程式碼遵循 Lua 的語法規則,但它仍然沒有意義,因為它不可能將字串與數字相加(除非字串表示數字,在這種情況下它將被強制轉換為數字)。這在英語中可以比作句子“I are big”。它遵循用英語建立句子的規則,但它仍然沒有意義,因為“I”是單數,而“are”是複數。
最後,語義錯誤是指程式碼的含義與其建立者所想的不一致時發生的錯誤。這些是最糟糕的錯誤,因為它們可能非常難發現。Lua 會在出現語法錯誤或靜態語義錯誤時始終提示你(這被稱為丟擲錯誤),但它無法在出現語義錯誤時提示你,因為它不知道你認為程式碼的含義是什麼。這些錯誤發生的頻率比大多數人認為的要高,查詢和修正它們是許多程式設計師花費大量時間做的事情。
查詢錯誤並修正錯誤的過程稱為除錯。大多數時候,程式設計師花費在查詢錯誤上的時間比實際修正錯誤的時間還要多。這對於所有型別的錯誤都是正確的。一旦你知道問題是什麼,通常很容易修復它,但有時,程式設計師可能會花幾個小時檢視一段程式碼,卻無法找到其中的錯誤。
丟擲錯誤是指指示程式碼有錯誤的行為,無論是手動完成還是由直譯器(讀取程式碼並執行它的程式)自動完成。當給出的程式碼無效時,Lua 會自動執行此操作,但可以使用error函式手動執行。
local variable = 500
if variable % 5 ~= 0 then
error("It must be possible to divide the value of the variable by 5 without obtaining a decimal number.")
end
error函式還有一個第二個引數,它指示應丟擲錯誤的堆疊級別,但本書不會介紹這一點。assert函式與error函式的作用相同,但它只會在第一個引數計算結果為 nil 或 false 時丟擲錯誤,並且沒有引數可以用於指定應丟擲錯誤的堆疊級別。assert函式在指令碼開頭很有用,例如,用於檢查指令碼工作所需的庫是否可用。
可能難以理解為什麼有人會想要自願丟擲錯誤,因為程式中的程式碼會在丟擲錯誤時停止執行,但是,通常情況下,在函式使用不正確或程式未在正確環境中執行時丟擲錯誤有助於幫助除錯程式碼的人立即找到它,而不必長時間盯著程式碼卻不知道出了什麼問題。
有時,防止錯誤停止程式碼,而是做一些事情,例如向用戶顯示錯誤訊息以便他們向開發人員報告錯誤,可能會很有用。這被稱為異常處理(或錯誤處理),它是透過捕獲錯誤來防止其傳播並執行異常處理程式來處理它。它在不同程式語言中的實現方式差異很大。在 Lua 中,它是使用受保護的呼叫完成的。[2] 它們被稱為受保護的呼叫,因為在受保護模式下呼叫的函式如果發生錯誤不會停止程式。有兩個函式可以用於在受保護模式下呼叫函式
| 函式 | 描述 |
|---|---|
pcall(function, ...) |
在受保護模式下呼叫函式,並返回一個狀態程式碼(一個布林值,其值取決於是否丟擲了錯誤)以及函式返回的值,或者如果函式被錯誤停止,則返回錯誤訊息。可以透過在第一個引數(應在受保護模式下呼叫的函式)之後將它們傳遞給pcall函式,將引數傳遞給函式。 |
xpcall(function, handler, ...) |
與 pcall 做相同的事情,但是,當函數出錯時,它不會返回與 pcall 相同的值,而是將它們作為引數呼叫處理程式函式。然後,處理程式函式可以用來例如顯示錯誤訊息。對於pcall函式,可以透過傳遞給xpcall函式來將引數傳遞給函式。 |
呼叫堆疊(包含所有已呼叫函式的堆疊,按呼叫順序排列)前面已提到過。大多數語言(包括 Lua)中的呼叫堆疊都有最大尺寸。這個最大尺寸非常大,在大多數情況下應該不用擔心,但是,如果沒有任何東西可以阻止它們無限地反覆呼叫自身,那麼呼叫自身的函式(這被稱為遞迴,這樣的函式被稱為遞迴函式)可以達到這個限制。這被稱為堆疊溢位。當堆疊溢位時,程式碼停止執行並丟擲錯誤。
可變引數函式(也稱為 vararg 函式)是接受可變數量引數的函式。可變引數函式在其引數列表的末尾用三個點 ("...") 表示。不適合引數列表中引數的引數不會被丟棄,而是透過 vararg 表示式提供給函式,vararg 表示式也用三個點表示。vararg 表示式的值是一個值列表(不是表),然後可以將其放入表中,以便使用以下表達式更輕鬆地進行操作:{...}。在 Lua 5.0 中,額外的引數不是透過 vararg 表示式提供,而是透過名為“arg”的特殊引數提供。以下函式是接受第一個引數,將其新增到所有接收到的引數中,然後將所有引數加在一起並列印結果的函式的示例
function add_one(increment, ...)
local result = 0
for _, number in next, {...} do
result = result + number + increment
end
end
無需理解上面的程式碼,因為它只是一個可變引數函式的演示。
select函式對於在不需要使用表的情況下操作引數列表很有用。它本身是一個可變引數函式,因為它接受無限數量的引數。它返回第一個引數後所有的引數(如果給出的數字為負數,則從末尾開始索引,這意味著 -1 是最後一個引數)。如果第一個引數是字串“#”,它還會返回它接收到的引數數量,不包括第一個引數。它對於丟棄引數列表中某個數字之前的引數很有用,而且更原始地,用於區分作為引數傳送的 nil 值和作為引數傳送的無值。實際上,當"#"作為第一個引數給出時,select會區分 nil 值和無值。引數列表(以及返回值列表)是元組的例項,將在有關表的章節中介紹;select函式適用於所有元組。
print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)()) --> no value
print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)(nil)) --> nil
print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)(variable_that_is_not_defined)) --> nil
-- As this code shows, the function is able to detect whether the value nil was passed as an argument or whether there was simply no value passed.
-- In normal circumstances, both are considered as nil, and this is the only way to distinguish them.
- ↑ "Lua Functions - Comprehensive Guide with Examples". 2023-03-22. Retrieved 2023-05-17.
- ↑ 有關詳細資訊,請參見:Ierusalimschy, Roberto (2003). "Error Handling in Application Code". Programming in Lua (first ed.). Lua.org. ISBN 8590379817. Retrieved June 20, 2014.
表是 Lua 中唯一的資料結構,但它們比許多其他語言中的資料結構更靈活。它們也被稱為字典(因為它們使值對應於索引,就像字典中的定義對應於詞條一樣)、關聯陣列(因為它們將值與索引關聯,從而使它們成為與索引關聯的值陣列)、雜湊表、符號表、雜湊對映和對映。它們是使用表構造器建立的,表構造器由兩個大括號定義,可以選擇包含用逗號或分號分隔的值。以下示例演示了陣列(一個有序的值列表)並演示瞭如何獲取陣列的長度。
local array = {5, "text", {"more text", 463, "even more text"}, "more text"}
print(#array) --> 4 ; the # operator for strings can also be used for arrays, in which case it will return the number of values in the array
-- This example demonstrates how tables can be nested in other tables. Since tables themselves are values, tables can include other tables. Arrays of tables are called multi-dimensional arrays.
表可以包含除 nil 之外的任何型別的值。這是合理的:nil 代表值的缺失。在表中插入“值的缺失”是沒有意義的。表中的值可以用逗號或分號分隔,並且可以跨多行繼續。通常使用逗號來進行單行表構造器,使用分號來進行多行表,但這並不是必須的。
local array = {
"text";
{
"more text, in a nested table";
1432
};
true -- booleans can also be in tables
}
表由欄位組成,欄位是值對,其中一個值是索引(也稱為鍵),另一個值是對應於該索引的值。在陣列中,索引始終是數值。在字典中,索引可以是任何值。
local dictionary = {
"text";
text = "more text";
654;
[8] = {}; -- an empty table
func = function() print("tables can even contain functions!") end;
["1213"] = 1213
}
如上例所示,可以像在陣列中一樣向字典新增值,只需新增值本身(在這種情況下,索引將是一個數字),或使用識別符號和等號作為字首新增值(在這種情況下,索引將是與識別符號相對應的字串),或者使用括號括住的值和等號作為字首新增值(在這種情況下,索引是括號中的值)。後一種方法是使索引與值相對應的最靈活的方式,因為它可以與任何值或表示式一起使用。
可以透過將與值相對應的索引放在方括號中,並在表示式後新增一個計算結果為表的表示式來訪問表中的值。
local dictionary = {number = 6}
print(dictionary["number"]) --> 6
如果索引是一個字串,並且符合 Lua 識別符號的標準(不包含空格,不以數字開頭,不包含除數字、字母和下劃線之外的任何其他字元),則可以透過在字串前面新增點來訪問它,而不使用方括號。
local dictionary = {["number"] = 6}
print(dictionary.number) --> 6
這兩個示例建立了相同的表並列印了相同的值,但使用不同的符號定義和訪問索引。同樣,對於包含其他表的表,也可以先對第一個表進行索引以獲取巢狀表,然後對巢狀表中的值進行索引來訪問巢狀表中的值。
local nested_table = {
[6] = {
func = function() print("6") end
}
}
nested_table[6].func() -- This will access the nested table, access the function that is inside it and then call that function, which will print the number 6.
關於語句的章節描述了兩種型別的迴圈:條件控制迴圈和計數控制迴圈。在 Lua 中,存在第三種類型的迴圈,即 foreach 迴圈,也稱為泛型 for 迴圈。 foreach 迴圈允許程式設計師為表中的每個欄位執行程式碼。下面的示例演示了一個 foreach 迴圈,該迴圈遍歷數字陣列中的專案,並列印所有索引以及與它們對應的值為 1 的值的總和。
local array = {5, 2, 6, 3, 6}
for index, value in next, array do
print(index .. ": " .. value + 1)
end
-- Output:
-- 1: 6
-- 2: 3
-- 3: 7
-- 4: 4
-- 5: 7
以下示例中的兩個迴圈將執行與上一個示例中的迴圈相同的操作。
local array = {5, 2, 6, 3, 6}
for index, value in pairs(array) do
print(index .. ": " .. value + 1)
end
for index, value in ipairs(array) do
print(index .. ": " .. value + 1)
end
第一個示例中顯示的方法與上一個示例中的第一個迴圈執行相同的操作。但是,最後一個(使用 ipairs 函式的迴圈)只會在表中不存在第一個整數鍵之前迭代,這意味著它只會在數字陣列中按順序迭代。以前有兩個函式分別稱為 table.foreach 和 table.foreachi,但它們在 Lua 5.1 中已被棄用,並在 Lua 5.2 中被刪除。 棄用 是應用於一項功能或實踐的狀態,表示它已被刪除或取代,應避免使用。因此,使用 foreach 迴圈遍歷表比使用這兩個函式更可取。
在 Lua 中,元組(一個簡單的值列表)和表(一個將索引對映到值的結構)之間存在區別。呼叫返回多個值的函式的函式呼叫將計算為元組。賦值語句中的一系列值,其中多個變數被一次性賦值,也是元組。用於可變引數函式的 vararg 表示式也是元組。由於元組是值列表,因此不是單個值,因此它不能儲存在變數中,儘管它可以儲存在多個變數中。可以透過將計算為元組的表示式放在表建構函式中來將元組轉換為陣列。
function return_tuple()
return 5, 6 -- Because this function returns two values, a function call to it evaluates to a tuple.
end
local array = {return_tuple()} -- same as local array = {5, 6}
local a, b = return_tuple() -- same as local a, b = 5, 6
print(return_tuple()) -- same as print(5, 6)
可以透過使用 table 庫的 unpack 函式並將陣列作為引數來解包陣列(將其從表更改為元組)。
local array = {7, 4, 2}
local a, b, c = table.unpack(array)
print(a, b, c) --> 7, 4, 2
在 Lua 5.2 之前的 Lua 版本中,unpack 函式是基本庫的一部分。它現在已移至 table 庫。
由於表可以包含函式並將名稱與這些函式關聯,因此它們通常用於建立庫。Lua 還具有語法糖,可用於建立方法,這些方法是用於操作物件的函式,通常由表表示。對於不瞭解面向物件程式設計的人來說,這可能有點難以理解,面向物件程式設計超出了本書的範圍,因此不理解這一點也無妨。以下兩個示例執行完全相同的操作。
local object = {}
function object.func(self, arg1, arg2)
print(arg1, arg2)
end
object.func(object, 1, 2) --> 1, 2
local object = {}
function object:func(arg1, arg2)
print(arg1, arg2)
end
object:func(1, 2) --> 1, 2
當呼叫表中某個函式時,該函式對應於一個字串型別的索引,使用冒號而不是點將新增一個隱藏引數,即表本身。類似地,使用冒號而不是點在表中定義函式將在引數列表中新增一個隱藏的 self 引數。使用冒號定義函式並不意味著呼叫函式時也必須使用冒號,反之亦然,因為它們是完全可以互換的。
在 Lua 中排序表可能比較簡單。在大多數情況下,可以將 table 庫的 sort 函式用於排序表,這使得一切變得相對容易。sort 函式按指定順序對陣列中的元素進行排序,就地進行(即,不建立新陣列)。如果提供了一個函式作為第二個引數,則該函式應接收陣列中的兩個元素,並在第一個元素應在最終順序中排在第二個元素之前時返回 true。如果沒有提供第二個引數,Lua 將根據 < 運算子對陣列中的元素進行排序,該運算子在用於兩個數字時返回 true,並且第一個數字小於第二個數字,但也適用於字串,在這種情況下,它將在第一個字串按字典順序小於第二個字串時返回 true。
元表是表,可用於控制其他表的行為。這透過 元方法 完成,該表中的欄位指示 Lua 虛擬機器在程式碼嘗試以特定方式操作表時應執行的操作。元方法由其名稱定義。例如,__index 元方法告訴 Lua 在程式碼嘗試對錶中尚未存在的欄位進行索引時該怎麼辦。可以使用 setmetatable 函式設定表的元表,該函式接受兩個表作為引數,第一個表是應設定元表的表,第二個表是應設定表元表的元表。還有一個 getmetatable 函式,它返回表的元表。以下程式碼演示瞭如何使用元表使表中所有不存在的欄位看起來像包含一個數字;這些數字是使用 math.random 函式隨機生成的。
local associative_array = {
defined_field = "this field is defined"
}
setmetatable(associative_array, {
__index = function(self, index)
return math.random(10)
end
})
在上面的示例中,可能會注意到很多事情。其中一件事可能是,包含 __index 元方法的欄位的名稱以兩個下劃線為字首。這種情況總是如此:當 Lua 在表的元表中查詢元方法時,它會查詢與元方法名稱相對應並且以兩個下劃線開頭的索引。另一個可能會注意到的是,__index 元方法實際上是一個函式(大多數元方法是函式,但並非全部都是函式),它接受兩個引數。第一個引數 self 是呼叫 __index 元方法的表。在本例中,我們可以直接引用 associative_array 變數,但這在多個表使用單個元表時很有用。第二個引數是嘗試索引的索引。最後,可以注意到該函式透過返回值來告訴 Lua 虛擬機器應該向索引表的程式碼提供什麼。大多數元方法只能是函式,但 __index 元方法也可以是一個表。如果是表,當程式嘗試索引表中不存在的欄位時,Lua 將在該表中查詢相同的索引。如果找到該索引,它將返回 __index 元方法中指定的表中與該索引相對應的值。
- __newindex(self, index, value)
__newindex元方法可用於告訴 Lua 虛擬機器在程式嘗試在表中新增新欄位時該怎麼辦。它只能是一個函式,並且僅在要寫入的索引不存在現有欄位時才會被呼叫。它有三個引數:前兩個引數與__index元方法的引數相同,第三個引數是程式嘗試將欄位的值設定為的值。- __concat(self, value)
__concat元方法可用於告訴 Lua 虛擬機器在程式嘗試使用連線運算子 (..) 將值連線到表時該怎麼辦。- __call(self, ...)
__call元方法可用於指定程式嘗試呼叫表時應該發生什麼。這使得表確實可以像函式一樣工作。在表被呼叫時傳遞的引數將放在self引數之後,self引數始終是表本身。此元方法可用於返回值,在這種情況下,呼叫表將返回這些值。- __unm(self)
- 此元方法可用於指定對錶使用一元減運算子的效果。
- __tostring(self)
- 此元方法可以是一個函式,該函式返回
tostring函式在使用表作為引數呼叫時應返回的值。 - __add(self, value)
- __sub(self, value)
- __mul(self, value)
- __div(self, value)
- __mod(self, value)
- __pow(self, value)
- 這些元方法可用於分別告訴虛擬機器在將值加到、減去、乘以或除以表時該怎麼辦。最後兩個類似,但分別用於模運算子 (
%) 和冪運算子 (^)。 - __eq(self, value)
- 此元方法由等號運算子 (
==) 使用。僅當比較的兩個值的元表相同時,才會使用它。不等運算子 (~=) 使用此函式結果的反面。 - __lt(self, value)
- __le(self, value)
- 這些元方法由“小於”和“小於或等於”運算子使用。“大於”和“大於或等於”運算子將返回這些元方法返回結果的反面。僅當值的元表相同時,才會使用它們。
- __gc(self)
- 當垃圾收集器收集一個附加了此元方法的元表的某個值之前,Lua 會呼叫此元方法。它只對使用者資料值起作用,不能用於表格。
- __len(self)
- 當在使用者資料值上使用長度運算子(
#)時,Lua 會呼叫此元方法。它不能用於表格。 - __metatable
- 如果存在此元方法(可以是任何內容),則在表格上使用
getmetatable將返回此元方法的值,而不是元表,並且不允許使用setmetatable函式更改表格的元表。 - __mode
- 此元方法應該是一個字串,其中可以包含字母 "k" 或 "v"(或兩者)。如果存在字母 "k",則表格的鍵將是弱的。如果存在字母 "v",則表格的值將是弱的。這到底意味著什麼將在後面解釋,以及垃圾收集。
元表雖然正常的 Lua 程式只能將它們與表格一起使用,但實際上是 Lua 處理運算子和操作的核心機制,並且理論上它們實際上可以與任何值一起使用。但是,Lua 只允許將它們與表格和使用未記錄的 newproxy 函式建立的使用者資料值一起使用。使用 Lua 的 C API 或除錯庫,可以設定其他型別值(如數字和字串)的元表。
有時希望在不呼叫元方法的情況下對錶格執行操作。這對於索引、在表格中新增欄位、檢查相等性和使用 rawget、rawset、rawequal 和 rawlen 函式分別獲取表格的長度是可能的。第一個返回對應於作為第二個引數給出的索引的值,該索引在作為第一個引數給出的表格中。第二個將作為第三個引數給出的值設定為對應於作為第二個引數給出的索引的值,該索引在作為第一個引數給出的表格中。第三個返回兩個給定值是否相等。最後,第四個返回它給定物件的長度(一個整數),該物件必須是表格或字串。
迭代器
[edit | edit source]迭代器是一個與 foreach 迴圈結合使用的函式。在大多數情況下,迭代器用於遍歷資料結構。例如,由 pairs 和 ipairs 函式返回的迭代器分別用於遍歷表格或陣列的元素。例如,pairs 函式返回 next 函式,以及它作為引數給定的表格,這解釋了 in pairs(dictionary) 與 in next, dictionary 的等效性,因為前者實際上評估為後者。
迭代器不需要始終與資料結構一起使用,因為迭代器可以針對需要迴圈的任何情況進行設計。例如,file:lines 函式返回一個迭代器,該迭代器在每次迭代時從檔案中返回一行。類似地,string.gmatch 函式返回一個迭代器,該迭代器在每次迭代時返回字串中模式的匹配項。例如,以下程式碼將列印名為“file.txt”的檔案中的所有行。
for line in io.open("file.txt"):lines() do
print(line)
end
建立迭代器
[edit | edit source]迭代器包含三個部分
- 一個轉換函式
- 一個狀態值
- 一個或多個迴圈變數
轉換函式用於修改每次迴圈迭代的迴圈變數(出現在 for 和 in 之間的變數)的值。此函式在每次迭代之前被呼叫,並以迴圈變數在上次迭代期間設定的值作為引數。該函式應返回一個包含這些變數的新值的元組(一個或多個值)。迴圈變數將設定為返回的元組的元件,並且迴圈將進行一次迭代。一旦該迭代完成(只要它沒有被 break 或 return 語句中斷),轉換函式將再次被呼叫,並返回另一組值,迴圈變數將為下一次迭代設定這些值,依此類推。呼叫轉換函式和迭代迴圈語句的這種迴圈將持續到轉換函式返回 nil 為止。
除了迴圈變數之外,轉換函式還會傳入一個狀態值,該狀態值在整個迴圈中將保持不變。例如,狀態值可用於維護對轉換函式正在迭代的資料結構、檔案控制代碼或資源的引用。
以下是一個將生成一系列數字(直到 10)的轉換函式的示例。此轉換函式僅需要一個迴圈變數,其值將儲存在 value 中。
function seq(state, value)
if (value >= 10) then
return nil -- Returning nil will terminate the loop
else
local new_value = value + 1 -- The next value to use within the loop is the current value of `value` plus 1.
-- This value will be used as the value of `value` the next time this function is called.
return new_value
end
end
泛型 for 迴圈期望一個包含轉換函式、狀態值和迴圈變數初始值的元組。此元組可以直接包含在 in 關鍵字之後。
-- This will display the numbers from 1 to 10.
for value in seq, nil, 0 do
print(value)
end
但是,在大多數情況下,此元組將由一個函式返回。這允許使用迭代器工廠,它們在被呼叫時返回一個新的迭代器,該迭代器可以與泛型 for 迴圈一起使用。
function count_to_ten()
local function seq(state, value)
if (value >= 10) then
return nil
else
local new_value = value + 1
return new_value
end
end
return seq, nil, 0
end
for value in count_to_ten() do
print(value)
end
由於 Lua 支援閉包和函式作為一等公民,因此迭代器工廠也可以接受引數,這些引數可以在轉換函式中使用。
function count_to(limit)
local function seq(state, value)
if (value >= limit) then
return nil
else
local new_value = value + 1
return new_value
end
end
return seq, nil, 0
end
for value in count_to(10) do -- Print the numbers from 1 to 10
print(value)
end
for value in count_to(20) do -- Print the numbers from 1 to 20
print(value)
end
標準庫
[edit | edit source]Lua 是一種被稱為“沒有提供電池”的語言。這意味著它的庫保持在執行某些任務所需的最低限度。Lua 依賴於其社群來建立可用於執行更特定任務的庫。Lua 參考手冊提供了所有庫[1]的文件,因此這裡將對其進行簡要說明。除基本庫和包庫之外的所有庫都將其函式和值作為表格的欄位提供。
基本庫
[edit | edit source]基本庫為 Lua 提供核心功能。它的所有函式和值都直接在全域性環境中可用,並且預設情況下,全域性環境中直接可用的所有函式和值都是基本庫的一部分。
斷言
[edit | edit source]斷言是一種謂詞,開發人員假設它是正確的。它們在程式中用於確保在程式執行的特定時刻特定條件為真。斷言用於單元測試中驗證程式是否正常工作,但也用於程式程式碼中,在這種情況下,當斷言為假時,程式將失敗,要麼為了驗證程式所在的執行環境是否正確,要麼為了驗證程式程式碼中沒有錯誤,並生成適當的錯誤訊息,以便在某些事情沒有按預期發生時更容易找到程式碼中的錯誤。在 Lua 中,斷言是透過 assert 函式進行的,該函式接受一個條件和一個訊息(預設值為“斷言失敗!”)作為引數。當條件評估為假時,assert 會丟擲一個包含訊息的錯誤。當它評估為真時,assert 將返回所有引數。
垃圾收集
[edit | edit source]垃圾收集是 Lua 和許多其他語言實現的一種自動記憶體管理形式。當程式需要將資料儲存在變數中時,它會要求作業系統在計算機的記憶體中分配空間來儲存變數的值。然後,當它不再需要空間時(通常是因為變數超出了作用域),它會告訴作業系統釋放空間,以便其他程式可以使用它。在 Lua 中,實際過程要複雜得多,但基本思想是一樣的:程式必須告訴作業系統它不再需要某個變數的值。在低階語言中,分配由語言處理,但釋放則不是,因為語言無法知道程式設計師何時不再需要某個值:即使引用該值的變數超出了作用域或被刪除,另一個變數或指令碼中的欄位可能仍然引用它,而釋放它會導致問題。在高階語言中,釋放可能由各種自動記憶體管理系統處理,例如垃圾收集,它是 Lua 使用的系統。垃圾收集器會定期搜尋 Lua 分配的所有值,以查詢任何地方都沒有引用的值。它將收集程式無法訪問的任何值(因為沒有引用它們),並且由於它知道這些值可以安全地釋放,因此它將釋放它們。所有這些都以透明且自動的方式完成,因此程式設計師通常不需要為此做任何事情。但是,有時開發人員可能希望向垃圾收集器發出指令。
弱引用
[edit | edit source]弱引用是指垃圾回收器會忽略的引用。這些引用由開發人員使用 `mode` 元方法指示給垃圾回收器。表的 `mode` 元方法應該是一個字串。如果該字串包含字母 "k",則該表的所有欄位鍵都是弱引用,如果包含字母 "v",則該表的所有欄位值都是弱引用。當物件陣列具有弱引用值時,即使這些物件在該陣列中被引用,只要它們只在該陣列和其它弱引用中被引用,垃圾回收器就會收集這些物件。這種行為有時很有用,但很少使用。
可以使用 `collectgarbage` 函式操作垃圾回收器,該函式是基本庫的一部分,用作垃圾回收器的介面。它的第一個引數是一個字串,指示垃圾回收器應該執行什麼操作;第二個引數由某些操作使用。`collectgarbage` 函式可用於停止垃圾回收器,手動執行收集週期並統計 Lua 使用的記憶體。
—維基百科, 協程
協程是可以透過 Lua 中的協程庫建立和操作的元件,允許透過呼叫從自身內部產生協程的函式或從自身外部恢復協程的函式,在特定位置產生和恢復函式的執行。例如
- 主執行緒中的一個函式使用 `coroutine.create` 從一個函式建立一個協程,並使用 `coroutine.resume` 恢復它,並將數字 3 傳遞給它。
- 協程中的函式執行並獲取傳遞給 `coroutine.resume` 作為引數的數字。
- 函式在其執行過程中的某個點呼叫 `coroutine.yield`,並將它接收到的引數(3)與 2 的總和(因此為 3+2=5)作為引數傳遞。
- 對 `coroutine.resume` 的呼叫返回 5,因為它被傳遞給了 `coroutine.yield`,並且現在再次執行的主執行緒將該數字儲存在一個變數中。在執行了一些程式碼之後,它再次恢復協程,並將 `coroutine.resume` 的呼叫結果(即 5×2=10)的雙倍傳遞給 `coroutine.resume`。
- 協程將傳遞給 `coroutine.resume` 的值作為對 `coroutine.yield` 的呼叫的結果,並在執行更多程式碼後終止。它返回對 `coroutine.yield` 的呼叫的結果與其最初作為引數傳遞給它的值之間的差值(即 10−3=7)。
- 主執行緒獲取協程作為對 `coroutine.resume` 的呼叫的結果返回的值,然後繼續執行。
這個示例用程式碼表示如下
local co = coroutine.create(function(initial_value)
local value_obtained = coroutine.yield(initial_value + 2) -- 3+2=5
return value_obtained - initial_value -- 10-3=7
end)
local _, initial_result = coroutine.resume(co, 3) -- initial_result: 5
local _, final_result = coroutine.resume(co, initial_result * 2) -- 5*2=10
print(final_result) --> 7
`coroutine.create` 函式從一個函式建立一個協程;協程是型別為 "thread" 的值。`coroutine.resume` 啟動或繼續執行協程。當協程遇到錯誤或沒有剩餘要執行的內容(在這種情況下它已經終止了執行)時,協程被稱為死亡。死亡的協程無法恢復。如果協程的執行成功,`coroutine.resume` 函式將返回 `true` 以及所有返回值(如果協程已終止)或傳遞給 `coroutine.yield` 的值(如果尚未終止)。如果執行不成功,它將返回 `false` 以及錯誤訊息。`coroutine.resume` 返回執行的協程和 `true`(當該協程為主執行緒時),否則返回 `false`。
`coroutine.status` 函式以字串形式返回協程的狀態
- "running" 如果協程正在執行,這意味著它必須是呼叫 `coroutine.status` 的協程
- "suspended" 如果協程掛起在對 `yield` 的呼叫中,或者它尚未開始執行
- "normal" 如果協程處於活動狀態但未執行,這意味著它已恢復另一個協程
- "dead" 如果協程已完成執行或遇到錯誤
`coroutine.wrap` 函式返回一個函式,該函式在每次呼叫時都會恢復協程。傳遞給該函式的額外引數將作為 `coroutine.resume` 的額外引數,協程返回的值或傳遞給 `coroutine.yield` 的值將由對該函式的呼叫返回。`coroutine.wrap` 函式與 `coroutine.resume` 不同,它不會在保護模式下呼叫協程,而是會傳播錯誤。
協程有很多用例,但描述它們超出了本書的範圍。
在操作字串時,能夠在字串中搜索符合特定模式的子字串通常很有用。Lua 具有一個字串操作庫,該庫提供了用於執行此操作的函式和用於表達這些函式可以在字串中搜索的模式的符號。Lua 提供的符號與正則表示式非常相似,正則表示式是大多數程式語言和工具使用的用於表達模式的符號。但是,它更加有限,語法略有不同。
字串庫的 `find` 函式在字串中查詢模式的第一個匹配項。如果它在字串中找到了模式的出現,它將返回字串中模式出現的位置的索引(整數,表示字串中字元的位置,從第一個字元開始,位置為 1)。如果它沒有找到模式的出現,它將不返回任何內容。它接受的第一個引數是字串,第二個引數是模式,第三個引數是一個整數,表示 `find` 函式應該開始搜尋的字元位置。最後,可以透過向 `find` 函式提供 `true` 作為其第四個引數來告訴它執行簡單的匹配而不需要模式。然後它將簡單地搜尋在第一個字串中找到的第二個字串的出現。當執行簡單匹配時,必須給出第三個引數。此示例程式碼在句子中搜索 "lazy" 一詞,並列印它找到的該詞的出現位置的開始和結束位置
local start_position, end_position = string.find("The quick brown fox jumps over the lazy dog.", "lazy", 1, true)
print("The word \"lazy\" was found starting at position " .. start_position .. " and ending at position " .. end_position .. ".")
此程式碼的結果為 The word "lazy" was found starting at position 36 and ending at position 39.。它等同於以下程式碼
local sentence = "The quick brown fox jumps over the lazy dog."
local start_position, end_position = sentence:find("lazy", 1, true)
print("The word \"lazy\" was found starting at position " .. start_position .. " and ending at position " .. end_position .. ".")
這是有效的,因為字串的 `index` 元方法被設定為包含字串庫函式的表,從而可以將 `string.a(b, ...) ` 替換為 `b:a(...)`。
字串庫中接受索引來指示字元位置或返回此類索引的函式將第一個字元視為位置 1。它們接受負數並將它們解釋為從字串末尾開始反向索引,最後一個字元位於位置 -1。
模式是遵循特定符號的字串,用於指示字串是否可以匹配的模式。為此,模式包含字元類,這些字元類代表字元集。
| 字元組合 | 描述 |
|---|---|
| . | 所有字元 |
| %a | 字母(大寫和小寫) |
| %c | 控制字元 |
| %d | 數字 |
| %g | 可列印字元(空格字元除外) |
| %l | 小寫字母 |
| %p | 標點符號 |
| %s | 空格字元 |
| %u | 大寫字母 |
| %w | 字母數字字元(數字和字母) |
| %x | 十六進位制數字 |
所有不是特殊字元的字元都代表自身,特殊字元(所有不是字母數字的字元)可以透過在前面加上百分號來轉義。可以透過將字元類放在集合中來組合字元類以建立更大的字元類。集合表示為方括號之間表示的字元類(例如 `[%xp]` 是所有十六進位制字元加上字母 "p" 的集合)。字元範圍可以透過用連字元(例如 `[0-9%s]` 表示從 0 到 9 的所有數字加空格字元)分隔範圍的結束字元(按升序)來表示。如果在集合的開頭(緊接在開方括號之後)放置插入符號 ("^") 字元,則該集合將包含所有字元,除了它在插入符號未放置在集合的開頭時會包含的字元。
可以透過百分號後跟相應的字母大寫來表示由百分號字首的字母表示的所有類的補集(例如 `%S` 表示所有非空格字元)。
模式是代表應在模式中找到哪些序列才能使字串與之匹配的模式項序列。模式項可以是一個字元類,在這種情況下它將匹配該類中的任何字元一次,一個字元類後跟 "*" 字元,在這種情況下它將匹配該類中字元的 0 次或多次重複(這些重複項將始終匹配最長的可能序列),一個字元類後跟加號 ("+") 字元,在這種情況下它將匹配該類中字元的 1 次或多次重複(這些重複項也將始終匹配最長的可能序列),一個字元類後跟減號 ("-") 字元,在這種情況下它將匹配該類中字元的 0 次或多次重複,但將匹配最短的可能序列,或者一個字元類後跟一個問號,在這種情況下它將匹配該類中字元的一次或零次出現。
可以匹配與之前捕獲的子字串等效的子字串:%1 將匹配第一個捕獲的子字串,%2 匹配第二個,依此類推,直到 %9。捕獲在下面描述。模式還提供其他兩種功能,如參考手冊所述。
%bxy,其中 x 和 y 是兩個不同的字元;此項匹配以 x 開頭、以 y 結尾的字串,並且 x 和 y 是平衡的。這意味著,如果從左到右讀取字串,對 x 計數 +1,對 y 計數 -1,則結束符 y 是計數達到 0 的第一個 y。例如,項%b()匹配具有平衡括號的表示式。
%f[set],一個邊界模式;此項在任何位置匹配空字串,使得下一個字元屬於集合而前一個字元不屬於集合。集合 set 的解釋如前所述。主題的開頭和結尾被視為字元 '\0'。——Lua 作者, Lua 5.2 參考手冊
模式是模式項的序列,可選地以插入符號開頭,表示模式只能在字串開頭匹配,可選地以美元符號結尾,表示模式只能在字串結尾匹配。這些符號被稱為錨定在字串的開頭或結尾匹配。這兩個字元只有在模式的開頭或結尾時才具有特殊含義。
子模式可以包含在模式中的括號內,以指示捕獲。當匹配成功時,與捕獲匹配的字串的子字串將被儲存以供將來使用,例如由 gmatch 返回。它們總是從其左括號的位置開始編號。兩個空括號表示空捕獲,它捕獲當前字串位置(這是一個數字,不是字串的一部分)。
gmatch 函式可用於遍歷字串中模式的出現次數;與 find 函式不同,它不能指定要開始搜尋的初始位置或執行簡單匹配。gmatch 函式返回一個迭代器,當呼叫時,它將從字串中的給定模式返回下一個捕獲。如果模式中沒有指定捕獲,則將給出整個匹配項。以下示例展示瞭如何遍歷句子中的單詞並將它們逐個打印出來
local sentence = "The quick brown fox jumps over the lazy dog."
for word in sentence:gmatch('%a+') do
print(word)
end
在這個示例中,整個匹配項由迭代器返回的唯一值 word 給出。
gsub 函式可用於將字串中模式的所有出現次數替換為其他內容。其前兩個引數是字串和模式,第三個引數是要替換出現的字串,第四個引數是要替換的出現次數的最大值。第三個引數可以是表或函式,而不是字串。
當第三個引數是字串時,它被稱為替換字串,它將替換字串中模式的出現次數。模式儲存的捕獲可以嵌入到替換字串中;它們由百分號後跟表示捕獲編號的數字來表示。匹配本身可以用 %0 表示。替換字串中的百分號必須轉義為 %%。
當第三個引數是表時,第一個捕獲將用作鍵來索引該表,替換字串是該表中對應於該鍵的值。當它是函式時,該函式將為每個匹配項呼叫,並將所有捕獲作為引數傳遞。在這兩種情況下,如果不存在捕獲,則使用整個匹配項。如果函式或表給出值 false 或 nil,則不會進行替換。
以下是一些直接摘自 Lua 5.2 參考手冊的示例
x = string.gsub("hello world", "(%w+)", "%1 %1") --> x="hello hello world world" x = string.gsub("hello world", "%w+", "%0 %0", 1) --> x="hello hello world" x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") --> x="world hello Lua from" x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv) --> x="home = /home/roberto, user = roberto" x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) return load(s)() end) --> x="4+5 = 9" local t = {name="lua", version="5.2"} x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) --> x="lua-5.2.tar.gz"——Lua 作者, Lua 5.2 參考手冊
Lua 提供了其他用於操作字串的函式,而不是用於模式匹配的函式。這些包括 reverse 函式,它返回一個字元順序顛倒的字串,lower 函式,它返回字串的小寫等效項,upper 函式,它返回字串的大寫等效項,len 函式,它返回字串的長度,以及 sub 函式,它返回字串的子字串,該子字串從給定為引數的兩個字元位置開始並結束。還有更多,它們的文件可以在參考手冊中找到。
- ↑ Ierusalimschy, Roberto; Celes, Waldemar; Henrique de Figueiredo, Luiz. Lua 5.2 參考手冊. Retrieved 30 November 2013.
術語表
[edit | edit source]這是一個包含與 Lua 中的程式設計相關的術語的術語表。建議使用它來查詢不理解的單詞的含義。
- 抽象類
- 一個抽象類是一個不能直接建立例項的類。抽象類是抽象型別。
- 抽象資料型別
- 一個抽象資料型別是一個模型,用於表示具有類似行為的一類資料結構。抽象資料型別由可以在其上執行的操作以及這些操作的數學約束來定義,而不是由實現和資料在計算機記憶體中的儲存方式來定義。
- 抽象型別
- 一個抽象型別是一種資料型別,不能直接建立其例項。
- 實際引數
- 見引數。
- 加法逆元
- 一個數字的加法逆元是當加到該數字上時會產生零的數字。例如,42 的加法逆元是 -42。
- 算術否定
- 算術否定是產生一個數字的加法逆元的操作。
- 算術運算
- 一個算術運算是一個操作,其運算元是數字。
- 元數
- 一個操作或函式的元數是操作或函式接受的運算元或引數的個數。
- 引數
- 一個引數是傳遞給函式的值。
- 陣列
- 一個陣列是一個資料結構,它由一組值組成,每個值至少由一個數組索引或鍵標識。
- 關聯陣列
- 一個關聯陣列是一個抽象資料型別,它由一組鍵值對組成,使得每個可能的鍵在集合中最多出現一次。
- 增強賦值
- 增強賦值是一種賦值型別,它給變數賦予一個相對於其先前值的價值。
- 二元運算
- 一個二元運算是一個元數為 2 的運算。
- 布林值
- 見邏輯資料。
- 布林否定
- 見邏輯否定。
- 鏈式賦值
- 鏈式賦值是一種賦值型別,它將單個值賦予多個變數。例如:
a = b = c = d = 0。 - 程式碼塊
- 程式碼塊是一系列語句。
- 複合賦值
- 見增強賦值。
- 串聯
- 字串串聯是連線兩個字元字串的操作。例如,"snow" 和 "ball" 的串聯是 "snowball"。
- 具體類
- 一個具體類是一個可以建立其例項的類。具體類是具體型別。
- 具體型別
- 一個具體型別是一種可以建立其例項的型別。
- 條件
- 一個條件是一個謂詞,它用在條件語句中或作為條件運算子的運算元。在 Lua 中,條件在它們的表示式計算出除
nil或false之外的值時被視為真,否則被視為假。 - 條件運算子
- 一個條件運算子是一個運算子,它在條件為真時返回一個值,而在條件為假時返回另一個值。
- 條件語句
- 一個條件語句是一個語句,它在條件為真時執行一段程式碼。
- 資料結構
- 一個資料結構是在計算機記憶體中儲存和組織資料的特定方式。它是抽象資料型別的實現。
- 資料型別
- 資料型別是用來描述計算機記憶體中資料儲存方式的模型。
- 字典
- 參見 關聯陣列。
- 異或
異或運算是一種二元運算,當其中一個運算元為真而另一個運算元為假時,它產生值為真。 a和 b的異或在數學上表示為 。 Lua中沒有與異或對應的運算子,但 可以用
韋恩圖 of (a or b) and not (a and b)表示。- 形式引數
- 參見 引數。
- 函式
- 函式是一系列執行特定任務的語句(指令)。函式可以在程式中需要執行該特定任務的任何地方使用。函式通常在將要使用它們的程式中定義,但也有些函式是在庫中定義的,其他程式可以使用這些庫。
- 雜湊表
- 參見 雜湊表。
- 雜湊表
- 雜湊表是作為 資料結構 實現的 關聯陣列。雜湊表使用 雜湊函式 來計算一個數組(桶或槽)中的索引,然後根據這個索引找到對應的值。
- 內聯 if
- 參見 條件運算子。
- 整數
- 整數是可以不帶分數或小數部分而寫出的數字。在 Lua 中,整數的實現方式與其他數字相同。
- 長度運算
- 長度運算是一種運算,用來計算陣列中值的個數。
- 字面量
- 字面量是在原始碼中表示固定值的一種表示法。除執行緒和使用者資料外,所有值都可以在 Lua 中用字面量表示。
- 邏輯非
- 布林值的邏輯非是與該值不同的布林值。這意味著 `true` 的邏輯非是 `false`,反之亦然。
- 邏輯與
邏輯與運算是一種二元運算,當兩個運算元都為真時,它產生值為真,在其他所有情況下,它產生值為假。 它在 Lua 中實現為 `and` 運算子,如果第一個運算元為 `false` 或 `nil`,則返回第一個運算元,否則返回第二個運算元。 a 和 b 的邏輯與在數學上表示為 。
韋恩圖 of - 邏輯資料
- 邏輯資料型別,通常稱為布林型別,是 `false` 和 `true` 這些值的資料型別。
- 邏輯或
邏輯或運算是一種二元運算,當兩個運算元都為假時,它產生值為假,在其他所有情況下,它產生值為真。 它在 Lua 中實現為 `or` 運算子,如果第一個運算元既不是 `false` 也不是 `nil`,則返回第一個運算元,否則返回第二個運算元。 a 和 b 的邏輯或在數學上表示為 。
韋恩圖 of - 邏輯否
- 邏輯否,在 Lua 中由 `not` 運算子實現,是產生布爾值 邏輯非 的運算。
- 對映
- 參見 關聯陣列。
- 方法
- 方法是物件的一個成員函式,通常對該物件進行操作。
- 模
- 參見 模運算。
- 模運算
- 模運算,在 Lua 中由 `%` 運算子實現,是產生一個數字被另一個數字除後的餘數的運算。
- 模數
- 參見 模運算。
- 多重賦值
- 參見 並行賦值。
- 空值
- 空值型別是 `nil` 值的型別,其主要特點是與任何其他值都不同;它通常表示沒有有用的值。
- 非運算子
- 見邏輯否定。
- 數字
- 數字型別表示實數 (雙精度浮點數)。 Lua 直譯器可以被構建為使用其他內部表示來表示數字,例如單精度浮點數或長整數。
- 運算子
- 運算子是一個標記,它從一個或多個運算元生成一個值。
- 並行賦值
- 並行賦值是一種同時將值賦給不同變數的賦值型別。
- 引數
- 引數是函式定義中的一個變數,函式呼叫中與它對應的引數將被賦給它。
- 謂詞
- 謂詞是一個表示式,它計算出一個邏輯資料。
- 過程
- 參見 函式。
- 關係運算符
- 關係運算符是用來比較值的運算子。
- 例程
- 參見 函式。
- 符號變更
- 參見 算術否定。
- 同時賦值
- 參見 並行賦值。
- 字串
- 字串型別表示字元陣列。 Lua 是 8 位安全的:字串可以包含任何 8 位字元,包括嵌入的零。
- 字串字面量
- 字串字面量是計算機程式原始碼中表示字串值的表示法。就語法而言,字串字面量是一個表示式,它計算出一個字串。
- 子程式
- 參見 函式。
- 子例程
- 參見 函式。
- 符號
- 參見 標記。
- 符號表
- 符號表是作為資料結構實現的關聯陣列。 它們通常用雜湊表實現。
- 標記
- 標記是資料的原子片段,例如人類語言中的詞語或程式語言中的關鍵字,在解析過程中可以推斷其含義。
- 變數
- 一個變數是與記憶體中某個位置關聯的標籤。 該位置的資料可以更改,變數將指向新資料。
- 可變引數函式
- 一個可變引數函式是具有不定元數的函式。
這是一個按字母順序排列的書籍索引。
Lua API 中的函式和變數有一個單獨的索引。該索引指向書中提到 API 中函式或變數的部分。
- assert
- collectgarbage
- dofile
- error
- getmetatable
- ipairs
- load
- loadfile
- next
- pairs
- pcall
- rawequal
- rawget
- rawlen
- rawset
- select
- setmetatable
- tonumber
- tostring
- type
- xpcall
- string.dump
- string.find
- string.gmatch
- string.gsub
- string.len
- string.lower
- string.reverse
- string.sub
- string.upper


