第六日筆記
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 函數文件路徑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>
標識符
定義
<code>_PROMPT = ">lua" -- 修改交互模式中的提示符,默認為 >/<code>
保留字
流程控制
ifthenelseifendfordoinwhilerepeatuntil<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>
條件控制
truefalse邏輯控制
andornot類型
functionlocalnil需要注意的點
nil == nil 是相等的and 和 And 不同,lua 區分大小寫lua 中條件值不僅僅只有 true 和 false在 lua 中任何值除了 false 和 nil 都可以用作表示「真」包括空字符串 "" 和數字 0註釋
全局變量
解釋器
參數
-i 先執行程序塊,後進入交互模式-e 直接執行代碼塊-l 加載庫文件解釋器執行參數前
解釋器運行腳本前
lua 將腳本前的參數存儲到 arg 這個 table 中,用作啟動參數腳本名在這個 table 中的索引為 0,其後參數依此類推腳本名前的參數為負數索引
<code>lua -i -e "hello">
2. 類型與值
獲取值的類型
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
實數,即雙精度浮點數boolean
在 lua 中,有兩個布爾值一個是 true 表示為「真」,一個是 false 表示為「假」但,這兩個值不是用來表示條件的唯一值,在 lua 中 除 nil 和 false 外的任何值,都可以用來表示「真」, 包括空字符串 "" 和數字 0nil
只有一個值,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 中的索引可以是任何值,除了 niltable 是匿名的程序僅保留了對 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
userdata
3. 表達式
定義
算數操作符
一元操作符
- 負號二元操作符
+- 減號*/%^<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級優先
and8級優先
ortable 構造式
記錄風格的 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 支持多重賦值,即 a, b = 1, 2會先計算等號右邊所有元素的值,然後再賦值如果<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 將局部變量聲明當作語句處理,即可以在任何支持書寫語句的地方書寫局部變量聲明聲明可以包含初始化賦值程序結構
條件結構
ifelseifelse<code>if 條件表達式 then -- 符合條件表達式執行endif 條件表達式1 then -- 符合條件表達式 1 執行 elseif 條件表達式2 then -- 符合條件表達式 2 執行endif 條件表達式 then -- 條件表達式為真時執行else -- 條件表達式為假是執行end/<code>
循環結構
forwhile 條件表達式為假時退出repeat ... until 條件表達式為真時推出,條件測試是在循環體之後做的,因此循環體至少會執行一次在循環體內聲明的局部變量的作用域包含了條件測試循環的三個表達式是在<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
兩者的共同點
<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>
多重返回值
<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>
不同情況下的返回值
一系列表達式的4種情況
多重賦值
如果一個函數調用是最後(或僅有)的一個表達式,lua 會保留儘可能多的返回值,用來匹配賦值的變量如果一個函數<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>
具名參數
<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 塊方便大家閱讀,在此祝願大家可以做什麼事都能夠踏踏實實地打好地基。