第六日笔记
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