第六日笔记
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 = [[
芜湖
]]
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
因为,目前只学到第五章函数篇,所以只有前五章的复习汇总,很基础,也很重要,也祝愿大家可以踏踏实实地打好地基。