第六日笔记
1. 基础概念
程序块
定义
- 在 lua 中任何一个源代码文件或在交互模式中输入的一行代码
- 程序块可以是任意大小的
- 程序块可以是一连串语句或一条命令
- 也可由函数定义构成,一般将函数定义写在文件中,然后用解释器执行这个文件
- 换行在代码中不起任何作用,只是为了提升可读性
- 分隔符 ; 起分隔作用
a = a * 2 b = a * ba = a * 2;b = a * ba = a * b; b = a * ba = 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 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
标识符
定义
- 由任意数字、字母、下划线构成的字符串叫做标识符
- 标识符不能由数字开头
- 标识符不能以下划线开头后跟多个大写字母
- 如: _PROMPT, _VERSION
- lua 将它们保留用作特殊用途,被称为哑变量
_PROMPT = ">lua" -- 修改交互模式中的提示符,默认为 >
保留字
流程控制
- if
- then
- elseif
- end
- for
- do
- in
- while
- repeat
- until
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
条件控制
- 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)) -- numberprint(type("a")) -- stringprint(type({"a", "b", "c"})) -- tableprint(type(io.read)) -- functionprint(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 表示为 aprint(type(3 .. "")) -- stringprint(3..4) --报错print(3 .. 4) -- 34print(#"hello") -- 5-- 获取子串,证明字符串是不可变的值a = "hello"b = a .. " ,world"print(a) -- helloprint(b) -- hello, worlda = [[ 芜湖 ]]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])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
function
- 第一类值
- 可以存储在变量中
- 可以作为函数的返回值或参数
- lua 可以调用自身编写的函数也可以调用 C 语言编写的函数
- lua 标准库中的函数都是用 C 语言编写的
userdata
- 由应用程序或 C 语言编写创建的新类型
- 没有太多的预定义操作
- 仅用来做赋值和条件测试
3. 表达式
定义
- 表达式用于表示值
- 在 lua 中,函数调用,函数定义,数字常量、字面字符串,变量,一元和二元操作符,table 构造式都是表达式
算数操作符
一元操作符
- - 负号
二元操作符
- +
- - 减号
- *
- /
- %
- ^
-- % 的技巧-- 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 以此类推,是整数部分 + 两位小数部分
关系操作符
- >
- <
- >=
- <=
- == 相等性判断
- ~= 不等性判断
逻辑操作符
- and 第一个操作数为假,返回第一个,否则返回第二个
- or 第一个操作数为真,返回第一个,否则返回第二个
- not 只会返回 true 或 false
-- 短路操作的使用技巧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
字符串连接
- .. 字符串连接
优先级
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 = nilfor line in io.lines() do list = {next = list, value = line}endlocal l = listwhile l do print(l.value) l = l.nextend
指定索引的 table
options = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"}print(options["+"]) -- "add"
4. 语句
- 在 lua 中包括赋值,程序结构和过程调用
- 还有多重赋值和局部变量声明
赋值
- lua 支持多重赋值,即 a, b = 1, 2
- 会先计算等号右边所有元素的值,然后再赋值
- 如果右边的值少于左边变量,未被初始化的变量就置为 nil
- 如果左边变量少于右边的值,多余的值会被「抛弃」
- 可用来收集函数的多个返回值
- 初始化变量就是为每一个变量赋一个初始值
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 -- 初始化赋值
局部变量与块
块
- 一个块就是程序结构的执行体,或函数的执行体
- 在块内声明的变量仅在块内生效,即作用域为声明它们的块
- 可显式声明一个块使用 do end 将要执行的内容包裹在一个块内
局部变量
- local 用来声明一个局部变量
- 局部变量仅在声明它的块内生效,在块的外部无效
- 如:在循环内部声明在的变量在循环外部则无法使用
a = 3b = 0if a then local a = 5 b = a -- 将 then 块内的局部变量 a ,保存到全局变量 b 中 print(a)endprint(a) -- 3print(b) -- 5do -- code blockend
尽量使用局部变量
- 使用局部变量要比全局变量要快
- 避免污染全局环境
- 局部变量仅在声明它的块中有效,即在块外这个变量就被释放掉了
- lua 将局部变量声明当作语句处理,即可以在任何支持书写语句的地方书写局部变量声明
- 声明可以包含初始化赋值
程序结构
- 程序结构中的条件表达式可以是任何值
条件结构
- if
- elseif
- else
if 条件表达式 then -- 符合条件表达式执行endif 条件表达式1 then -- 符合条件表达式 1 执行 elseif 条件表达式2 then -- 符合条件表达式 2 执行endif 条件表达式 then -- 条件表达式为真时执行else -- 条件表达式为假是执行end
循环结构
- for
- while 条件表达式为假时退出
- repeat ... until 条件表达式为真时推出,条件测试是在循环体之后做的,因此循环体至少会执行一次
- 在循环体内声明的局部变量的作用域包含了条件测试
- 循环的三个表达式是在循环开始前一次性求值的
- 控制变量会被自动声明为 for 块中的局部变量,即作用域为 for 块,在循环结束后不可见
- 不要在循环过程中修改控制变量的值
- 可以使用 break 或 return 在循环正常结束前提前结束它
for exp1, exp2, exp3 do endwhile 条件表达式 do endrepeat until 条件表达式a = 20repeat 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)endfor i,v in pairs(a) do print(i) print(v)end
两种 for 的共同点
- 循环变量都是循环体的局部变量
- 不应该对循环遍历进行赋值
days = {"第一天", "第二天", "第三天"}revdays = {}for i, v in ipairs(days) do revdays[v] = i -- 逆向数组,将数组索引和数组元素调换,可获取数组元素的位置endprint(revdays["第二天"]) -- 获取第二天所在位置
break 和 return
- break 用于结束一个循环,跳出内层循环后在外层循环中继续执行
- return 用于返回函数结果或简单的结束函数的执行
- 任何函数的结尾处实际都有一句隐式的 return
- 如果函数无返回值,就无需在结尾处加 return
两者的共同点
- 都是用作跳出当前块
- 都需要放在一个块的结尾处,即一个块的最后一条语句
- 因为 return 或 break 后的语句将无法执行到
- 可以用 do ... end 块包裹 return,用与调试,即调用函数但不执行函数内容的情况
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)
5. 函数
- 函数是对语句和表达式进行抽象的主要机制
函数的两种用法
- 一是可以完成特定的任务,一句函数调用被视为一条语句
- 二是只用来计算并返回特定结果,视为一句表达式
print("hello") -- 用来完成打印任务,视为一条语句a = os.date() -- os.date() 用来返回日期,视为一句表达式
两种用法的共同点
- 都需要将所有参数放到一对圆括号中 ()
- 但当参数为字面字符串或 table 构造式的时候 ()可以省略
- 即使调用函数没有参数,也必须要有一对空的圆括号 ()
print "hello" -- helloprint {1, 2, 3} -- 1 2 3print(os.date) -- 当前日期
定义
- function 是创建函数的关键字
- function add add 是函数的名称
- function add(n) n 是函数的形式参数,简称为形参
- add(4) 4 是调用 add()函数时的实际参 ,简称为实参
- 实参多余形参,多余的实参被「抛弃」
- 形参多余实参,多余的形参被置为nil
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 作为第一个参数
多重返回值
- 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, valenda = {1, 2, 55, 22, 29, 4}maximum(a)
不同情况下的返回值
- 如果将函数作为单独的语句执行,lua 会丢弃所有的返回值
- 如果将函数作为表达式的一部分调用,只保留函数的第一个返回值
- 只有当函数是一系列表达式中的最后一个元素(或只有一个元素的时候),才会获取所有的返回值
一系列表达式的4种情况
多重赋值
- 如果一个函数调用是最后(或仅有)的一个表达式,lua 会保留尽可能多的返回值,用来匹配赋值的变量
- 如果一个函数没有返回值或没有返回足够多的返回值,那么 lua 会用 nil 来补充缺失的值
- 如果一个函数调用不是一系列表达式中的最后一个元素,就只能返回一个值
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
函数调用时传入的实参列表
- 如果一个函数调用作为另一个函数调用的最后一个(或仅有的)实参的时候,第一个函数的所有返回值都会作为实参传递给另一个函数
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"
table 构造式
- table 构造式会完整接收一个函数调用的所有结果,即不会由任何数量方面的调整
- 但这种行为,只有当一个函数调用作为最后一个元素时才发生
- 其他位置上的函数调用总是只产生一个结果值
function foo() endfunction foo1() return "a" endfunction foo2() return "a", "b" end-- 函数调用是 table 中的最后一个元素a = {foo2()} -- a = {"a", "b"}a = {foo2(), 10} -- a = {"a", 10}
return 语句
- 将函数调用放入一对圆括号 () 中,使其只返回一个结果
- return 语句后面的内容不需要 () 圆括号
- 如果强行加上则会使一个多返回值的函数,强制其只返回一个 return(f())
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() 没有返回值
unpack 函数
- 接收一个数组作为参数
- 并从下标 1 开始返回该数组的所有元素
- 这个预定义函数由 C 语言编写
print(unpack{10, 20, 30}) -- 10 20 30a, b = unpack{10, 20, 30} -- a = 10, b = 20
- 用于泛型调用
- 泛型调用就是可以以任何实参来调用任何函数
-- 调用任意函数 f, 而所有的参数都在数组 a 中-- unpack 将返回 a 中的所有值,这些值作为 f 的实参f(unpack(a)) f = string.finda = {"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) endend
变长参数
- 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 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
具名参数
- 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)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 块方便大家阅读,在此祝愿大家可以做什么事都能够踏踏实实地打好地基。