13 元表(metatable)与元方法(metamethod)
1 开始
Hello World
print("Hello World");
程序块(chunk)
一个程序块也就是一连串的语句或命令
几条连续的Lua语句之间并不需要分隔符,但如果愿意,也可以使用分号来分隔语句.
Lua通常还被作为一种数据描述语言来使用,几兆字节的程序块也是很常见的.
交互模式:
退出交互模式:UNIX(Ctrl+D),Windows(Ctrl+Z). os.exit()
运行完指定程序后进入交互模式:%lua -i prog
交互模式中使用dofile函数立即执行一个文件:
词法规范
Lua中的标识符可以是由任意字母,数字和下划线构成的字符串,但不能以数字开头
应该避免使用以一个下划线开头并跟着一个或多个大写字母的标识符,Lua将这类标识符保留用作特殊用途
Lua是有大小写之分的
行注释(--),块注释(--[[ ]])
全局变量
全局变量不需要声明
在Lua中,访问一个未初始化的变量不会引发错误,访问结果是一个特殊的值nil
解释器程序(the dtand-slone interpreter)
解释器是一个小型的程序,可以通过它来直接使用Lua
解释器程序的用法:lua [选项参数] [脚本[参数]]
选项参数"-e"可以直接在命令行中输入代码:
选项参数"-l"用于加载库文件.
在脚本代码中,可以通过全局变量arg来检索脚本的启动参数:%lua 脚本 a b c
2 类型与值
Lua是一种动态类型的语言.在语言中没有类型定义的语法.每个值都"携带"了它自身的类型信息
在Lua中,函数是作为"第一类值(first-class value)"来看待的,可以像操作其他值一样来操作一个函数值
nil(空)
nil是一种类型,它只有一个值nil,它的主要功能是用于区别其他任何值.
boolean(布尔)
boolean类型有两个可选值:false和true,这与传统的布尔值一样.然而boolean却不是一个条件值的唯一表示方式.在Lua中任何值都可以表示一个条件.Lua将值false和nil视为"假",而将除此之外的其他值视为"真".Lua在条件测试中, 将数字零和空字符串也都视为"真"
number(数字)
number类型用于表示实数.Lua没有整数类型.
string(字符串)
Lua完全采用8位编码,Lua字符串中的字符可以具有任何数值编码,包括数值0.也就是说,可以将任意二进制数据存储到一个字符串中
Lua的字符串是不可变的值(immutable values)
Lua的字符串和其他Lua对象(例如table或函数等)一样,都是自动内存管理机制所管理的对象
Lua提供了运行时的数字与字符串的自动转换
在Lua中,".."是字符串连接操作符.当直接在一个数字后面输入它的时候,必须要用一个空格来分隔它们.不然,Lua会将第一个点理解为一个小数点.
若要将一个数字转换成字符串,可以调用函数tostring,或者将数该数字与第一个空字符串相连接
在Lua5.1中,可以在字符串前放置操作符"#"来获得该字符串的长度
table(表)
在Lua中,table既不是"值"也不是"变量",而是"对象".可以将一个table想象成一种动态分配的对象,程序仅持有一个对它们的引用(或指针),Lua不会暗中产生table的副本或创建新的table.此外,在Lua中也不需要声明一个table.事实上也没有办法可以声明table.table的创建是通过"构造表达式(constructor expression)"完成的,最简单的构造表达式是{}.
table永远是"匿名的(anonymous)",一个持有table的变量与table自身之间没有固定的关联性.
当一个程序再也没有对一个table的引用时,Lua的垃圾收集器(garbage collector)最终会删除该table,并复用它的内存
function(函数)
在Lua中,函数是作为"第一类值"来看待的.这表示函数可以存储在变量中,可以通过参数传递给其他函数,还可以作为其他函数的返回值.
userdata(自定义类型)和thread(线程)
由于userdata类型可以将任意的C语言数据存储到Lua变量中.在Lua中,这种类型没有太多的预定义操作,只能进行赋值和相等性测试.
3 表达式
算术操作符
Lua支持常规的算术操作符有: + - * / ^ % -(负号)
关系操作符
Lua提供了以下关系操作符: < > <= >= == ~= 所有这些操作符的运算结果都是true或false 特别需要说明的是,nil只与其自身相等
对于table,userdata和函数,Lua是作引用比较的.也就是说,只有当它们引用同一个对象时,才认为它们相等.
只能对两个数字或两个字符串作大小性比较.而数字和字符串之外的其他类型只能进行相等性或不等性比较.
当对两个不同类型的值作比较时,要格外小心.请记住,"0"与0是不同的
逻辑操作符
逻辑操作符有and,or和not.与条件控制语句一样,所有的操作符将false和nil视为假,而将其他的任何东西视为真.
and和or都是使用"短路求值(short-cut evaluation)"
有一种常用的Lua习惯写法"x=x or v",它等价于 if not x then x = v end
字符串连接
要在Lua中连接两个字符串,可以使用操作符"..".如果其任意一个操作数是数字的话,Lua会将这个数字转换成一个字符串
请记住,Lua中的字符串是不可变的值(immutable value).连接操作符只会创建一个新字符串,而不会对其原操作数进行任何修改.
优先级
在二元操作符中,除了指数操作符"^"和连接操作符".."是"右结合"的,所有其他操作符都是"左结合(left associative)"的.
table构造式(table constructor)
最简单的构造式就是一个空构造式{},用于创建一个空table
列表风格:
记录风格:
记录风格与列表风格的混合:
通用格式:
无论是列表风格的初始化,还是记录风格的初始化,起始都是这种通用语法特例.{x=0,y=0}等价于{["x"]=0,["y"]=0},{“r","g","b"}等价于{[1]="r",[2]="g",[3]="b"}
不推荐在Lua中以0作为数组的起始索引
4 语句
赋值
赋值的基本含义(assignment)的基本含义是修改一个变量或一个table中字段的值
Lua允许"多重赋值"
在多重赋值中,Lua先对等号右边的所有元素求值,然后才执行赋值.这样可以用一句多重赋值来交换两个变量
Lua总是会将等号右边值的个数调整到与左边变量的个数相一致.
多重赋值的常见用法,交换两个变量,收集函数的多个返回值
局部变量和块(block)
Lua通过local语句来创建局部变量
与全局变量不同的是,局部变量的作用域仅限于声明它们的那个块.一个块(block)是一个控制结构的执行体,或者是一个函数的执行体再或者是一个程序块(chunk)
交互模式中每行输入内容自身就形成了一个程序块.使用do-end显示界定一个块
控制结构
用于条件执行的if,用于迭代的while,repeat和for.所有的控制结果都有一个显示的终止符:if,for和while以end作为结尾,repeat以until作为结尾
Lua不支持switch
在Lua中一个声明在循环体中的局部变量的作用域包括了条件测试
for语句有两种形式:数字型for(numeric for)和泛型for(generic for).
数字型for循环和泛型for循环有两个相同点: 1.循环变量是循环体的局部变量. 2.绝不应该对循环变量作任何赋值
数字型for的语法如下:
for var=exp1,exp2,exp3 do
<执行体>
end
var 从exp1变化到exp2,每次变化都以exp3作为步长(step)递增var,并执行一次"执行体".第三个表达式exp3是可选的,若不指定的话,Lua会将步长默认为1
泛型for循环通过一个迭代器(iterator)函数来遍历所有值
标准库提供了几种迭代器,包括用于迭代文件中每行的(io.line),迭代table元素的(pairs),迭代数组元素的(ipairs),迭代字符串中单词的(string.gmatch)
break与return
break和return语句用于跳出当前的块
5 函数
Lua为面向对象式的调用也提供了一种特殊的语法---冒号操作符.表达式o.foo(o,x)的另一种写法是o:foo(x),冒号操作符使调用o.foo时将o隐含地作为函数的第一个参数.
多重返回值(multiple results)
关于多重返回值还要介绍一个特殊函数--unpack.它接受一个数组作为参数,并从下标1开始返回该数组的所有元素:
unpack的一项重要用途体现在"泛型调用(generic call)"机制中.泛型调用机制可以动态地以任何实参来调用
变长参数(variable number of arguments)
具名实参(named arguments)
6 深入函数
closure(闭合函数)
非全局的函数(non-global function)
正确的尾调用(propertailcall)
7 迭代器与泛型for
迭代器与closure
泛型for的语义
无状态的迭代器
1 a = {"one","two","three"} 2 for i,v in ipairs(a) do 3 print(i,v) 4 end 5 6 local function iter(a,i) 7 i = i + 1 8 local v = a[i] 9 if v then 10 return i,v 11 end 12 end 13 14 function ipairs(a) 15 return iter,a,0 16 end 17 18 function pairs(t) 19 return next,t,nil 20 end 21 22 for k,v in next,t do 23 <loop body> 24 end 25 26 local function getnext(list,node) 27 if not node then 28 return list 29 else 30 return node.next 31 end 32 end 33 34 function traverse(list) 35 return getnext,list,nil 36 end 37 38 list = nil 39 for line in io.lines() do 40 list = {val = line,next = list} 41 end 42 43 for node in traverse(list) do 44 print(node.val) 45 end
具有复杂状态的迭代器
真正的迭代器
8 编译,执行与错误
编译
C代码
错误(error)
错误处理与异常
错误消息与追溯(traceback)
9 协同程序(coroutine)
协同程序基础
1 co = coroutine.create(function() print("hi") end) 2 print(co) --thread:00B5C840 3 4 print(coroutine.status(co)) --suspended 5 6 coroutine.resume(co) --hi 7 8 print(coroutine.status(co)) --dead 9 10 co = coroutine.create(function () 11 for i = 1,3 do 12 print("co",i) 13 coroutine.yield() 14 end 15 end) 16 17 coroutine.resume(co) --co 1 18 print(coroutine.status(co)) --suspended 19 20 coroutine.resume(co) --co 2 21 coroutine.resume(co) --co 3 22 coroutine.resume(co) --什么都不打印 23 24 print(coroutine.resume(co)) --false cannot resume dead coroutine 25 26 co = coroutine.create(function(a,b,c) 27 print("co",a,b,c) 28 end) 29 coroutine.resume(co,1,2,3) --co 1 2 3 30 31 co = coroutine.create(function(a,b) 32 coroutine.yield(a+b,a-b) 33 end) 34 print(coroutine.resume(co,20,10)) --true 30 10 35 36 co = coroutine.create(function() 37 print("co",coroutine.yield()) 38 end) 39 coroutine.resume(co) 40 coroutine.resume(co,4,5) --co 4 5 41 42 co = coroutine.create(function() 43 return 6,7 44 end) 45 print(coroutine.resume(co)) --true 6 7
管道(pipe)与过滤器(filter)
1 function producer() 2 while true do 3 local x = io.read() --产生新的值? 4 send(x) --发送给消费者 5 end 6 end 7 8 function consumer() 9 while true do 10 local x = receive() --从生产者接收值 11 io.write(x,"\n") --消费新的值 12 end 13 end 14 15 function receive() 16 local status,value = coroutine.resume(producer) 17 return value 18 end 19 20 function send(x) 21 coroutine.yield(x) 22 end 23 24 producer = coroutine.create( 25 function() 26 while true do 27 local x = io.read() --产生新值 28 send(x) 29 end 30 end) 31 32 function receive(prod) 33 local status,value = coroutine.resume(prod) 34 return value 35 end 36 37 function send(x) 38 coroutine.yield(x) 39 end 40 41 function producer() 42 return coroutine.create(function() 43 while true do 44 local x = io.read() --产生新值 45 send(x) 46 end 47 end) 48 end 49 50 function filter(prod) 51 return coroutine.create(function() 52 for line = 1,math.huge do 53 local x = receive(prod) --获取新值 54 x = string.format("%5d%s",line,x) 55 send(x) --将新值发送给消费者 56 end 57 end) 58 end 59 60 function consumer(prod) 61 while true do 62 local x = receive(prod) --获取新值 63 io.write(x,"\n") --消费新值 64 end 65 end 66 67 p = producer() 68 f = filter(p) 69 consumer(f)
以协同程序实现迭代器
1 function permgen(a,n) 2 n = n or #a --默认n为a的大小 3 if n<= 1 then --还需要改变吗 4 printResult(a) 5 else 6 for i = 1,n do 7 --将第i个元素放到数组末尾 8 a[n],a[i] = a[i],a[n] 9 --生成其余元素的排列 10 permgen(a,n-1) 11 --恢复第i个元素 12 a[n],a[i] = a[i],a[n] 13 end 14 end 15 end 16 17 function printResult(a) 18 for i = 1,#a do 19 io.write(a[i]," ") 20 end 21 io.write("\n") 22 end 23 24 permgen({1,2,3,4}) 25 26 function permgen(a,n) 27 n = n or #a 28 if n <= 1 then 29 coroutine.yield(a) 30 else 31 <as before> 32 33 function permutations(a) 34 local co = coroutine.create(function() permgen(a) end) 35 return function() --迭代器 36 local code,res = coroutine.resume(co) 37 return res 38 end 39 end 40 41 for p in permutations{"a","b","c"} do 42 printResult(p) 43 end
非抢先式的(non-preemptive)多线程
10 完整的示例
暂无
11 数据结构
数组
矩阵与多维数组
链表
队列与双向队列
1 function ListNew() 2 return {first=0,last=-1} 3 end 4 5 List = {} 6 function List.new() 7 return {first=0,last=-1} 8 end 9 10 function List.pushfirst(list,value) 11 local first = list.first-1 12 list.first = first 13 list[first] = value 14 end 15 16 function List.pushlast(list,value) 17 local last = list.last + 1 18 list.last = last 19 list[last] = value 20 end 21 22 function List.popfirst(list) 23 local first = list.first 24 if first > list.last then error("list is empty") end 25 local value = list[first] 26 list[first] = nil --为了允许垃圾收集 27 list.first = first + 1 28 return value 29 end 30 31 function List.poplast(list) 32 local last = list.last 33 if list.first > last then error("list is empty") end 34 local value = list[last] 35 list[last] = nil --为了允许垃圾收集 36 list.last = last - 1 37 return value 38 end
集合与无序组(bag)
1 reserved = { 2 ["while"] = true, 3 ["end"] = true, 4 ["function"] = true, 5 ["local"] = true, 6 } 7 8 for w in allwords() do 9 if not reserved[w] then 10 <对'w'作任意处理> --'w'不是保留字 11 end 12 end 13 14 function Set(list) 15 local set = {} 16 for _,l in ipairs(list) do set[l] = true end 17 return set 18 end 19 20 reserved = Set{"while","end","function","local"} 21 22 function insert(bag,element) 23 bag[element] = (bag[element] or 0) + 1 24 end 25 26 function remove(bag,element) 27 local count = bag[element] 28 bag[element] = (count and count > 1) and count - 1 or nil 29 end
字符串缓冲
图
12 数据文件与持久性
数据文件
串行化(Serialization)
13 元表(metatable)与元方法(metamethod)
可以通过元表来修改一个值的行为,使其在面对一个非预定义的操作时执行一个指定的操作
Lua中的每个值多又一个元表.table和userdata可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表
Lua在创建新的table时不会创建元表:
可以使用setmetatable来设置或修改任何table的元表:
任何table都可以作为任何值的元表,而一组相关的table也可以共享一个通用的元素,此元表描述了它们共同的行为.一个table甚至可以作为它自己的元表,用于描述其特有的行为.
在Lua代码中,只能设置table的元表.若要设置其他类型的值的元表,则必须通过C代码来完成.
算术类的元方法
在元表中,每种算术操作符多有对应的字段名.__add,__mul,__sub,__div,_unm(相反数),__mod,__pow,__concat
1 Set = {} 2 local mt = {} --集合的元表 3 4 --根据参数列表中的值创建一个新的集合 5 function Set.new(l) 6 local set = {} 7 setmetatable(set,mt) 8 for _,v in ipairs(l) do 9 set[v] = true 10 end 11 return set 12 end 13 14 function Set.union(a,b) 15 local res = Set.new{} 16 for k in pairs(a) do 17 res[k] = true 18 end 19 for k in pairs(b) do 20 res[k] = true 21 end 22 return res 23 end 24 25 function Set.intersection(a,b) 26 local res = Set.new{} 27 for k in pairs(a) do 28 res[k] = b[k] 29 end 30 return res 31 end 32 33 function Set.tostring(set) 34 local l = {} --用于存放集合中所有元素的列表 35 for e in pairs(set) do 36 l[#l + 1] = e 37 end 38 return "{" .. table.concat(l,", ") .. "}" 39 end 40 41 function Set.print(s) 42 print(Set.tostring(s)) 43 end 44 45 46 47 s1 = Set.new{10,20,30,50} 48 s2 = Set.new{30,1} 49 50 print(getmetatable(s1)) 51 print(getmetatable(s2)) 52 53 mt.__add = Set.union 54 55 s3 = s1 + s2 56 Set.print(s3) 57 58 mt.__mul = Set.intersection 59 Set.print((s1 + s2)*s1)
关系类的元方法
元表还可以指定关系操作符的含义,元方法为__eq,__lt(小于),__le(小于等于).而其他3个关系操作符则没有单独的元方法,Lua会将a~=b转化为not(a==b),将a>b转化为b<a,将a>=b转化为b<=a
库定义的元方法
table访问的元方法
__index元方法
__newindex元方法
__newindex元方法与__index类似,不同之处在于前者用于table的更新,而后者用于table的查询
具有默认值的table
跟踪table的访问
只读的table
14 环境
Lua将其所有的全局变量保存在一个常规的table中,这个table称为"环境(enviroment)".Lua将环境table自身保存在一个全局变量_G中.
具有动态名字的全局变量
全局变量声明
简单地检测所有对全局table中不存在key的访问
如何声明一个新的变量,其一是使用rawset,它可以绕过元表
另外一种更简单的方法就是只允许在主程序中对全局变量进行赋值
非全局的环境
Lua5允许每个函数拥有一个自己的环境来查找全局变量
15 模块与包
require函数
如果require为指定模块找到了一个Lua文件,它就通过loadfile来加载该文件.而如果找到的是一个C程序库,就通过loadlib来加载.注意,loadfile和loadlib都只是加载了代码,并没有运行它们.
require采用的路径是一连串的模式(pattern),其中每项都是一种将模块名转换为文件名的方式
require用于搜索Lua文件的路径存放在变量package.path中.当Lua启动后,便以环境变量LUA_PATH的值来初始化这个变量.
具有良好行为的C程序库应该导出一个名为"luaopen_<模块名>"的函数.
编写模块的基本方法
在Lua中创建一个模块最简单的方法是:创建一个table,并将所有需要导出的函数放入其中,最后返回这个table.
使用环境
module函数
子模块与包
16 面向对象编程
类
继承
多重继承
1 --在table'plist'中
请发表评论