-- Lua 允许多重赋值
a, b = 10, 2 * 8
-- 并且先对等号右边所有元素求值后再赋值
-- swap可以这样实现
a, b = b, a
-- 如果右边个数多于左边,多出来的值会被丢弃
a, b = 10, 2 * 8, 133, 42
-- 如果右边个数少于左边,左边多余的变量会被赋为nil
a, b = 1
一般很少会为几个没有关联的变量使用多重赋值。有时会用到:
j = 10 -- 全局变量
local i = 1 -- 局部变量
局部变量的作用域仅限于声明它们的块
如果需要严格控制局部变量的作用域,使用 do 块
a = 10
do
local a = 2;
a = a + 1
end
print(a)
一种习惯写法:
local foo = foo
创建了局部变量 foo,用全局变量 foo 的值初始化它。作用:
if … then … end
if … then … else … end
if … then … elseif … then… elseif … then … else … end
while … do … end
repeat … until …
类似 C 中的 do while。注意,在 repeat 块中的局部变量,在 until 条件中依然可见。
-- i从2变化到5(闭区间),步长为2
-- 步长可选,若不指定,默认为1
for i = 2, 5, 1 do
print(i)
end
-- 如果不想设置上限
for i = 1, math.huge do
print(i)
end
for 的三个表达式在循环开始前一次性求值
循环变量自动声明为局部变量
不要在循环过程中修改控制变量的值
通过迭代器函数来遍历
a = {"A", "B", "C"}
-- 基础库提供的迭代器函数
-- ipairs迭代数组
for key, val in ipairs(a) do
print(key, val)
end
-- pairs迭代table
for val in pairs(a) do
print(val)
end
-- io,lines迭代文件中每行
-- string.gmatch迭代字符串中单词
循环变量自动声明为局部变量
不要在循环过程中修改控制变量的值
例:构造逆向 table
days = {"Sun", "Mon", "Tue",
"Wed", "Thu", "Fri", "Sat"}
revdays = {}
for key, val in pairs(days) do
revdays[val] = key
end
for key, val in pairs(revdays) do
print(key, val)
end
用于跳出当前的块
如果函数没有 return,结尾处会有隐式的 return
可以这样在调试的时候忽略 return
function foo ()
do return end -- 这样return就失去本来作用
-- ...
end
函数调用都要将参数放入圆括号,没有参数也要写空括号。以下两种例外可以没有圆括号:
为面向对象的调用提供了冒号操作符,详见 16 章
实参数量会被自动调整,匹配参数表的要求,调整和多重赋值一样。
b, e = string.find("Hello my friend", "my");
print(string.sub("Hello my friend", b, e)) -->my
只需在编写函数的时候,在 return 后面用逗号分隔,列出所有的返回值即可。
同样类似于多重赋值,Lua 会调整返回值数量来适应不同的调用情况。详见 P37
如果把函数调用放入一对圆括号,将迫使它只返回一个结果
通过 unpack 函数来把数组中下标 1 开始的所有元素作为函数参数
f = string.find
a = {"hello", "ll"}
print(f(unpack(a)))
-- ...表示函数接受不同数量的实参
function add(...)
local s = 0
for i, v in ipairs{...} do
s = s + v
end
return s
end
print(add(3,4,5,6,7))
跟踪特定函数的技巧:
function add(...)
local s = 0
for i, v in ipairs{...} do
s = s + v
end
return s
end
-- 用于跟踪add,每次调用前先显示函数名及参数
function tracingAdd(...)
print("calling add:", ...)
return add(...)
end
print(tracingAdd(3,4,5,6,7))
结合格式化文本和输出文本两个函数:
function fwrite (fmt, ...)
return io.write(string.format(fmt, ...))
end
fwrite("%d%d\n", 4, 5)
使用 select 来遍历所有变长参数(包括 nil)
select 的第一个参数为 selector,如果是数字 n,返回第 n 个可变实参,否则只能是字符串 “#”,返回变长参数的总数(包括 nil)
for i = 1, select("#", ...) do
local arg = select(i, ...)
print(arg)
end
通过把所有实参组织到一个 table 中,并将 table 作为唯一的实参。这样就可以用 table 构造式来代替圆括号。
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) ~= "number" then
error("no height")
end
-- 其他参数都是可选的
-- _Window才是真正创建窗口的函数
_Window(
options.title,
options.x or 0
options.y or 0
options.width,
options.height,
options.background or "white"
options.border
)
end
自己写的例子:
function addABC(numbers)
-- 检查必要参数
if type(numbers.a) ~= "number" then
error("a is not a number")
elseif type(numbers.b) ~= "number" then
error("b is not a number")
elseif type(numbers.c) ~= "number" then
error("c is not a number")
end
return _addABC(numbers.a, numbers.b, numbers.c)
end
function _addABC(a, b, c)
return a + b + c
end
print(addABC{a = 2, b = 3, c = 4})
函数与所有其他值一样都是匿名的,一个函数名实际上是一个持有某函数的变量。
最常见的函数编写方式只是一种语法糖:
--[[
function add(a, b)
return a + b
end
--]]
-- 上下两种写法完全等价
add = function(a, b) return a + b end
类似 cpp 中的 sort,Lua 的 table.sort 接受一个 table 并对其排序,第二个参数是一个次序函数,并且是可选的。该函数接受两个元素,并返回第一个元素是否应该排在第二个元素前面。(cpp 中 sort 的 compare 函数本质上也是这样)
接受另一个函数作为实参的,称其是一个高阶函数(只是一种定义,高阶函数并没有什么特权)
一个 closure 就是一个函数加上该函数所需访问的所有 “非局部的变量”
Lua 只有 closure,不存在“函数”,函数本身是一种特殊的 closure(恰巧没有 upvalue)
function newCounter()
local i = 0
return function ()
-- 这里的i就是一个upvalue
-- 可以理解为捕获了外面的i
-- 在这里对i的修改会影响到外面的i
i = i + 1
return i
end
end
-- c1是匿名函数
c1 = newCounter()
print(c1()) -->1
print(c1()) -->2
c2 = newCounter()
print(c2()) -->1
print(c1()) -->3
print(c2()) -->2
重新定义预定义的函数:
do
-- 把预定义的print保存在局部变量中
-- 用do块封装
local oldPrint = print
print = function(...)
oldPrint(...)
oldPrint("my new print")
end
end
-- 在do块外面就只能使用新定义的函数了
print(4)
创建安全的运行环境(沙盒),详见 P49
只要将一个函数存储到一个局部变量中,就得到了一个局部函数。
注意递归的局部函数的写法,需要预定义函数
--[[ 错误示范
local fact = function(n)
if n == 0 then
return 1
else
return n * fact(n - 1)
end
end
--]]
local fact -- 预定义即可
fact = function(n)
if n == 0 then
return 1
else
return n * fact(n - 1)
end
end
-- 或者使用语法糖
local function fact(n)
if n == 0 then
return 1
else
return n * fact(n - 1)
end
end
尾调用:一个函数调用是另一个函数的最后一个动作
Lua 解释器在进行尾调用时不耗费任何栈空间
-- 不管n有多大,都不会栈溢出
foo = function(n)
print(n)
if n > 0 then
return foo(n-1)
end
end
但是要确保调用是“正确的尾调用”
-- 以下情况都不是正确的尾调用
return g(x) + 1
return x or g(x)
return (g(x))
-- 只有以下是正确的尾调用
return <func>(<args>)
尾调用好比是一条 goto 语句
P53 展示了一个用尾调用来实现状态机的例子,无论状态机中的状态转移多少次也不会造成栈溢出