1.Lua的8种基本数据类型:
nil,string,number,boolean,table,function,thread,userdata
nil表示空值,和其他类型都不相等,在条件判断中只有nil和false表示否定。 number表示值类型,5.3以前lua是不区分整型和浮点型,5.3以后新增了整型作为number的子类型,可以使用math.type判断一个值是整型还是浮点型。
2.table是唯一的数据结构
table是lua唯一的数据结构,其他结构都可以用table实现,它不仅可以用数值作索引,也可以用其他类型(比如字符串,函数,甚至另一个table),lua内部用一个数组和一个哈希表来实现table。这使得table非常强大,即可以用作字典,也可以用作数组,配合元表机制还可以模拟面向对象。lua的很多基础设施,比如模块,全局变量,元表,都是基于table实现的。在语言层面它是如此简洁,可又能实现无比强大的功能。 在table中要谨慎的使用nil,例:
1 2 3 4 |
local t = {1, 2, nil, nil, 5} print(#t) --> 5 t = {[1]=1, [2]=2, [3]=nil, [4]=nil, [5]=4} print(#t) --> 2 |
两种表达方式,得到的结果不一样,所以在table中不能使用nil,应该使用false代替。
3.函数
Lua函数支持多返回值,也支持可变参数,例:
1 2 3 4 5 6 7 8 9 10 |
function myprint(...) local str = table.concat({...}, \t) .. '\n' io.stdout:write(str) io.stdout:flush() end function myfun() return 1, 2, 3 end myprint(myfun()) --> 1 2 3 |
pcall与xpcall: lua可以使用pcall, xpcall来实现错误的捕获,在pcall中执行的函数如果出错,会返回false,后面带一个错误消息:(pcall直接调用该函数,后面跟函数的参数)
1 2 3 4 5 6 7 8 |
local function test(a) if a == 2 then error(test error) end return true end local ok, ret = pcall(test, 1) --> true true local ok, ret = pcall(test, 2) --> false test.lua:5: test error |
pcall返回的第1个值表明test函数是否成功执行,如果为true,则后面的返回值就是test函数的返回值;如果为false,后面的值是一个错误消息。但是pcall有时候并不能满足要求,比如我们想知道错误是在哪里发生的,那么就要用xpcall来实现:(xpcall第一个参数为要调用的函数,第二个参数为调用失败时的回调函数,第三个参数开始为要调用函数的参数列表)
1 2 3 4 5 6 |
local function msghander(msg) print(msg) print(debug.traceback()) end local ok, ret = xpcall(test, msghander, 1) --> true true 不会调用msghander local ok, ret = xpcall(test, msghander, 2) --> false nil 调用msghander |
内部函数里可以引用外部函数的局部变量,这些引用被称为upvalue,而Lua的函数也可以叫闭包。闭包和upvalue是非常重要的特性。 作为一个典型的应用,假设要创建一个对象,对外只提供有限的访问接口,而对象内部的数据不能直接被修改,那么我们可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
local function new_object() local obj = { -- 这就是要创建的对象 _data1 = 1, -- 假设这是内部数据 _data2 = 2, -- 这是外部可修改的数据 } return { -- 这是返回的接口table get_data2 = function() return obj._data2 end, set_data2 = function(value) obj._data2 = value end, } end local obj_inteface = new_object() obj_inteface.set_data2(100) print(obj_inteface.get_data2()) --> 100 |
4.元表
给一个table设置元表,当对这个table执行特定操作时,如果元表存在对应的元方法,则会调用该元方法。元表大概是这样设置的:
1 2 3 |
local t = {} -- 表 local mt = {} -- 元表 setmetatable(t, mt) -- 将mt设置为t的元表 |
元方法:
1.__tostring
如果t的元表存在tostring元方法,调用tostring(t)时(或者print(t)时,因为print内部会自动调用tostring),会调用元表的tostring方法,这样我们就可以对t进行序列化,下面是一个例子:
1 2 3 4 5 6 7 8 |
local mt = { __tostring = function(t) return string.format("a=%s, b=%s", t.a, t.b) end } local t = {a = 1, b = 2} setmetatable(t, mt) print(tostring(t)) --> a=1,b=2 |
2.__pairs
当调用pairs(t)时,如果元表存在__pairs,就会调用该元方法并传入t。这可以实现对表的特殊遍历。
我们想遍历出值是number类型的元素,这时就可以使用__pairs来实现,在实现之前我们先对泛型for循环作一个介绍,pairs返回3个变量:迭代器函数(比如next),表,当前索引。for循环从pairs得到这3个变量后,会调用迭代函数,如:next(t, index),一开始index为nil,这样next就返回第一个index和value。 接着for循环不断的调用next并传入上一次调用得到的index,直到next返回nil为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
local mt = { __pairs = function(t) local function itr(t, idx) local v while true do idx, v = next(t, idx) if not idx then return nil elseif type(v) == 'number' then return idx, v end end end return itr, t, nil end } local t = {a = 1, b = "s", c = "ss", d = 1} setmetatable(t, mt) for k, v in pairs(t) do print(k, v) end |
3.__len
如果t是一个数组,用#t可以取得该数组的长度, 但如果是字典就不行了,必须用__len元方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
local mt = { __len = function(t) local n = 0 for k, v in pairs(t) do n = n + 1 end return n end } local t = {a = 1, b = "s", c = "ss", d = 1} setmetatable(t, mt) print(#t) --> 4 |
4.__index与__newindex
这两个元方法用于对table的读写控制。
读表中key关联的值,如果key不存在,会调用元表的index。index可以是一个函数__index(t, k);也可以是另一个表,此时会直接读取该表的key值。如果不想调用t[k]时触发元方法,可以使用rawget(t, k)。
要设置表中key关联的值,如果key不存在,会调用元表的newindex。同样newindex可以是一个函数__newindex(t, k, v);也可以是另一张表,此时会直接向该表的key设置值。如果不想t[k]=v时触发元方法,可以使用rawset(t, k, v)。
这两个元方法,可用于实现类似面向对象的机制。
P.S. rawget与rawset就是不使用元表和元方法的情况下对表的访问和赋值。
5.__call
这个元方法可使非函数对象像函数一样调用,比如一个表t,我们这样写:t("hi"),如果它的元表存在__call函数,则会触发调用:
1 2 3 |
__call = function(t, a1) -- a1 == "hi" --do something end |
5.Debug库
debug.debug 进入命令行交互模式,在这里相当于进入了虚拟机内部,可以查看全局变量,但是这是一个独立的上下文环境,不能查看调用它的函数的局部变量。
debug.getinfo ([thread,] f [, what]) 取函数调试信息,f可以是一个函数,也可以是调用堆栈的层数,1表示调用getinfo的函数,2表示再上一层,以此类推。what指定想查哪些信息,不指定表示全部信息。
debug.getlocal ([thread,] f, local) 取函数的本地变量,注意f是栈的层次,和上面一样1表示调用debug.getlocal的函数。local从1开始取函数参数和本地变量,从-1开始是可变参数,返回名字和值。如果取不到返回nil
debug.getupvalue (f, up) 取函数引用的upvalue,f是一个函数对象,up从1开始,返回名字和值。如果取不到返回nil。
debug.sethook ([thread,] hook, mask [, count]),设置一个hook,当指定事件发生时,这个hook会被回调,hook是回调函数,mask表明想触发什么事件,如果mask为空,count须指定一个值表示执行多少条指令后触发。mask是下面值的组合:
- 'c' 每次调用一个函数触发。
- 'l' 每执行一行触发。
- 'r' 每次从一个函数返回触发
debug.traceback ([thread,] [message [, level]]) 返用调用堆栈的信息,message表示返回的头部加一段信息,level表示栈的层次,1表示调用debug.traceback那个函数,往上类推。
debug.setupvalue (f, up, value) 给函数f设置upvalue,up同样是一个序号
debug.setlocal ([thread,] level, local, value) 给函数设置本地变量, level是栈层次,local是本地变量的序号,value是值。
6.协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function foo (a) print("foo", a) return coroutine.yield(2*a) end co = coroutine.create(function (a,b) print("co-body", a, b) local r = foo(a+1) print("co-body", r) local r, s = coroutine.yield(a+b, a-b) print("co-body", r, s) return b, "end" end) print("main", coroutine.resume(co, 1, 10)) print("main", coroutine.resume(co, "r")) print("main", coroutine.resume(co, "x", "y")) print("main", coroutine.resume(co, "x", "y")) |
输出结果:
1 2 3 4 5 6 7 8 |
co-body 1 10 foo 2 main true 4 co-body r main true 11 -9 co-body x y main true 10 end main false cannot resume dead coroutine |