跳轉到內容

Lua 程式設計/語句

來自華夏公益教科書,開放的書籍,開放的世界

語句 是可以執行的程式碼片段,包含指令和用於該指令的表示式。一些語句還包含自身內部的程式碼,例如,可能在特定條件下執行。與表示式不同,它們可以直接放入程式碼中,並將執行。Lua 只有少數指令,但這些指令結合其他指令和複雜的表示式,為使用者提供了大量的控制和靈活性。

程式設計師經常需要能夠將值儲存在記憶體中以便稍後使用。這是使用變數完成的。 變數 是對儲存在計算機記憶體中的值的引用。它們可以用來在將數字儲存在記憶體中之後訪問它。 賦值 是用於將值賦給變數的指令。它包括要儲存值的變數的名稱、一個等號以及要儲存在變數中的值。

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 中也稱為名稱。它們可以是任何由字母、數字和下劃線組成的文字,並且不能以數字開頭。它們用於命名變數和表字段,這些將在關於表的章節中介紹。

以下是一些有效的名稱

  • name
  • hello
  • _
  • _tomatoes
  • me41
  • __
  • _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 區分大小寫。這意味著 Hellohello 是兩個不同的名稱。

作用域

[編輯 | 編輯原始碼]

變數的作用域 是程式程式碼中該變數有意義的區域。您之前見過的變數示例都是全域性變數的示例,這些變數可以從程式中的任何地方訪問。另一方面,區域性變數只能從定義它們的程式區域以及位於該程式區域內部的程式區域使用。它們與全域性變數的建立方式完全相同,但必須以 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,已知存在於CJavaScriptRubyPython中,但在Lua中不存在,這意味著需要編寫a = a + 8

鏈式賦值

[編輯 | 編輯原始碼]

鏈式賦值是一種賦值型別,它為多個變數賦予單個值。例如,程式碼a = b = c = d = 0將在C和Python中將abcd的值設定為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迴圈。此類迴圈將執行程式碼,然後檢查條件是否為真。如果為真,則再次執行程式碼,並重復執行,直到條件為假。當條件為假時,它們停止重複程式碼,程式流程繼續。每次程式碼執行稱為一次迭代。whilerepeat迴圈之間的區別在於,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。因此,下面的程式碼將列印 11.11.21.31.41.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),但它適用於 >= 運算子。

程式碼塊

[edit | edit source]

程式碼塊是一系列按順序執行的語句。這些語句可以包含空語句,不包含任何指令。空語句可以用來用分號開始一個程式碼塊,或者在序列中寫兩個分號。

函式呼叫和賦值可以以括號開頭,這會導致歧義。這個片段就是一個例子

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

程式碼塊

[edit | edit source]

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 不在受保護模式下執行,因此透過它執行的程式碼塊中的所有錯誤都會傳播。

  1. http://codepad.org/kYHPSvqx
華夏公益教科書