-
书:Programming in lua,2th edition
-
参考手册:http://www.lua.org/manual/5.1/
-
IDEA:https://blog.csdn.net/zjz520yy/article/details/79919267
lua大小写敏感
lua源码是C实现的
自己编译lua源文件,包含链接库,解释器以及编译器。
library: lapi.c lcode.c ldebug.c ldo.c ldump.c lfunc.c lgc.c llex.c
lmem.c lobject.c lopcodes.c lparser.c lstate.c lstring.c
ltable.c ltm.c lundump.c lvm.c lzio.c
lauxlib.c lbaselib.c ldblib.c liolib.c lmathlib.c loslib.c
ltablib.c lstrlib.c loadlib.c linit.c
interpreter: library, lua.c
compiler: library, luac.c print.c
lua注释
-- 一行注释
--[[multiline
multiline
]]
注释小技巧
--[[
xxx
--]]
取消注释前面加一个-即可
---[[
xxx
--]]
还可以在[[以及]]之间加入任意数量的=,用于某些内容也包含[]符号的情况
--[==[
xxx
]==]
用lua解释器
lua -i xx.lua -- 在执行一个lua文件后开始交互模式
lua xx.lua-- 执行一个lua文件后结束
lua xx.out -- 执行一个编译后的lua文件
lua -- 进入可交互模式
lua -e "print(math.sin(12))" -- 直接执行代码
lua -i -l lib -e "x = 10" -- 加载lib库后,运行"x=10",然后进入可交互模式
交互模式的每一行都是一个程序块,执行完就结束了,当用全局变量的时候,一点问题都没,当用了局部变量,下一行再访问它,就是访问同名的全局变量了。
在UNIX输入CTRL+D,在windows输入CTRL_Z或者输入os.exit()可以退出交互模式
用lua编译器
luac xx.lua
lua函数
dofile("xx.lua") -- 执行lua文件,这个函数做了两件事,先loadfile得到一个函数,再调用它。
loadfile("xx.lua") -- 编译lua文件,不运行,返回一个函数,如果发生错误则返回nil和错误信息。(更灵活)
loadstring("xxx") -- 与loadfile差不多,只是loadfile是从文件读取代码,loadstring是从字符串读取
load(xxx) -- 最底层的load函数
loadxxx函数把程序块当作一个匿名函数,function(...) 程序块 end
lua常量
_VERSION -- lua版本
_PROMPT -- 可以修改lua交互模式下的提示符
全局变量,局部变量
a -- global
local a -- local
类型
nil, Boolean, number, string, userdata, function, thread, and table
type(var) -- 打印变量类型的字符串描述
and or not
短路求值
a and b, 结果是a,如果a是false,反之是b
a or b,结果是a,如果a是true,反之是b
x=x or v -- 常见写法
x = a and b or c -- 常见写法
not not x -- 一定返回boolean
POSIX系统(如Unix)直接执行lua文件
在lua文件开头加上#!/usr/local/bin/lua或者#!/usr/bin/env lua,然后给lua文件加执行权限,就可以
./xx.lua直接执行lua文件,解释器会忽略开头以#开始的行。相当于lua xx.lua
全局变量arg
在命令行模式中lua -e "sin=math.sin" script a b
arg[-3] = "lua"
arg[-2] = "-e"
arg[-1] = "sin=math.sin"
arg[0] = "script"
arg[1] = "a"
arg[2] = "b"
string
'#'长度操作符,取字符串长度。按byte取,如果是其它编码,如中文,要自定义一个取长度接口。
‘#’长度操作符还能取数组table的元素个数。
长字符串
在[[...]]之间的内容不转义
page = [[
<html>
<head>
<title>An HTML Page</title>
</head>
<body>
<a href="http://www.lua.org">Lua</a>
</body>
</html>
]]
这个方括号间也可以加入任意数量的=,但是前后=数量要匹配,用于一些内容也包含[]这两个符号的情况,如
[==[
xxx
]==]
%取模操作符
a % b = a - floor(a/b)*b
table删除某个字段可以把那个字段设nil,如w.x=nil
lua多重赋值,如x,y=y,x 这样x,y值就互换了。用于接收函数返回的多个值,或者就像例子那样交换两个变量的值。
用do...end把代码包起来就形成一个块,类似其它的语言的{}。在交互模式可以使用阻止局部变量被释放。
尽量使用局部变量阻止扰乱全局环境。
控制结构:if...else,while,for,repeat...until(重复执行知道until条件为真)
break,return只能写在程序块的最后一个语句上。想在其它地方return,比如语句块的中间,可以用do return end,或者if true then return end
o.foo(o,x) <==> o:foo(x)
如果函数返回多个值,用table构造式{foo()},会把函数返回值作为table的初始元素。比如foo返回"a","b",table的元素为{"a","b"},把函数调用放在一个()内,可以迫使它只返回一个值,如(foo())就只返回"a"。
变长参数...,可以赋值给变量,如local a, b = ...,相当于用变长参数列表的第一个以及第二个参数赋值给a,b,如果没有则是nil。也可以用{...}初始化一个table。...有点像返回多个值的函数。
当变长参数里夹杂着nil时,可以用select函数来获取变长参数,用法如下
函数定义就是一个赋值语句,函数名是一个变量,持有了函数。
lua有全局变量,局部变量,非局部变量(就是upvalue)
访问速度是局部变量最快,非局部变量次之,全局变量最慢。
lua函数的本质是闭包,闭包是由一个函数和它所需要用到的非局部变量组成,就是一个函数里面包含了另一个函数,里面的那个函数用到了外部函数的局部变量(upvalue)。如下图:
lua尾调用
在函数末尾调用了另一个函数后就没其它事情了,像如下形式:
return <func>(<args>)
类似goto,可用来实现状态机
好处在于就没有堆栈信息了,就不会出现栈溢出。如下所示:
用闭包实现迭代器
需要一个迭代器工厂函数,返回一个迭代器函数,每次调用它都会保留调用完的状态,直到迭代器返回nil,迭代就结束。
泛型for的语义
无状态的迭代器(不用闭包,闭包是有开销的),尽量用这种迭代器
package.loadlib(path,functionname)
require调用这个函数来加载一个库,并把库中的函数注册到lua
pcall(protected call),xpcall,debug.debug,debug.traceback
error (message [, level])
Terminates the last protected function called and returns message
as the error message. Function error
never returns.
Usually, error
adds some information about the error position at the beginning of the message. The level
argument specifies how to get the error position. With level 1 (the default), the error position is where the error
function was called. Level 2 points the error to where the function that called error
was called; and so on. Passing a level 0 avoids the addition of error position information to the message.
coroutine(协程)
有4个状态:挂起,运行,死亡,正常。
刚创建的时候是挂起状态。
当协程A唤醒了协程B,协程B是运行状态,协程A的状态变为正常。
resume和yield可以交换数据。
第一次唤醒协程的时候,通过resume的额外参数传给协程的主函数的形参,然后把yield圆括号内的参数返回给resume,会返回类似true,yield参数1,yield参数2...,然后协程的状态变为挂起。再次resume,这回额外参数传进去,会变为yield函数返回的值。如下图所示:
coroutine.wrap(f)这个函数会返回一个函数,每次调用这个返回的函数,就会唤醒一次由f构造的协程,与resume的区别是不会返回错误代码
协程是非抢先式的多线程,即当一个线程阻塞后,整个程序都会卡住,多线程只是会充分利用时间,而不是同时运行,就是当一个线程卡住了就换另一个线程跑,直到全部跑完
foo("xxx") <====> foo "xxx"
foo({}) <====> foo {}
这样的语法糖可以用来实现一些东西,感觉像数据,其实是函数调用。
table数值如果index不是从1开始,或者中间有nil,或者不连续,很多内置函数和#操作符的结果就会有问题
pairs只遍历非nil元素
string.format("%q", a) -- 用双引号括住字符串a,并对字符串a中的双引号等其它特殊字符自动转义。
当用一些保留字做table的key时,不能写成{if=xxx},而应该写成{["if"]=xxx}
metatable
getmetatable(table)
setmetatable(t, meta)
所有字符串拥有同一个元表(不可改),只能设置修改table的元表,其它类型没有元表,要修改只能改lua的C源码。在元表中设置相应的字段和元方法,就可以在具体操作的时候调用相应的元方法
算术类的元方法:
__add (加+),__sub (减-), __mul (乘*), __div(除/),__unm(相反数-),__mod(取模%),__pow(幂^),__concat(连接..)
在算术表达式中,会先查找第一个操作数的元表是否有对应运算符的元方法,有的话就执行,没有就找第二个操作数,如果都没有就报错。
关系类的元方法:
__eq(==),__lt(<),__le(<=)
库定义的元方法
__tostring(tostring(x)) -- print(a),会调用a的tostring实现。
__metatable -- 当设置了这个字段,getmetatable就会返回这个字段的值,setmetatable就会报错
table访问的元方法
__index(table[index]时触发)-- 这个字段的值可以是一个函数function(t,key),也可以是一个table(对这个table继续执行t[index]),如果不想触发__index,可以用rawget(t,i),就是原始的访问。
__newindex (table[index]=xx,如果index原先不存在,就会触发) -- 可以是函数function(t,key,value),也可以是一个table(对这个table进行赋值),如果不想触发就用rawset(t,k,v)
弱引用的元方法
__mode -- 'k'表示key是弱引用,'v'表示value是弱引用, 'kv'完全弱引用
所有的全局变量存放在表_G里面
setfenv(f,t)或者setfenv(level,t)
设置函数环境,设置的是全局环境
require(modulerName) <=> require "modulerName"
所有的模块都存在一个包里,即package.loaded,用名字索引,require只是返回了package.loaded[name]
如果在package.preload中没找到对应的模块加载器,就会使用通用的规则查找lua文件(loadfile)或者C程序库(loadlib)。loadfile,loadlib只是加载了代码,还需要以模块名作为参数调用这些代码。
多次require同一个模块是不会有问题的,因为有缓存,如果想强行再次加载一个模块,可以先把package.loaded["modulerName"] = nil再require。
require函数会用传进入的参数替换每个路径里面的?,遍历每个路径,直到找到。路径存放在package.path(这个是lua文件的路径)里面(以;分隔)。在启动lua时,会用LUA_PATH环境变量初始化package.path,lua会把其中的字串";;"替换成默认路径。
如果找不到lua文件,就会找C程序库,路径存放在package.cpath,相应用LUA_CPATH初始化。文件一般以so或者dll为扩展名。
一个模块的写法
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
M.Constant = 10
function M.func() {}
当加载一个模块的时候,实际上就是把模块代码包在function(...) end里面,require函数的参数就会传给这个函数的...,如果要清理一个模块,要写_G[modname] = nil以及package.loaded[modname]=nil。如果我们不把模块赋值给全局变量,我们就不能直接用全局变量使用这个模块,只能通过返回的table来使用,但是想释放,还说要写package.loaded[modname]=nil
另一种模块的写法(更正规)
-- 模块设置
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
-- 声明这个模块从外界需要的所有东西
local sqrt = math.sqrt
local io = io
setfenv(1, M)
function funct() end -- 这个会自动赋值到M这个table里,访问变量也是从M获取
module函数包含了上述更正规模块写法。
-- 包含了上述模块设置,以及设置环境代码
module(..., package.seeall)
-- 模块的其它定义
function funct() end
子模块
require("mod.sub"),在查找文件时会把‘.’替换为目录分隔符,如在windows中替换为‘/’,这样就可以把模块子模块按目录结构存放了。
在查找C程序库时,其初始化函数会被命名为luaopen_mod_sub。如果模块名是a-b,初始化函数名是luaopen_b(省略连字符之前的字符)
面向对象编程
利用a.func(a,x) <=>a:func(x) 以及元表元方法__index,就可以实现类表以及从类表调用new返回对应的对象。
function Class:func(x)
-- 这里隐藏了一个参数self,即Object:func(x)时,self就是Object这个table
end
基类,继承基类的类还有对象
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
SpecialAccount = Account:new()
s = SpecialAccount:new()
多重继承,__index不是table而是一个函数
local function search(k, plist)
for i = 1, #plist do
local v = plist[i][k]
if v then return v end
end
end
function createClass(...)
local c = {}
local parents = {...}
setmetatable(c, {__index = function(t, k)
local v = search(k, parents)
-- t[k] = v
return v
end})
c.__index = c
function c:new(o)
o = o or {}
setmetatable(o,c)
return o
end
return c
end
私密性写法,通过闭包实现,把不让访问的东西放在非全局变量里,返回开放的东西,如果只有一个接口,直接返回函数就好了,就是我们先前说的闭包。
function newAccount(initialBalance)
local self = {balance = initialBalance}
local withdraw = function (v) self.balance = self.balance - v end
return {withdraw = withdraw}
end
lua的全局变量要设为nil才会被清理,table里面不需要的值也需要设nil才会被清理。
弱引用table,用元表的元方法__mode实现
有三种模式:弱引用key(当key被回收,整个条目都会从table删除),弱引用value(当value被回收,整个条目都会从table删除),同时弱引用key和value(当key或value被回收,整个条目都会从table删除)。
只有对象会被回收。数字和boolean这样的值不会被回收,如果value被设nil了才会被回收
调试库
debug.getinfo等等