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