官网:http://www.lua.org/
手册:http://www.lua.org/manual/5.2/manual.html
社区:http://lua-users.org/
论坛:http://www.luaer.cn/
在线中文手册:http://manual.luaer.cnhttp://www.codingnow.com/2000/download/lua_manual.html
花了三天多时间看这本书,基本算入门了吧,有些东西因为时间关系没细看,以后再逐个击破。
摘要(2014.4.24):
1.table可用c中的数组跟python中的dict混合组成,作为c数组的那部分索引从1开始
2.对于table A,#A拿到的是A中作为c数组那部分元素的个数(有坑,详见lua随手记item10);ipairs()迭代的也是作为c数组那部分的元素;pairs()迭代的全部
3.默认情况下,用不存在的key直接访问table对象(myobj[mykey])时返回nil。Python dict的话会报错,需要用has_key来判断key是否存在。C++ map的话会在map中新增一个跟key对应的条目,把默认value返回来(如果value是int类型就返回0)。为什么说默认情况下呢,因为lua的table可以用metatable的__index 来定制访问不存在key时的行为,Python的dict默认的__getitem__()貌似(试了一下不行,没深究)做了保护而不可改变,不过继承dict的话应该是可以定制__getitem__()的。
4.默认形参不直接支持,可用一个table参数的成员模拟多个参数来实现
5.多变量赋值时,变量个数大于值个数时多余变量用nil赋值,变量个数小于值个数时多余值丢弃;对函数传参也是如此;函数返回值也是如此
6.做是否为真的判断时false和nil以外的都是真(跟Python不一样,None/False/零数值/空串/空元组/空列表/空字典都是假(自定义类型据__nonzero__()or__len__()而定),其他为真)
[第一章] 起点
Chunk
Chunk 语句块,一个语句/一系列语句的组合/函数(类似于Python中block的概念,不同的是Python的交互模式是一个__main__ module)。e.g. 一个文件,交互模式下的每一行(所以在交互模式下输入一行"local i=1",该局部变量可能只在本行有效,除非该行被do..end等block包含)
分号(;) 可选,一行多语句时最好用分号隔开(更清晰)
退出交互模式(跟Python一致),ctrl-D (for Unix) / ctrl-Z (for dos/windows) / os.exit()
lua -i -la -lb 在一个Chunk中先执行a然后执行b,最后进入交互模式(Python也有这个功能,只是没用过),-l等效于require
dofile("lib1.lua") 加载文件并执行(Python中的import lib1)
-i / dofile 用于调试和测试Lua代码很方便
全局变量
给一个变量赋值即创建了该全局变量,当前仅当一个变量不等于nil时,这个变量存在
词法
标示符:字符下或下划线开头,字母下划线数字组成 (避免使用"下划线+大写字母开头的标示符")
保留字:and break do else else elseif end false for function if in local nil not or repeat return then true until while
Lua大小写敏感
单行注释 --
多行注释 --[[ --]]
命令行方式
-e 执行语句
-l 加载文件
-i 交互模式
_PROMPT 内置变量,存储交互模式提示符
LUA_INIT 环境变量,值为@filename则Lua会加载指定文件,值不以@开头则Lua把其值作为代码块执行(待验证 markbyxds)
arg 全局变量,存放Lua的cli参数,脚本名的index为0,脚本的参数从1开始增加,脚本前面的参数从-1开始减少
[第二章]类型和值
基本类型:nil boolen number string userdata function thread table
Lua允许我们将任何对象当做table的key
type(xxx)测试给定变量或值的类型,Lua是动态性语言,变量不要类型定义(Python也有类似函数,内建type())
Nil 特殊类型,只有一个值(nil);一个全局变量被赋值之前默认值就是nil;给全局变量赋值nil可以删除该变量
Booleans 两个取值false/true; Lua中所有值都可以作为条件;控制结构中除false和nil为假以外其他都为真(0和空串都是真)
Numbers 实数(Lua中没有整数),可以处理任何长整数不用担心误差
Strings 字符串
不可修改(同Python);单引号/双引号都行;用\转义;和其他对象一样自动分配/释放内存;
[[..]]可以包含多行,可嵌套且不会解释转义序列,第一个字符如果是换行符则自动忽略 (类似于Python中用三个单引号包含多行的字符串)
string-numbers自动类型转换:当字符串使用算数操作符时string会被转成数字;Lua期望string而碰到numbers时会将number转换成string
.. 字符串连接,数字后面写..时必须加上空格防止被解释错
10 == “10” ---> false
tonumber(xx) 显式的把string转换成number,如果xx不能转成number就返回nil
Functions
函数是第一类值(和其他变量相同;第一类值(first-class values)指,一个对象(广义概念上)在运行期间被创建,可以当做一个参数被传递)
函数可以被存储在变量中,可以作为函数的参数和返回值
Lua可以调用lua或者c实现的函数,Lua的所有标准库(string table io os 算术 debug)都是用c实现的
Userdata and Threads userdata 用于描述应用程序或c库创建的新类型
[第三章]表达式
关系运算符 < > <= >= == ~=
==和~=,如果类型不同Lua认为两者不同;
nil只和自己相等;
通过引用比较tables、userdata、functions(也就是说当前仅当两者表示同一对象时相等)(可改写metatabl的__eq方法,类似于Python中的__eq__)
数字按照传统数字大小比较
字符串按照字母顺序进行,但是字母顺序依赖于本地环境(不太明白 markbyxds)
不能用大于小于比较数字和字符串(以及不同类型变量)
逻辑运算符 and or not
and 优先级高于 or
false和nil是假(false),其他(包括0)是真(true)
and/or的运算结果不是true/false:a and b, 如果a为false则返回a,否则返回b; a or b, 如果a为true则返回a,否则返回b
not的运算结果是true/false
x = x or v 如果x为false/nil则给x赋初值v(if not x then x = v end)
"(a and b) or c" 等效于c语言的三元运算符 "a?b:c" (WARNING:小心b的类型,如果b的类型是布尔值是false,那么就无法达到预期效果了)
> print(true and false or true) true > print(true and false or "a") a > print(false and false or "a") a
连接运算符 .. 字符串连接;如果操作数字,Lua将数字转成字符串(前面的数字跟连接符之间需要有空格)
优先级从高到低:
除^和..外所有二元运算符都是左连接的(左连接?markbyxds)^ not -(unary) * / + - .. < > <= >= ~= == and or
表的构造
混合list和record初始化a.x = nil --> remove field 'x' list: a = {"Sunday", "Monday"} (索引从1开始) <===> a = {[1]="Sunday",[2]="Monday"} record: a = {x=0,y='hello'} <===> a = {}; a.x = 0; a.y = 'hello' <===> a = {['x']=0,['y']='hello'} linked list: > for line in io.lines() do l = {next = l, value = line} end hi hello why? > print(l) table: 0x167e530 > while l do print(l.value); l = l.next; end why? hello hi
v = {color="blue", week="Monday", {x=0,y=0}, {x=1,y=1}}
一般初始化(list风格初始化和record风格初始化是一般初始化的特例)
opnames = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"} i = 20; s = "-" a = {[i+0] = s, [i+1] = s..s, [i+2] = s..s..s} print(opnames[s]) --> sub print(a[22]) --> ---
[第四章]基本语法
赋值语句
变量个数与值的个数不一致时:变量个数>值的个数,则按变量个数补足nil; 变量个数<值的个数,则多余的值会被忽略( Python会报错)a, b = 10, 2*x <===> a = 10; b = 2*x x, y = y, x -- swap 'x' for 'y' (Lua先计算右边所有的值然后再进行赋值操作) a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'
局部变量和代码块(block)
local 创建局部变量,只在被声明的代码块内有效
block 代码块,一个控制结构/函数体/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)
应尽可能使用局部变量,好处:避免命名冲突;访问局部变量的速度比全局变量快
用do..end可以给block划定明确的界限以控制局部变量的作用范围,等效于c/c++中的{}
控制结构语句
if .. then .. end; if .. then .. else .. end; if .. then .. elseif .. then .. else .. end; while .. do .. end; repeat .. until ..;
数值for循环:for var=exp1,exp2,exp3 do .. end;
exp1初始值 exp2终止值 exp3是每次循环对v的增量,缺省为1;
三个表达式只在循环开始前计算一次;
控制变量var是只在循环内有效的被自动声明的局部变量;
循环过程中不要改变控制变量var的值,结果不可预知;可用break退出循环(没有continue,空语句即可)
泛型for循环:
for i,v in ipairs(a) do print(v) end-- print all values of array 'a'
for k in pairs(t) do print(k) end-- print all keys of table 't'
(pairs()函数基本和ipairs()函数用法相同, 区别在于ipairs只返回从1开始的连续数字索引部分)
控制变量var是只在循环内有效的被自动声明的局部变量;
循环过程中不要改变控制变量var的值,结果不可预知;可用break退出循环
break & return
break退出当前循环(for,repeat,while),循环外部不可用break
return从函数返回结果,函数自然结束时候结尾会有默认的return
break/return只能出现在block的结尾一句(作为chunk的最后一句/end之前/else之前/until之前),为了调试而需特殊使用时可加上do..end来实现
[第五章]函数
语法
function func_name (arguments-list) statements-list; end;
当函数只有一个参数且参数是字符串或表构造时,调用时可省略():print "hello,world"; type {}; f {x=10, y=20};
实参和形参的匹配跟赋值语句类似,忽略多余部分,nil补足缺少部分 (Python会报错)
圆括号强制性的使函数调的返回变成一个值(不再是多个)
调用可变参数的可变函数:f(unpack(a)) (unpack,接受一个数组作为输入参数,返回数组的所有元素)
可变参数(...)在名为arg的表中,arg中有个名为n的域记录参数个数
今天(2014.6.5)在项目中遇到一个错误attempt to index global 'arg' (a nil value)
原因:将变参自动存储到arg这个table,这是lua 4.x版的默认行为。而在lua 5.x系列,这个特性被改成了可配置特性。如果打开它,行为和lua 4.x一致,仍然可以使用arg来访问,但代价是每次函数调用,虚拟机都要自动构造arg这个table,影响效率。所以如果要提高效率,可以通过配置关闭这个特性,即只能用...的方式访问变参。可以用local args = {...}把参数转过来。
哑元(dummy variable,下划线) _
文本格式化的函数string.format(类似C语言的sprintf函数)
命名参数的实现:将所有的参数放在一个表中,把表作为函数的唯一参数
返回值个数跟接受结果的变量个数不一致时,赋值规则跟传参规则一样(Python规则不一样:如果是多返回值给一个变量,那么会作为tuple赋值;如果是多返回值给多个变量而个数不一致就会报错)
[第六章]再论函数
Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
第一类值指:在Lua中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
词法定界指:嵌套的函数可以访问他外部函数中的变量(在嵌套函数内这个变量被叫做“外部的局部变量”/"upvalue")。(Python也有这个,闭包!)
Lua中关于函数稍微难以理解的是函数也可以没有名字,匿名的。(Python也有这个,lambda!)
function foo (x) return 2*x end <===> foo = function (x) return 2*x end
局部函数像局部变量一样在一定范围内有效
尾调用(尾递归),函数的最后一个动作是调用另外一个函数。处理尾调用时不需要额外的栈(层次无限制、不会栈溢出)就表示语言(Lua)支持正确的尾调用
[第七章]迭代器与泛型for
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素
要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)。
范性for:
for
in do end
泛型for执行过程:(什么泛型、无状态什么玩意儿的把人都搞晕了,反正写自定义遍历和迭代照着下面这个流程写就是了!)
首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
第三,将迭代函数返回的值赋给变量列表。
第四,如果返回的第一个值(即,控制变量)为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数。
如果我们的迭代函数是f,状态常量是s,控制变量的初始值是a0,那么控制变量将循环:a1=f(s,a0)、a2=f(s,a1)、……,直到ai=nil。
无状态的迭代器
无状态的迭代器是不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量)
当Lua调用ipairs(a)开始循环时,他获取三个值:迭代函数iter、状态常量a、控制变量初始值0;然后Lua调用iter(a,0)返回1,a[1](除非a[1]=nil);第二次迭代调用iter(a,1)返回2,a[2]……直到第一个非nil元素。
多状态的迭代器(两种方式)
1.闭包
2.将所有状态信息封装到table内,将table作为迭代器的状态常量
[第八章]编译·运行·错误信息
Lua会首先把代码预编译成中间码然后再执行(Python会先把代码编译成字节码然后执行)
loadfile 编译代码成中间码并返回编译后的chunk作为一个函数,loadfile不抛出错误而是返回错误码
运行多次的时候loadfile只编译一次,但可多次运行;dofile需要每次都编译(loadfile和dofile的关系见下面一小段代码)
loadstring类似于loadfile,只是从字符串中读入;loadstring总是在全局环境中编译他的串
function dofile (filename) local f = assert(loadfile(filename)) return f() end
require函数
require跟dofile的区别:
1.require会搜索目录加载文件
2.require会判断是否文件已经加载避免重复加载同一文件
_LOADED 加载过的虚文件名
_REQUIREDNAME 保存被required的虚文件的文件名
C Packages
print(loadlib())检测该版本lua是否支持动态链接机制
loadlib(库的绝对路径,初始化函数)
错误
error(err_str, framenum)显式抛出错误,第一个参数是要抛出的错误信息,第二个参数是错误发生的层级
assert(exp, err_str) 检测第一个参数,若没问题不做任何事情;否则以第二个参数作为错误信息抛出
处理异常是返回错误代码还是抛出错误,原则是:对于程序逻辑上能避免的异常以抛出错误方式处理,否则返回错误代码
pcall(func, arg_for_func)在保护模式(protected mode)下执行函数内容,同时捕获所有的异常和错误。若一切正常,pcall返回true以及“被执行函数”的返回值;否则返回nil和错误信息(错误信息不一定仅为字符串,传递给error的任何信息都会被pcall返回)
pcall返回错误信息时已经释放了保存错误发生情况的栈信息
xpcall(function,error_function),发生错误时在释放栈信息之前调用error_function。这样就有机会使用debug库收集错误信息:debug.debug() debug.traceback()
function func()
assert(false)
end
a,b = pcall(func)
print(a,b) --false E:\lua-study\pcall.lua:2: assertion failed!
a,b = xpcall(func, "error")
print(a,b) --false error in error handling
[第九章]协同程序 时间关系暂时略过 貌似跟Python的stackless的微进程之类的比较类似 markbyxds
[第十一章]数据结构
数组
通过整数下标访问table中元素,即是数组。并且数组大小不固定,可动态增长
习惯上,Lua的数组下标从1开始。Lua的标准库遵循此惯例,因此你的数组下标必须也是从1开始,才可以使用标准库的函数
矩阵
两种实现:数组的数组;行列组合作为索引
稀疏举证,矩阵的大部分元素都为空或者0的矩阵(用table实现的数据本身天生的就具有稀疏的特性)
链表
每一个节点是一个table,指针是这个表的一个域(field),并且指向另一个节点(table)
队列和双向队列
其实就是把创建table作为队列的函数、插入数据的函数、弹出数据的函数都放到另一个table里面(不放也不要紧啊,只是为了避免全局命名空间的污染),然后调用这个table的属性来完成相关操作
说白了就是把数据和两个额外的标记('first','last')存到table中,用1、2、3..做数据索引操作数据,用'first'/'last'作索引存放队列头尾的索引值
集合和包
Lua中表示集合的方法,将所有集合中的元素作为下标存放在一个table里,只需要测试看对于给定的元素,表的对应下标的元素值是否为nil就知道是否存在于集合中了
字符串缓冲
Lua使用真正的垃圾收集算法,但他发现程序使用太多的内存他就会遍历他所有的数据结构去释放垃圾数据
当Lua执行".."字符串连接时,它创建一个新的字符串,并且从老串中把数据拷贝到新串中。老的字符串变成了垃圾数据,垃圾数据占的内存达到阀值时Lua会进行垃圾收集
table.concat函数可以将一个列表的所有串合并(table.contact是用C语言实现的,处理速度很快)
[第十二章]数据文件与持久化 (lua居然没有专门的序列化功能库,其他脚本大多会有,如Python的pickle/cpickle/msgpack)
相对CSV和其他紧缩格式,自描述数据格式更容易阅读和理解
数据描述是Lua的主要应用之一,从Lua发明以来,我们花了很多心血使他能够更快的编译和运行大的chunks
为了以安全的方式引用任意的字符串,string标准库提供了格式化函数专门提供"%q"选项。它可以使用双引号表示字符串并且可以正确的处理包含引号和换行等特殊字符的字符串
[第十三章]Metatables and Metamethods (元表Metatable跟Python的元类还有些区别,Python中的元类是创建类的类,不过跟Python中的各magic methods有些异曲同工之妙)
说白了,一个对象的元表就记录着这个对象某些特定操作需要调用的函数
算数运算的Metatables
两个table相加: a+b, 检查a或b是否有Metatable以及其Metatable是否有__add域,如找到则调用该__add函数(所谓的Metamethod)去计算结果.(类似于Python的__add__(http://docs.python.org/2/reference/datamodel.html#object.__add__))
Lua默认创建一个不带metatable的新表
getmetatable(tablename)
setmetatable(tablename, metatable_name)
任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)
对于每一个算术运算符,metatable都有对应的域名与其对应,除了__add、__mul外,还有__sub(减)、__div(除)、__unm(负)、__pow(幂),我们也可以定义__concat定义连接行为
对于加法,Lua选择metamethod的规则:
检测第一个参数是否存在带有__add域的metatable,是则使用该函数做运算
检测第二个参数是否存在带有__add域的metatable,是则使用该函数做运算
报错
关系运算的Metatables
__eq(等于),__lt(小于),和__le(小于等于)(对剩下的三个关系运算符没有专门的metamethod,因为Lua将a ~= b转换为not (a == b);a > b转换为b < a;a >= b转换为 b <= a)
关系元算的metamethods不支持混合类型运算:比较不同类型的对象,Lua将抛出错误;比较两个带有不同metamethods的对象,Lua也将抛出错误
相等比较从来不会抛出错误,如果两个对象有不同的metamethod,比较的结果为false,甚至可能不会调用metamethod;仅当两个有共同的metamethod的对象进行相等比较的时候,Lua才会调用对应的metamethod
库定义的Metamethods
print函数总是调用tostring来格式化它的输出,然而当格式化一个对象的时候,tostring会首先检查对象是否存在一个带有__tostring域的metatable。如果存在则以对象作为参数调用对应的函数来完成格式化,返回的结果即为tostring的结果。
setmetatable/getmetatable函数调用也会使用对象的metafield(metatable中的域__metatable)。假定你想保护你的集合使其使用者既看不到也不能修改metatables。如果你对metatable设置了__metatable的值,getmetatable将返回这个域的值,而调用setmetatable 将会出错.
__index metamethod (类似于Python中的__getattr__(http://docs.python.org/2/reference/datamodel.html#object.__getattr__))
print(w.width),当Lua发现w不存在域width时,但是有一个metatable带有__index域,Lua使用w(the table)和width(缺少的值)来调用__index metamethod并把结果返回
简写升级:__index metamethod不需要非是一个函数,他也可以是一个表。当它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数;当它是一个表的时候,Lua将在这个表中看是否有缺少的域。
rawget(t,i)绕过__index metathod直接访问表
__newindex Metamethod
__newindex metamethod用来对表更新,__index则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod:如果存在则调用这个函数而不进行赋值操作.
__newindex metamethod可以跟__index一样也是个table,这样就直接对该table做赋值操作
rawset(t,k,v)不调用任何metamethod直接对表t的k域赋值为v
有默认值的表
如何让带有不同默认值的表mytable可以重用相同的元表mymt:
a.把默认值赋给mytable的一个唯一key(可用新建的table作为key来保证唯一),并对mytable的元表mymt设置__index域,该域对应的函数从mytable上获取唯一key对应的value
b.使用一个分开的表来处理,在这个特殊的表中索引是表,对应的值为默认值(需要weak table)
c.memoize metatables(需要weak table)
监控表
如果我们想监控一个表的所有访问情况,我们应该为真实的表创建一个代理。这个代理是一个空表,并且带有__index和__newindex metamethods,由这两个方法负责跟踪表的所有访问情况并将其指向原始的表
只读表
采用代理的思想很容易实现一个只读表。我们需要做得只是当我们监控到企图修改表时候抛出错误
这种用法要求每一个只读代理有一个单独的新的metatable,使用__index指向原始表 (为了重用metatable还是可以像监控表的代理那样用__index来对应一个函数而不是table啊!)
[第十四章]环境
全局变量_G记录所有全局变量(_G._G等于_G)(类似于Python的globals(),前者用table记录所有全局符号跟全局对象之间的对应关系,后者用dict记录;插述一句,Lua对table的依赖和使用密集度跟Python对dict一样样的!正因为这个原因,Python的dict选用了高效的hash table做存储,Lua的话还不清楚。)
使用动态名字访问全局变量
操纵一个名字被存储在另一个变量中的全局变量,或者需要在运行时才能知道的全局变量:
loadstring("value = " .. varname)()
value = loadstring("return " .. varname)()
更高效更简洁的方式:
function declare (name, initval)
rawset(_G, name, initval or false)
end
非全局的环境
function func()
a = 1
local newgt = {} -- create new environment
setmetatable(newgt, {__index = _G})
setfenv(1, newgt) -- set it
print(a) --> 1 _G.a
a = 10
print(a) --> 10 当前环境的a
print(_G.a) --> 1 _G.a (如果没对newgt的metatable做__index=_G设置的话,这里是找不到名为"_G"的全局变量的)
_G.a = 20
print(_G.a) --> 20 _G.a
local t = getfenv()
for k,v in pairs(t) do --> {a=10}
print(k,v)
end
end
func()
local P = {} -- package
if _REQUIREDNAME == nil then
complex = P
else
_G[_REQUIREDNAME] = P
end
使用全局表
local P = {} -- package
if _REQUIREDNAME == nil then
complex = P -->package名是complex
else
_G[_REQUIREDNAME] = P -->package名是_G[_REQUIREDNAME],也就是变量_REQUIREDNAME的值
end
setfenv(1, P)
要在package内部访问以前的全局变量的话,可以使用继承:必须在调用setfenv之前调用setmetatable,否则在当前环境会找不到setmetatable函数!
local P = {}
pack = P
local _G = _G
setfenv(1, P)
或者只把需要的函数或者packages声明为local:
local P = {}
pack = P
-- Import Section:
-- declare everything this package needs from outside
local sqrt = math.sqrt
local io = io
-- no more external access after this point
setfenv(1, P)
Account = {}
function Account:PrintSex() --等价于 function Account.PrintSex(Account) print Account.sex end
print(self.sex)
end
function Account:new(o)
o = o or {}
setmetatable(o, self) -->o的元表metatable设置为Account
self.__index = self -->访问o不存在的域时从Account的域里面取
return o
end
a = Account.new(Account, {sex='male'}) -->Account作为a的prototype <==> Account是class a是Account的instance
a:PrintSex() -->a没有PrintSex的域(方法),从元表Account中获取该域
b = Account:new({sex='female'})
b.PrintSex(a)
继承
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
function Account:withdraw (v)
if v > self.balance then error"insufficient funds" end
self.balance = self.balance - v
end
SpecialAccount = Account:new() ---> class(prototype):Account instance:SpecialAccount
s = SpecialAccount:new{limit=1000.00} ---> class(prototype):SpecialAccount instance:s
s:deposit(100.00) ---> Account.deposit(s, 100.00)
function SpecialAccount:withdraw (v) ---> 重定义从父类继承来的方法
if v - self.balance >= self:getLimit() then
error"insufficient funds"
end
self.balance = self.balance - v
end
function SpecialAccount:getLimit ()
return self.limit or 0
end
function s:getLimit () ---> 修改特定对象s的方法
return self.balance * 0.10
end
s:getLimit() -->s.getLimit(s)
s:withdraw(200.00) --> SpecialAccount.withdraw(s,200.00) (withdraw内部调用getLimit的时候调的是s.getLimit(s)不是SpecialAcount.getLimit)
多重继承
-- look up for `k' in list of tables 'plist'
local function search (k, plist)
for i=1, table.getn(plist) do
local v = plist[i][k] -- try 'i'-th superclass
if v then return v end
end
end
function createClass (...)
local c = {} -- new class
-- class will search for each method in the list of its
-- parents (`arg' is the list of parents)
setmetatable(c, {__index = function (t, k) return search(k, arg) end})
--optimized for method search
--[[
setmetatable(c, {__index = function (t, k)
local v = search(k, arg)
t[k] = v -- save for next access
return v
end
}
)
--]]
-- prepare `c' to be the metatable of its instances
c.__index = c
-- define a new constructor for this new class
function c:new (o)
o = o or {}
setmetatable(o, c)
return o
end
-- return new class
return c
end
私有性(privacy)
function newAccount (initialBalance)
local self = { -->私有数据 私有方法都可以放到这个table里面
balance = initialBalance,
LIM = 10000.00,
}
function self.extra() -->私有方法
if self.balance > self.LIM then
return self.balance*0.10
else
return 0
end
end
local withdraw = function (v) self.balance = self.balance - v end
local deposit = function (v) self.balance = self.balance + v end
local getBalance = function () return self.balance + self.extra() end
return {
withdraw = withdraw,
deposit = deposit,
getBalance = getBalance
}
end
acc1 = newAccount(10010.00)
acc1.withdraw(40.00)
print(acc1.getBalance())
function newObject (value)
return function (action, v)
if action == "get" then return value
elseif action == "set" then value = v
else error("invalid action")
end
end
end
d = newObject(0)
print(d("get")) --> 0
d("set", 10)
print(d("get")) --> 10
a = {}
b = {}
setmetatable(a, b)
b.__mode = "k" -- now 'a' has weak keys
key = {} -- creates first key
a[key] = 1
key = {} -- creates second key
a[key] = 2
collectgarbage() -- forces a garbage collection cycle
for k, v in pairs(a) do print(v) end --> 2
要注意,只有对象才可以从一个weak table中被收集。比如数字和布尔值类型的值,都是不会被收集的。例如,如果我们在table中插入了一个数值型的key(在前面那个例子中),它将永远不会被收集器从table中移除。当然,如果对应于这个数值型key的vaule被收集,那么它的整个入口将会从weak table中被移除。
local results = {}
setmetatable(results, {__mode = "v"}) -- make values weak
function mem_loadstring (s)
if results[s] then -- result available?
return results[s] -- reuse it
else
local res = loadstring(s) -- compute new result
results[s] = res -- save for later reuse
return res
end
end
关联对象属性
local defaults = {}
setmetatable(defaults, {__mode = "k"})
local mt = {__index = function (t) return defaults[t] end}
function setDefault (t, d)
defaults[t] = d
setmetatable(t, mt)
end
t = {1,2,3,4,5}
setDefault(t, 100)
for k,v in pairs(t) do
print(k,v)
end
print("t['x']", t['x'])
使用不同的metatables来保存不同的默认值,但当我们重复使用一个默认值的时候,重用同一个相同的metatable:
local metas = {}
setmetatable(metas, {__mode = "v"})
function setDefault (t, d)
local mt = metas[d]
if mt == nil then
mt = {__index = function () return d end}
metas[d] = mt -- memoize
end
setmetatable(t, mt)
end
t = {1,2,3,4,5}
setDefault(t, 1000)
for k,v in pairs(t) do
print(k,v)
end
print("t['x']",t['x'])
'n' selects fields name and namewhat
'f' selects field func
'S' selects fields source, short_src, what, and linedefined
'l' selects field currentline
'u' selects field nup
debug.getlocal
local Counters = {}
local Names = {}
local function hook ()
local f = debug.getinfo(2, "f").func
if Counters[f] == nil then -- first time `f' is called?
Counters[f] = 1
Names[f] = debug.getinfo(2, "Sn")
else -- only increment the counter
Counters[f] = Counters[f] + 1
end
end
function getname (func)
local n = Names[func]
if n.what == "C" then
return n.name
end
local loc = string.format("[%s]:%s",
n.short_src, n.linedefined)
if n.namewhat ~= "" then
return string.format("%s (%s)", loc, n.name)
else
return string.format("%s", loc)
end
end
local f = assert(loadfile("e:\\lua-study\\weaktable_function.lua"))
debug.sethook(hook, "c") -- turn on the hook
f() -- run the main program
debug.sethook() -- turn off the hook
for func, count in pairs(Counters) do
print(getname(func), count)
end
Lua库没有定义任何全局变量,它所有状态保存在动态结构lua_State中,其指针作为所有Lua函数的参数
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#ifdef __cplusplus
}
#endif
int main (void)
{
char buff[256];
int error;
lua_State *L = lua_open(); /* opens Lua */
luaopen_base(L); /* opens the basic library */
luaopen_table(L); /* opens the table library */
// luaopen_io(L); /* opens the I/O library */ //PANIC: unprotected error in call to Lua API (no calling environment)
luaL_openlibs(L); //instead of last line: luaopen_io(L);
luaopen_string(L); /* opens the string lib. */
luaopen_math(L); /* opens the math lib. */
while (fgets(buff, sizeof(buff), stdin) != NULL) {
error = luaL_loadbuffer(L, buff, strlen(buff),"line") || lua_pcall(L, 0, 0, 0);
if (error) {
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1);/* pop error message from the stack */
}
}
lua_close(L);
getchar();
return 0;
}
堆栈
Lua和C之间交换数据时面临着两个问题:动态与静态类型系统的不匹配和自动与手动内存管理的不一致( Python面临的是引用计数到底加还是减的问题)void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t length); //函数返回后可随意修改和释放s,Lua内部已做拷贝等处理
void lua_pushstring (lua_State *L, const char *s); //同上
int lua_checkstack (lua_State *L, int sz);
查询元素:
int lua_is...(lua_State *L, int index) //lua_istable..,检查元素类型 lua_isnumber,lua_isstring检查元素能否被转换成指定类型
lua_type函数返回栈中元素的类型
int lua_toboolean (lua_State *L, int index); //类型不匹配时返回0
double lua_tonumber (lua_State *L, int index); //类型不匹配时返回0
const char * lua_tostring (lua_State *L, int index); //类型不匹配时返回NULL
size_t lua_strlen (lua_State *L, int index); //类型不匹配时返回0
lua_tostring函数返回一个指向字符串的内部拷贝的指针(const char*)。只要这个指针对应的值还在栈内,Lua会保证这个指针一直有效。当一个C函数返回后,Lua会清理他的栈,所以,有一个原则:永远不要将指向Lua字符串的指针保存到访问他们的外部函数中(有点晕 markbyxds )
lua_tostring返回的字符串结尾总会有一个字符结束标志0,但是字符串中间也可能包含0,lua_strlen返回字符串的实际长度
其他堆栈操作
int lua_gettop (lua_State *L); //返回堆栈中的元素个数,即栈顶元素的索引
void lua_settop (lua_State *L, int index); //重置栈中元素个数,缩小就抛弃栈顶元素,变大就用nil填充栈顶位置
void lua_pushvalue (lua_State *L, int index); //copy栈上index对应的元素到栈顶
void lua_remove (lua_State *L, int index); //移除index对应的元素,并把栈顶元素下移
void lua_insert (lua_State *L, int index); //把栈顶元素移动到index对应的位置,其他元素上移
void lua_replace (lua_State *L, int index); //弹出栈顶元素,并用它覆盖index对应的元素
示例:
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#ifdef __cplusplus
}
#endif
static void stackDump (lua_State *L)
{
int i;
int top = lua_gettop(L);
for (i = 1; i <= top; i++) { /* repeat for each level */
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: /* strings */
printf("`%s'", lua_tostring(L, i));
break;
case LUA_TBOOLEAN: /* booleans */
printf(lua_toboolean(L, i) ? "true" : "false");
break;
case LUA_TNUMBER: /* numbers */
printf("%g", lua_tonumber(L, i));
break;
default: /* other values */
printf("%s", lua_typename(L, t));
break;
}
printf(" "); /* put a separator */
}
printf("\n"); /* end the listing */
}
void stack_test()
{
lua_State *L = lua_open();
lua_pushboolean(L, 1);
lua_pushnumber(L, 10);
lua_pushnil(L);
lua_pushstring(L, "hello");
stackDump(L); /* true 10 nil `hello' */
lua_pushvalue(L, -4); stackDump(L); /* true 10 nil `hello' true */
lua_replace(L, 3); stackDump(L);/* true 10 true `hello' */
lua_settop(L, 6); stackDump(L); /* true 10 true `hello' nil nil */
lua_remove(L, -3); stackDump(L); /* true 10 true nil nil */
lua_settop(L, -5); stackDump(L); /* true */
lua_close(L);
getchar();
}
int main (void)
{
//helloworld();
stack_test();
return 0;
}
C API的错误处理(时间问题暂不深究)
setjmp( http://www.cplusplus.com/reference/csetjmp/setjmp/?kw=setjmp)[第二十五章]扩展你的程序
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#ifdef __cplusplus
}
#endif
void load (char *filename, int *width, int *height) {
lua_State *L = lua_open();
luaopen_base(L);
luaL_openlibs(L);
luaopen_string(L);
luaopen_math(L);
if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
luaL_error(L, "cannot run configuration file: %s", lua_tostring(L, -1));
lua_getglobal(L, "width");
lua_getglobal(L, "height");
if (!lua_isnumber(L, -2))
luaL_error(L, "`width' should be a number\n");
if (!lua_isnumber(L, -1))
luaL_error(L, "`height' should be a number\n");
*width = (int)lua_tonumber(L, -2);
*height = (int)lua_tonumber(L, -1);
lua_close(L);
}
int main (void)
{
//helloworld();
//stack_test();
int a, b;
load("config.lua", &a, &b);
printf("a=%d,b=%d", a, b);
getchar();
return 0;
}
表操作
LUA_API void (lua_settable) (lua_State *L, int idx); //将栈顶的key(-2)和value(-1)出栈,用这两个值设置栈中以inx为索引存储的table