lua學習之複習彙總篇
阿新 • • 發佈:2020-02-19
第六日筆記
1. 基礎概念
程式塊
定義
- 在 lua 中任何一個原始碼檔案或在互動模式中輸入的一行程式碼
- 程式塊可以是任意大小的
- 程式塊可以是一連串語句或一條命令
- 也可由函式定義構成,一般將函式定義寫在檔案中,然後用直譯器執行這個檔案
- 換行在程式碼中不起任何作用,只是為了提升可讀性
- 分隔符 ; 起分隔作用
a = a * 2
b = a * b
a = a * 2;
b = a * b
a = a * b; b = a * b
a = a * b b = a * b
互動模式
在互動模式中輸入的一行內容會被直譯器當作一個完整的程式塊,如果這一行的內容不足以構成一個完整的程式塊,就會等待輸入
退出互動模式
Ctrl + Z
是 end-of-file 控制字元,在 dos 中是這個快捷鍵os.exit()
標準庫中的退出函式
區域設定
- lua 中識別什麼是字母是通過區域設定來判別的
- 如設定希臘,就可以識別希臘字母作為變數
- 但在不支援該區域的系統上無法執行
執行函式檔案
- lua 函式檔案路徑
dofile("檔案路徑 / 需要轉義")
載入函式庫
-- 階乘函式 function fact(n) if n == 0 then return 1 --0 的階乘是 1 else return n * fact(n - 1) -- 3 的階乘, 3 * 2 * 1 end end print("Enter a number:") a = io.read("*number") -- 讀取使用者輸入且需為數字型別的 print(fact(a)) --呼叫階乘函式,並傳入實參 a -- lib1 函式庫 function norm(x, y) return (x ^ 2 + y ^ 2) ^ 0.5 -- 兩個數的平方和再開平方根 end function twice(x) return 2 * x -- 一個數的兩倍 end
識別符號
定義
- 由任意數字、字母、下劃線構成的字串叫做識別符號
- 識別符號不能由數字開頭
- 識別符號不能以下劃線開頭後跟多個大寫字母
- 如: _PROMPT, _VERSION
- lua 將它們保留用作特殊用途,被稱為啞變數
_PROMPT = ">lua" -- 修改互動模式中的提示符,預設為 >
保留字
流程控制
if
then
elseif
end
for
do
in
while
repeat
until
if 條件表示式 then elseif 條件表示式 then end for 控制變數, 終止變數, 步長 do <迴圈體> end a = {} for i,v in ipairs(a) do <迴圈體> end while i < 10 do i = i + 1 print(i) end repeat i = 0 i = i + 1 until i > 10
條件控制
true
false
邏輯控制
and
or
not
型別
function
local
nil
需要注意的點
nil == nil
是相等的and
和And
不同,lua 區分大小寫- lua 中條件值不僅僅只有
true
和false
- 在 lua 中任何值除了
false
和nil
都可以用作表示「真」 - 包括空字串
""
和數字0
註釋
- 單行註釋
--
- 多行註釋
--[[]]
- 使多行註釋中的程式碼生效
---[[ <程式碼塊> --]]
- 多行註釋中包含多行註釋
--[==[ <多行註釋> ]==]
全域性變數
- 全域性變數不需要宣告,只需要將一個值賦給它即可
- lua 中可以訪問一個未初始化的變數且不會發生錯誤
- 但這個未初始化的變數的值為
nil
- 刪除全域性變數賦值
nil
即可 - lua 將全域性變數儲存在一個普通的 table 中
直譯器
引數
-i
先執行程式塊,後進入互動模式-e
直接執行程式碼塊-l
載入庫檔案
直譯器執行引數前
- 會先尋找一個叫做
LUA_INIT
的環境變數 - 找到了,且內容為
@檔名
的話,就執行這個檔案 - 沒找到,就假設內容為 lua 程式碼, 並執行
直譯器執行指令碼前
- lua 將指令碼前的引數儲存到 arg 這個 table 中,用作啟動引數
- 指令碼名在這個 table 中的索引為 0,其後引數依此類推
- 指令碼名前的引數為負數索引
lua -i -e "hello" script a b
arg[0] = "script"
arg[1] = "a"
arg[-1] = "hello"
arg[-2] = "-e"
arg[-3] = "-i"
- 在 lua 中也可以通過變長引數語法來檢索指令碼引數
- 變長引數為
...
三個點,作為函式引數傳遞時表示傳遞所有引數
2. 型別與值
- lua 是動態型別語言
- 每個值都攜帶有它的型別資訊
獲取值的型別
type()
可以返回一個值的型別名稱type()
的返回結果永遠是string
型別的
print(type(3)) -- number
print(type("a")) -- string
print(type({"a", "b", "c"})) -- table
print(type(io.read)) -- function
print(type(true)) -- boolean
number
- 實數,即雙精度浮點數
- 可使用科學計數法,如
2e2
表示 200 - 可重新編譯 lua,使用其他型別的值來表示數字型別,如
long
tonumber()
用於將一個字串顯式的轉換為數字型別
boolean
在 lua 中,有兩個布林值一個是
true
表示為「真」,一個是false
表示為「假」但,這兩個值不是用來表示條件的唯一值,在 lua 中 除
nil
和false
外的任何值,都可以用來表示「真」, 包括空字串
""
和數字0
nil
- 只有一個值,
nil
- 僅用來表示為空,表示未初始化的變數或 table 元素
- 也可用來刪除變數或 table 元素
string
- 是物件,由自動記憶體回收器進行分配和釋放
- 是字元序列,是8位編碼
- 可以包含數值編碼,如二進位制
- lua 中的字串是唯一不可變的值
..
字串連線符,用於連線兩個字串,但數字型別使用時需要用空格隔開#
長度操作符,後跟字串,可以獲取字串長度[[]]
在期內的特殊字元不需要轉義[==[ <多行註釋> ]==]
可以正確列印多行註釋的內容"3" + 4
這樣的值會是number
型別,發生了執行時隱式轉換
print("\97" == "a") -- 在 ASCII 編碼表中,\97 表示為 a
print(type(3 .. "")) -- string
print(3..4) --報錯
print(3 .. 4) -- 34
print(#"hello") -- 5
-- 獲取子串,證明字串是不可變的值
a = "hello"
b = a .. " ,world"
print(a) -- hello
print(b) -- hello, world
a = [[
<html>
<head><title>蕪湖</title></head>
<body></body>
</html>
]]
a = [==[
--[[
print("多行註釋")
print("多行註釋")
]]
]==]
print(type("3" + 4)) -- number
顯式轉換為字串
tostring()
.. ""
任意數字連線一個空字串即可轉換為字串
table
是物件,由自動記憶體回收器進行分配和釋放
table 沒有固定大小,可以動態新增元素
{}
是 table 構造式,用來建立一個 table#
長度操作符可以獲取 table 的大小table 中的元素在未被初始化前都是
nil
可以將 table 中的元素賦值
nil
來進行刪除如果 table 中間部分的元素值為
nil
就說明這是一個有「空隙」的 table有「空隙」的 table 要使用
table.maxn()
來返回這個函式的最大正索引數table 可以用來表示模組、包、物件
table 中的索引可以是任何值,除了
nil
table 是匿名的
程式僅保留了對 table 的一個引用
一個僅有 table 的變數和 table 自身並沒有關係
a.x
等價於a["x"]
是以字串為索引的a[x]
是以變數x
為索引的
a = {}
for i = 1, 10 do
a[i] = i
print(a[i])
end
for i = 1, #a do
print(a[i])
end
print(a[10]) -- 10
print(#a) -- 10
a[10] = nil
print(#a) -- 9
a[10000] = 666
print(#a) -- 9
print(table.maxn(a)) -- 10000
a = {}
b = {}
c = a
print(type(a == b)) -- false
print(type(a == c)) -- true
x = "y"
a["x"] = 666
a["y"] = 777
print(a.x) --666
print(a[x]) -- 777
function
- 第一類值
- 可以儲存在變數中
- 可以作為函式的返回值或引數
- lua 可以呼叫自身編寫的函式也可以呼叫 C 語言編寫的函式
- lua 標準庫中的函式都是用 C 語言編寫的
userdata
- 由應用程式或 C 語言編寫建立的新型別
- 沒有太多的預定義操作
- 僅用來做賦值和條件測試
3. 表示式
定義
表示式用於表示值
在 lua 中,函式呼叫,函式定義,數字常量、字面字串,變數,一元和二元操作符,table 構造式都是表示式
算數操作符
一元操作符
-
負號
二元操作符
+
-
減號*
/
%
^
-- % 的技巧
-- x % 1
print(3.13 % 1) -- 得到小數部分
-- x - x % 1
print(3.14 - 3.14 % 1) -- 得到整數部分
-- x - x % 0.1
print(3.14 - 3.14 % 0.1) -- 得到整數部分 + 一位小數部分
-- x - x % 0.01 以此類推,是整數部分 + 兩位小數部分
關係操作符
>
<
>=
<=
==
相等性判斷~=
不等性判斷
邏輯操作符
and
第一個運算元為假,返回第一個,否則返回第二個or
第一個運算元為真,返回第一個,否則返回第二個not
只會返回true
或false
-- 短路操作的使用技巧
print(x = x or v) -- 初始化一個值,如果 x 為 nil 沒有被初始化過,就賦值 v
-- 等價於
if not x then
x = v
end
-- 實現 C 語言中的三元操作符, a ? b : c
print((a and b) or c) -- b 必須為真,才可以這樣操作
-- 等價於
if a == true then
return b
elseif a == false then
return c
end
-- 實現返回兩個數中的較大值
max = (x > y) and x or y -- 因為 lua 將數字視為「真」
-- 等價於
if x > y then
return x
else
return y
end
字串連線
..
字串連線
優先順序
1級優先
^
2級優先
-
負號not
#
3級優先
*
/
%
4級優先
+
-
減號
5級優先
..
字串連線
6級優先
>
<
>=
<=
==
~=
7級優先
and
8級優先
or
table 構造式
- lua 標準庫中的函式對 table 的索引都是從 1 開始處理的
記錄風格的 table
a = {x = 10, y = 20} -- 等價於 a.x = 10, a.y = 20
混合使用的 table
- 這種方式的 table 不能以負數和操作符作為索引
a = {
color = {"red", "green", "blue"}
width = 200,
height = 300
}
連結串列
- 由一系列節點組成,節點就是元素
- 節點可以再執行時動態生成
- 每個節點包括兩部分
- 儲存資料的資料域
- 儲存下一個地址節點的指標域
list = nil
for line in io.lines() do
list = {next = list, value = line}
end
local l = list
while l do
print(l.value)
l = l.next
end
指定索引的 table
options = {["+"] = "add", ["-"] = "sub",
["*"] = "mul", ["/"] = "div"}
print(options["+"]) -- "add"
4. 語句
- 在 lua 中包括賦值,程式結構和過程呼叫
- 還有多重賦值和區域性變數宣告
賦值
- lua 支援多重賦值,即
a, b = 1, 2
- 會先計算等號右邊所有元素的值,然後再賦值
- 如果右邊的值少於左邊變數,未被初始化的變數就置為
nil
- 如果左邊變數少於右邊的值,多餘的值會被「拋棄」
- 可用來收集函式的多個返回值
- 初始化變數就是為每一個變數賦一個初始值
a, b = 1, 2
x, y = y, x -- 交換變數
a, b = 1 -- a = 1, b = nil
a, b = 1, 2, 3 -- a = 1, b = 2, 3 被拋棄
a, b = f() -- a 接收函式 f 的第一個返回值,b 接收第二個
a, b, c = 0, 0, 0 -- 初始化賦值
區域性變數與塊
塊
- 一個塊就是程式結構的執行體,或函式的執行體
- 在塊內宣告的變數僅在塊內生效,即作用域為宣告它們的塊
- 可顯式宣告一個塊使用
do <要執行的內容> end
將要執行的內容包裹在一個塊內
區域性變數
local
用來宣告一個區域性變數- 區域性變數僅在宣告它的塊內生效,在塊的外部無效
- 如:在迴圈內部宣告在的變數在迴圈外部則無法使用
a = 3
b = 0
if a then
local a = 5
b = a -- 將 then 塊內的區域性變數 a ,儲存到全域性變數 b 中
print(a)
end
print(a) -- 3
print(b) -- 5
do
-- code block
end
儘量使用區域性變數
- 使用區域性變數要比全域性變數要快
- 避免汙染全域性環境
- 區域性變數僅在宣告它的塊中有效,即在塊外這個變數就被釋放掉了
- lua 將區域性變數聲明當作語句處理,即可以在任何支援書寫語句的地方書寫區域性變數宣告
- 宣告可以包含初始化賦值
程式結構
- 程式結構中的條件表示式可以是任何值
條件結構
if
elseif
else
if 條件表示式 then
<執行體> -- 符合條件表示式執行
end
if 條件表示式1 then
<執行體 1> -- 符合條件表示式 1 執行
elseif 條件表示式2 then
<執行體 2> -- 符合條件表示式 2 執行
end
if 條件表示式 then
<執行體 1> -- 條件表示式為真時執行
else
<執行體 2> -- 條件表示式為假是執行
end
迴圈結構
for
while
條件表示式為假時退出repeat ... until
條件表示式為真時推出,條件測試是在迴圈體之後做的,因此迴圈體至少會執行一次- 在迴圈體內宣告的區域性變數的作用域包含了條件測試
- 迴圈的三個表示式是在迴圈開始前一次性求值的
- 控制變數會被自動宣告為 for 塊中的區域性變數,即作用域為 for 塊,在迴圈結束後不可見
- 不要在迴圈過程中修改控制變數的值
- 可以使用
break
或return
在迴圈正常結束前提前結束它
for exp1, exp2, exp3 do
<迴圈體>
end
while 條件表示式 do
<迴圈體>
end
repeat
<迴圈體>
until 條件表示式
a = 20
repeat
local a = 0
print(a)
until a == 0 -- 可訪問在 repeat 塊內宣告的 a, 而不是全域性變數 a
數值型 for
for i = 10, 0, -1 do
print(i)
end
泛型 for
ipairs()
用來遍歷陣列i
每次迴圈時都會賦予一個新的索引值,v
則是索引值所對應的元素
a = {1, 2, 3, 4, 5, 6}
for i,v in ipairs(a) do
print(i)
print(v)
end
for i,v in pairs(a) do
print(i)
print(v)
end
兩種 for 的共同點
- 迴圈變數都是迴圈體的區域性變數
- 不應該對迴圈遍歷進行賦值
days = {"第一天", "第二天", "第三天"}
revdays = {}
for i, v in ipairs(days) do
revdays[v] = i -- 逆向陣列,將陣列索引和陣列元素調換,可獲取陣列元素的位置
end
print(revdays["第二天"]) -- 獲取第二天所在位置
break 和 return
break
用於結束一個迴圈,跳出內層迴圈後在外層迴圈中繼續執行return
用於返回函式結果或簡單的結束函式的執行- 任何函式的結尾處實際都有一句隱式的
return
- 如果函式無返回值,就無需在結尾處加
return
兩者的共同點
- 都是用作跳出當前塊
- 都需要放在一個塊的結尾處,即一個塊的最後一條語句
- 因為
return
或break
後的語句將無法執行到 - 可以用
do ... end
塊包裹return
,用與除錯,即呼叫函式但不執行函式內容的情況
a = 1
if a then
print("hello")
break
print("world") -- 會報錯
end
for i = 1, 10 do
print(i)
if i > 3 then
break -- 只會列印 1 2 3 4 然後就跳出迴圈了
end
end
-- 除錯
function foo(...)
do
return
end
print("執行 foo 函式") -- 不會列印
end
foo(1, 2 ,3)
5. 函式
- 函式是對語句和表示式進行抽象的主要機制
函式的兩種用法
- 一是可以完成特定的任務,一句函式呼叫被視為一條語句
- 二是隻用來計算並返回特定結果,視為一句表示式
print("hello") -- 用來完成列印任務,視為一條語句
a = os.date() -- os.date() 用來返回日期,視為一句表示式
兩種用法的共同點
- 都需要將所有引數放到一對圓括號中
()
- 但當引數為字面字串或 table 構造式的時候
()
可以省略 - 即使呼叫函式沒有引數,也必須要有一對空的圓括號
()
print "hello" -- hello
print {1, 2, 3} -- 1 2 3
print(os.date) -- 當前日期
定義
function
是建立函式的關鍵字function add
add 是函式的名稱function add(n)
n 是函式的形式引數,簡稱為形參add(4)
4 是呼叫add()
函式時的實際參 ,簡稱為實參- 實參多餘形參,多餘的實參被「拋棄」
- 形參多餘實參,多餘的形參被置為
nil
function foo(a, b)
return a or b
end
foo(1) -- a = 1, b = nil
foo(1, 2) -- a = 1, b = 2
foo(1, 2, 31) -- a = 1, b = 2, 多餘的 31 被拋棄
-- 面向物件式呼叫
o.foo(o, x)
o:foo(x) -- 與上面的效果一樣,: 冒號操作符,隱式的將 o 作為第一個引數
多重返回值
- lua 中的函式允許返回多個結果
- 用逗號分隔所需要返回的所有引數
string.find("you are cool", "are") -- 5 7 返回找到的字串的開頭位置和結尾位置
-- 查詢陣列中的最大元素,並返回這個元素的所在位置
function maximum(a)
local val = 1
local max = a[val]
for i,v in ipairs(a) do
if max < a[i] then
max = a[i]
val = i
end
end
return max, val
end
a = {1, 2, 55, 22, 29, 4}
maximum(a)
不同情況下的返回值
- 如果將函式作為單獨的語句執行,lua 會丟棄所有的返回值
- 如果將函式作為表示式的一部分呼叫,只保留函式的第一個返回值
- 只有當函式是一系列表示式中的最後一個元素(或只有一個元素的時候),才會獲取所有的返回值
一系列表示式的4種情況
多重賦值
- 如果一個函式呼叫是最後(或僅有)的一個表示式,lua 會保留儘可能多的返回值,用來匹配賦值的變數
- 如果一個函式沒有返回值或沒有返回足夠多的返回值,那麼 lua 會用
nil
來補充缺失的值 - 如果一個函式呼叫不是一系列表示式中的最後一個元素,就只能返回一個值
function foo() end
function foo1() return "a" end
function foo2() return "a", "b" end
-- 第一種情況,最後(或僅有)的一個表示式
x, y = foo1() -- x = a, y = b
-- 第二種情況,沒有返回值
x = foo() -- nil
-- 第二種情況,沒有返回足夠多的返回值
x, y, z = foo1() -- x = a, y = b, z = nil
-- 第三種情況,不是表示式中的最後一個元素
x, y = foo2(), 10 -- x = a, y = 10
函式呼叫時傳入的實參列表
- 如果一個函式呼叫作為另一個函式呼叫的最後一個(或僅有的)實參的時候,第一個函式的所有返回值都會作為實參傳遞給另一個函式
function foo() end
function foo1() return "a" end
function foo2() return "a", "b" end
-- 第四種情況,作為 print 函式中的最後一個(或僅有的)實參
print(foo()) -- nil
print(foo1()) -- "a"
print(foo2()) -- "a" "b"
print(foo1() .. "test") -- "atest"
print(foo2() .. "test") -- "atest"
table 構造式
- table 構造式會完整接收一個函式呼叫的所有結果,即不會由任何數量方面的調整
- 但這種行為,只有當一個函式呼叫作為最後一個元素時才發生
- 其他位置上的函式呼叫總是隻產生一個結果值
function foo() end
function foo1() return "a" end
function foo2() return "a", "b" end
-- 函式呼叫是 table 中的最後一個元素
a = {foo2()} -- a = {"a", "b"}
a = {foo2(), 10} -- a = {"a", 10}
return 語句
- 將函式呼叫放入一對圓括號 () 中,使其只返回一個結果
- return 語句後面的內容不需要 () 圓括號
- 如果強行加上則會使一個多返回值的函式,強制其只返回一個 return(f())
function foo0() end
function foo1() return "a" end
function foo2() return "a", "b" end
function foo(i)
if i == 0 then return foo0()
elseif i == 1 then return foo1()
elseif i == 2 then return foo2()
end
end
print(foo(1)) -- a
print(foo(2)) -- a, b
print(foo(0)) -- 無返回值,在互動模式中會是一個空行
-- () 包裹
print((foo(1)) -- a
print((foo(2)) -- a
print((foo(0)) -- nil ,應該是強制返回了一個未初始化的值,因為 foo0() 沒有返回值
unpack 函式
- 接收一個數組作為引數
- 並從下標 1 開始返回該陣列的所有元素
- 這個預定義函式由 C 語言編寫
print(unpack{10, 20, 30}) -- 10 20 30
a, b = unpack{10, 20, 30} -- a = 10, b = 20
- 用於泛型呼叫
- 泛型呼叫就是可以以任何實參來呼叫任何函式
-- 呼叫任意函式 f, 而所有的引數都在陣列 a 中
-- unpack 將返回 a 中的所有值,這些值作為 f 的實參
f(unpack(a))
f = string.find
a = {"hello", "ll"}
f(unpack(a)) -- 3 4 等效於 string.find("hello", "ll")
用 lua 遞迴實現 unpack
function unpack(t, i)
i = i or 1
if t[i] then
return t[i], unpack(t, i + 1)
end
end
變長引數
- lua 中的函式可以接收不同數量的實參
- 當這個函式被呼叫時,它的所有引數都會被收集到一起
- 這部分收集起來的實參稱為這個函式的「變長引數」
...
三個點表示該函式接收不同數量的實參- 一個函式要訪問它的變長引數時,需要用
...
三個點,此時...
三個點是作為一個表示式使用的 - 表示式
...
三個點的行為類似一個具有多重返回值的函式,它返回的是當前函式的所有變長引數 - 具有變長引數的函式也可以擁有任意數量的固定引數
- 但固定引數一定要在變長引數之前
- 當變長引數中包含 nil ,則需要用 select 訪問變長引數
- 呼叫
select
時,必須傳入一個固定引數selector
(選擇開關) 和一系列變長引數 - 如果
selector
為數字 n ,那麼select
返回它的第 n 個可變實參 - 否則,
select
只能為字串"#"
,這樣select
會返回變長引數的總數,包括nil
-- 返回所有引數的和
function add(...)
local s = 0
for i, v in ipairs{...} do -- 表示式{...}表示一個由變長引數構成的陣列
s = s + v
end
return s
end
print(add(3, 4, 5, 100)) -- 115
-- 除錯技巧 ,類似與直接呼叫函式 foo ,但在呼叫 foo 前先呼叫 print 列印其所有的實參
function foo1(...)
print("calling foo:", ...)
return foo(...)
end
-- 獲取函式的實參列表
function foo(a, b, c) end
function foo(...)
local a, b, c = ...
end
-- 格式化文字 string.format ,輸出文字 io.write
-- 固定引數一定要在變長引數之前
function fwrite(fmt, ...)
return io.write(string.format(fmt, ...))
end
fwrite() -- fmt = nil
fwrite("a") -- fmt = a
fwrite("%d%d", 4, 5) -- fmt = "%d%d" , 變長引數 = 4, 5
for i = 1, select('#', ...) do
local arg = select('#', ...)
<迴圈體>
end
具名引數
- lua 中的引數傳遞機制是具有 「位置性」的
- 就是說在呼叫一個函式時,實參是通過它在引數表中的位置與形參匹配起來的
- 第一個實參的值與第一個形參相匹配,依此類推
- 定義:通過名稱來指定實參
- 可將所有的實參組織到一個 table 中,並將這個 table 作為唯一的實參傳給函式
- lua 中特殊的函式呼叫語法,當實參只有一個 table 構造式時,函式呼叫中的圓括號
()
是可有可無的
os.rename -- 檔案改名,希望達到的效果 os.rename(old = "temp.lua", new = "temp1.lua")
-- lua 不支援註釋的寫法
rename = {old = "temp.lua", new = "temp1.lua"}
function rename (arg)
return os.rename(arg.old, arg.new)
end
x = Window{x = 0, y = 0, width = 300, height = 200, title = "Lua", background = "blue", border = "true"}
-- Window 函式根據要求檢查必填引數,或為某些函式新增預設值
-- 假設 _Window 是真正用於建立新視窗的函式,要求所有引數以正確次序傳入
function Window(options)
if type(options.title) ~= "string" then
error("no title")
elseif type(options.width) ~= "number" then
error("no width")
elseif type(options.height) ~= "height" then
error("no height")
end
_Window(options.title,
options.x or 0 -- 預設值
options.y or 0 -- 預設值
options.width, options.height,
options.background or "white" -- 預設值
options.border -- 預設值為 false(nil)
)
end
因為,目前只學到第五章函式篇,所以只有前五章的複習彙總,很基礎,也很重要,也祝願大家可以踏踏實實地打好地基