Lua学习笔记
一、Lua介绍
遵循国际惯例介绍下Lua。 Lua(英语发音:/ˈluːə/)是一个轻量级可扩展的脚本语言,可以轻松嵌入其他语言,如C/C++。介绍完毕>.<
一些网站: 中文手册:http://book.luaer.cn/ API: http://manual.luaer.cn/
一个ide: luaForWindows
二、Lua基本知识
Chunks: 一系列的语句的组合,可以是函数,可以理解为语句块。
交互模式下(命令行模式),Lua通常把每一个行当作一个Chunk,但如果Lua一行不是一个完整的Chunk时,他会等待继续输入直到得到一个完整的Chunk.在Lua等待续行时,显示不同的提示符(一般是>>).
一个可行的运行外部Chunk的方法:dofile函数加载文件并执行。假设有一个文件:
-- file 'lib1.lua' function twice (x) return 2*x end
在交互模式下:
> dofile(lib1.lua) > print(twice(2)) -- -> 4
全局变量: 不需要声明,只要对某个变量进行赋值即为全局变量, 未赋初始值的变量的值为nil。若需要删除一个全局变量,只需要把其赋值为nil
局部变量: 需要用local关键字声明
词法约定:
标识符: 字母(letter)或者下划线开头的字母、下划线、数字序列.最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。Lua中,letter的含义是依赖于本地环境的。
保留字:
and break do else elseif end false for function if in local nil not or repeat return then true until while
Lua是大小写敏感的。 注释符为 -- 。 多行注释为 --[[ --]]
命令行:lua [options] [script [args]]
-e:直接将命令传入Lua
-l:加载一个文件.
-i:进入交互模式.
变量和值:
Lua中有8个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table。函数type可以测试给定变量或者值的类型。
Nil : Lua中特殊的类型,一个值未被赋值之前都是nil类型。要删除一个变量只要把变量赋值为nil
Booleans: true、false 但要注意Lua中所有的值都可以作为条件。在控制结构的条件中除了false和nil为假,其他值都为真。所以Lua认为0和空串都是真。
Numbers: Lua没有整数,只有实数。不会产生误差除非数字大于100,000,000,000,000 (概念是这样说的,但是...看看下面的实例)
--while i = 1 while i < 10 do print(i) i = i + 0.01 end
以下是部分的输出:
Strings: 指字符的序列。lua是8位字节,所以字符串可以包含任何数值字符,包括嵌入的0。这意味着你可以存储任意的二进制数据在一个字符串里。Lua中字符串是不可以修改的, 可使用单引号或双引号表示字符串
转义字符:
\a bell \b back space -- 后退 \f form feed -- 换页 \n newline -- 换行 \r carriage return -- 回车 \t horizontal tab -- 制表 \v vertical tab \\ backslash -- "\" \" double quote -- 双引号 \' single quote -- 单引号 \[ left square bracket -- 左中括号 \] right square bracket -- 右中括号
还可以使用转义字符表示字母:\ddd (ddd代表3位十进制数)
还可以使用[[...]]来表示字符串,这样可以嵌套多行字符串(如HTML代码),但[[...]]内的'[' ']'符需要用转义符'\'
Lua支持数字与字符串自动转换。
字符串连接符为'..'
Functions:函数是第一类值(和其他变量相同),意味着函数可以存储在变量中,可以作为函数的参数,也可以作为函数的返回值。
function sortbygrade(names,grades) --函数作为参数 table.sort(names,function (n1, n2) return grades[n1] > grades[n2] end) end --函数存为变量 func = sortbygrade names = {"peter","paul","mary"} grades = {mary = 10, paul = 7, peter = 8} func(names, grades) for k,v in pairs(names) do print(v) end
Tables: table(表) 可以构造一个空表或者直接赋初值达到构造表的目的
-- 创建一个空的 table local tbl1 = {} -- 直接初始表 local tbl2 = {"apple", "pear", "orange", "grape"} print(tbl2[1]) --> apple
表的索引可以是一个数或字符串,表的值是一个索引,表的第一个元素的索引默认为1(不同于C/C++从0开始),表的长度不固定
a = {} a["key"] = "val1" a[1] = 1; --值甚至可以是一个表 a["self"] = a;
Threads: thread(线程)
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
Userdatas: userdata是自定义类型
用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
算数运算符:
二元运算符:+ - * / ^ (加减乘除幂)
一元运算符:- (负值)
这些运算符的操作数都是实数。
关系运算符:
与C差不多 注意不等于是'~='
逻辑运算符: and or not (与C差太远了...)
a and b -- 如果a为false,则返回a,否则返回b
a or b -- 如果a为true,则返回a,否则返回b
and的优先级比or高
-- 若x为false或nil则把x赋初始值v x = x or v -- C中的a?b:c 可以这样实现 (a and b) or c
连接运算符: ..
优先级:除了^和..外所有的二元运算符都是左连接的。
^ not - (负号) * / + - (减号) .. < > <= >= ~= == and or
三、基本语法
赋值语句: 支持多个变量同时赋值
a, b = 10, 2*x <--> a=10; b=2*x --十分方便的交换变量方法 x , y = y , x
当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
a. 变量个数 > 值的个数 按变量个数补足nil
b. 变量个数 < 值的个数 多余的值会被忽略
局部变量和代码块:
使用local创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。代码块:指一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串)。
x = 10 local i = 1 -- local to the chunk while i<=x do local x = i*2 -- local to the while body print(x) --> 2, 4, 6, 8, ... i = i + 1 end if i > 20 then local x -- local to the "then" body x = 20 print(x + 2) else print(x) --> 10 (the global one) end print(x) --> 10 (the global one)
应该尽可能的使用局部变量,有两个好处:
1. 避免命名冲突
2. 访问局部变量的速度比全局变量更快.
控制结构:控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,其他值为真。
if语句,有三种形式:
if conditions then then-part end; if conditions then then-part else else-part end; if conditions then then-part elseif conditions then elseif-part else else-part end;
while和repeat-until
while condition do statements; end;
repeat statements; until conditions;
for语句有两大类:
第一,数值for循环:
for var=exp1,exp2,exp3 do loop-part end
for将用exp3作为step从exp1(初始值)到exp2(终止值),执行loop-part。其中exp3可以省略,默认step=1
有几点需要注意:
1. 三个表达式只会被计算一次,并且是在循环开始前。
2. 控制变量var是局部变量自动被声明,并且只在循环内有效.
for i=1,10 do print(i) end max = i -- probably wrong! 'i' here is global
3. 循环过程中不要改变控制变量的值,那样做的结果是不可预知的。如果要退出循环,使用break语句。
范型for循环:
-- print all values of array 'a' for i,v in ipairs(a) do print(v) end -- print all keys of table 't' for k in pairs(t) do print(k) end
范型for和数值for有两点相同:
1. 控制变量是局部变量
2. 不要修改控制变量的值
break和return: Lua只有break和return,没有continue...
break语句用来退出当前循环(for、repeat、while)。在循环外部不可以使用。
return用来从函数返回结果,当一个函数自然结束时,结尾会有一个默认的return。(这种函数类似pascal的过程(procedure))
Lua语法要求break和return只能出现在block的结尾一句(也就是说:作为chunk的最后一句,或者在end之前,或者else前,或者until前)
所以如果需要在函数的中间return,可以显式使用"do return end"来跳出函数
function foo () return --<< SYNTAX ERROR -- 'return' is the last statement in the next block do return end -- OK ... -- statements not reached end
函数:
语法:
function func_name (arguments-list) statements-list; end;
Lua函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用nil补足。
Lua函数支持多个值返回
function f(a, b) return a or b end f(3) --> a=3, b=nil f(3, 4) --> a=3, b=4 f(3, 4, 5) --> a=3, b=4 (5 is discarded) function g(a,b,c) return a,b,c end a,b,c = g(1,2,3) print(a,b,c)
可变参数:Lua函数可以接受可变数目的参数,和C语言类似在函数参数列表中使用三点(...)表示函数有可变的参数。Lua将函数的参数放在一个叫arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数。
function g (a, b, ...) end g(3) --> a=3, b=nil, arg={n=0} g(3, 4) --> a=3, b=4, arg={n=0} g(3, 4, 5, 8) --> a=3, b=4, arg={5, 8; n=2}
举个具体的例子,如果我们只想要string.find返回的第二个值。一个典型的方法是使用哑元(dummy variable,下划线):
local _, x = string.find(s, p) -- now use `x' ...
命名参数:
rename{old="temp.lua", new="temp1.lua"} -->注意不要用() function rename (arg) return os.rename(arg.old, arg.new) end
闭包:
词法定界:当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量
function sortbygrade (names, grades) -- grades是sortbygrade的局部变量 这里grades称为 外部的局部变量 table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- 可以访问外部函数的局部变量 end) end
看下面的代码:
function newCounter() local i = 0 return function() -- anonymous function i = i + 1 return i end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2 c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c2()) --> 2
这里就体现了闭包的思想,c1和c2虽然调用了同样的函数,但其局部变量i是c1和c2各自私有的
闭包在完全不同的上下文中也是很有用途的。因为函数被存储在普通的变量内我们可以很方便的重定义或者预定义函数。通常当你需要原始函数有一个新的实现时可以重定义函数。例如你可以重定义sin使其接受一个度数而不是弧度作为参数:
do local oldSin = math.sin local k = math.pi/180 math.sin = function (x) return oldSin(x*k) end end
这样我们把原始版本放在一个局部变量内,访问sin的唯一方式是通过新版本的函数。
利用同样的特征我们可以创建一个安全的环境(也称作沙箱,和java里的沙箱一样),当我们运行一段不信任的代码(比如我们运行网络服务器上获取的代码)时安全的环境是需要的,比如我们可以使用闭包重定义io库的open函数来限制程序打开的文件。
do local oldOpen = io.open io.open = function (filename, mode) if access_OK(filename, mode) then return oldOpen(filename, mode) else return nil, "access denied" end end end
非全局函数:
函数可以作为表table的域,例如以下三种方法初始化
1. Lib = {} Lib.foo = function (x,y) return x + y end Lib.goo = function (x,y) return x - y end 2. Lib = { foo = function (x,y) return x + y end, goo = function (x,y) return x - y end } 3. Lib = {} function Lib.foo (x,y) return x + y end function Lib.goo (x,y) return x - y end
函数可以赋值给一个局部变量,此时函数就是一个非全局函数(局部函数), 局部函数像局部变量一样在一定范围内有效
这种定义在包中是非常有用的:因为Lua把chunk当作函数处理,在chunk内可以声明局部函数(仅仅在chunk内可见),词法定界保证了包内的其他函数可以调用此函数。下面是声明局部函数的两种方式:
1. local f = function (...) ... end local g = function (...) ... f() -- external local `f' is visible here ... end 2. local function f (...) ... end
正确的尾调用(Proper Tail Calls)
在函数的尾部调用其他函数或者直接return,不作其他任何操作,则称为尾调用。例如:
function f(x) return g(x) end
例子中f调用g后不会再做任何事情,这种情况下当被调用函数g结束时程序不需要返回到调用者f;所以尾调用之后程序不需要在栈中保留关于调用者的任何信息。一些编译器比如Lua解释器利用这种特性在处理尾调用时不使用额外的栈,我们称这种语言支持正确的尾调用。
由于尾调用不需要在栈中保留调用者的任何信息,所以使用尾调用的函数不需要担心爆栈的问题。
以下这些不属于尾调用
function f (x) g(x) return -- 由于不得不丢弃g地返回值,所以不是尾调用 end return g(x) + 1 -- must do the addition return x or g(x) -- must adjust to 1 result return (g(x)) -- must adjust to 1 result
迭代器与泛型for
迭代器与闭包:迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
迭代器需要保留上一次成功调用的状态和下一次成功调用的状态,也就是他知道来自于哪里和将要前往哪里。闭包提供的机制可以很容易实现这个任务。记住:闭包是一个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)。
举一个简单的例子,我们为一个list写一个简单的迭代器,与ipairs()不同的是我们实现的这个迭代器返回元素的值而不是索引下标:
function list_iter (t) local i = 0 local n = table.getn(t) return function () i = i + 1 if i <= n then return t[i] end end end t = {10, 20, 30} for element in list_iter(t) do print(element) end
每次循环都会调用一次list_iter(t),产生一个新的闭包
以下跳过n章。。(由于不知道是否会用到这些知识>.< 有点难就先放着啦)
序列化:
function serialize (o) if type(o) == "number" then io.write(o) elseif type(o) == "string" then io.write(string.format("%q", o)) --> %q是Lua专门为了格式化字符串而提供的 else ... end
保存不带循环的table (不存在如a[1]=a等这样的情况)
function serialize(o) if type(o) == "number" then io.write(o) elseif type(o) == "string" then io.write(string.format("%q", o)) elseif type(o) == "table" then io.write("{\n") for k,v in pairs(o) do io.write(" [") serialize(k) io.write("] = ") serialize(v) io.write(",\n") end io.write("}\n") end end
o = {a=12, b='Lua', key='another "one"'} serialize(o) --输出 { ["a"] = 12, ["key"] = "another \"one\"", ["b"] = "Lua", }
存在循环的表table的保存方法如下:
为了表示循环我们需要将表名记录下来,下面我们的函数有两个参数:table和对应的名字。另外,我们还必须记录已经保存过的table以防止由于循环而被重复保存。我们使用一个额外的table来记录保存过的表的轨迹,这个表的下表索引为table,而值为对应的表名。
我们做一个限制:要保存的table只有一个字符串或者数字关键字。下面的这个函数序列化基本类型并返回结果。
function basicSerialize (o) if type(o) == "number" then return tostring(o) else -- assume it is a string return string.format("%q", o) end end
关键内容在接下来的这个函数,saved这个参数是上面提到的记录已经保存的表的踪迹的table。
function save (name, value, saved) saved = saved or {} -- initial value io.write(name, " = ") if type(value) == "number" or type(value) == "string" then io.write(basicSerialize(value), "\n") elseif type(value) == "table" then if saved[value] then -- value already saved? -- use its previous name io.write(saved[value], "\n") else saved[value] = name -- save name for next time io.write("{}\n") -- create a new table for k,v in pairs(value) do -- save its fields local fieldname = string.format("%s[%s]", name, basicSerialize(k)) save(fieldname, v, saved) end end else error("cannot save a " .. type(value)) end end
举个例子:
a = {x=1, y=2; {3,4,5}} a[2] = a -- cycle a.z = a[1] -- shared sub-table --调用save('a', a)之后结果为: a = {} a[1] = {} a[1][1] = 3 a[1][2] = 4 a[1][3] = 5 a[2] = a a["y"] = 2 a["x"] = 1 a["z"] = a[1]