Lua程序设计

关于Lua

游戏开发应用中Lua和C#的区别

Lua可以在几乎所有的操作系统和平台进行编译运行,可以很方便的更新代码,更新了代码后,可以直接在手机上运行,不需要重新安装(热更新方案)。
C#只能在特定的操作系统中进行编译成dll文件,然后打包进安装包在其他平台(Android、iOS)运行,在移动平台上不能更新替换已有的dll文件,除非重新下载安装包。

语言基础

语言入门

Lua类型

Lua是动态类型定义的语言(类型可以随意改变)。
Lua中的类型有(八种):
nil boolean userdata number string table function thread

nil类型

Lua中变量不需要声明,也不需要初始化,可以直接使用。
对于没有声明和初始化的变量默认值都为nil(空类型),如:
b = nil
把一个变量置空,相当于从没有使用过这个变量,Lua会销毁b所占的内存。

boolean类型

  1. false和nil都代表false,其他任何值都代表true(0和空字符串lua都认为是true)。
  2. and 和 or都是短路运算符,如果第一个操作数可以得到结果,就不会运算第二个操作数。

userdata类型

userdata是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。

局部变量与代码块

代码块可是是一个控制结构的主题、函数的主体、一个代码段(变量被声明时所咋IDE文件或字符串,交互模式中不用do-end的每一行代码都是代码段)。

尽可能使用局部变量进行编程。
首先,可以避免由于不必要的命名导致与全局变量的混乱。
其次,局部变量可以避免不同代码段的变量命名冲突。
再次,访问局部变量比访问全局变量快。
最后,局部变量会随着作用范围的结束而被垃圾收集器进行释放。

实例1:
local x = 10
if i > 20 then
  local x -->对于then来说是局部的
  x = 20
  print(x)  -->20
else 
  print(x)  -->10(全局的)
实例2:
local x1, x2 = 1, 10
do
  local x1 = 2
  local x2 = 20
end
print(x1, x2)  -->1, 10
实例3:
local a, b = 1, 10
if a < b = then
  print(a)  --> 1
  local a  --> a = nil,局部变量a将屏蔽全局变量a
  print(a)  --> 10   
  local b = b  -->局部变量b保留全局变量a此时的初值
  print(b)  -->10
end
  print(a, b) -->1, 10

控制结构

Lua的控制结构包括用于条件执行的if,用于循环执行的for、while、repeat。( 条件判断均不用(),结构体均不用{} )
if、for、while的显式终结符为end,repeat的显式终结符为until。
控制表达式的结果可以是任何值。除了false和nil,所有其他值都被当作真值,包括0和空字符串。
另外,Lua不支持switch语句。

  1. 对于repeat-until,重复执行循环体直到条件为真时结束。
    对于数值型for:
for var = exp1, exp2, exp3 do  -->第三个表达式exp3可选,若不存在,默认为1   
  something    
end  -->循环结束,控制变量var作用范围仅仅在循环体内  
  1. 对于return,有一种特殊使用情况,使用do-end。
function foo()  
  do return end  
  something  -->此时return不会结束函数的执行  
end  
  1. 对于goto的使用
    goto语句后面紧跟标签名。标签的设定是名字的名字前后紧跟两个冒号,这个设定是为了更好的突出标签。
    使用goto,Lua进行了一些限制:
    首先,标签遵循基本的可见性规则,即不能直接跳转到一个代码块中的标签(代码块中的标签对外不可见)。
    其次,goto不能直接跳转到函数外。
    最后,goto不能跳转到局部变量的作用域。
    Lua语言规范有一个细节,局部变量的作用域终止于声明变量的代码块中的最后一个有效(non-void)语句处,便签被认为是无效的语句。如下:
while some_condition do
  if some_other_condition then
    goto continue
  end
  some code
  ::continue::  ->此处的标签为无效的语句,因此if中的跳转并未包含在局部变量的范围内
end

语法补充点

  1. 多重赋值:a = 1, 2,多余的值被丢弃,即a = 1,a, b = 1,多余的变量赋值为nil,即b = nil。
  2. 变量交换:a, b = b, a,返回结果为ab值互换。
  3. type(type(x):不管x是什么值,返回的永远是String,因为type函数的返回值是String。
    因此Type()与nil作比较时nil应该加上双引号
type(X)  --nil
type(X)==nil --false,原因是type(x)返回的是string,注意,nil是一种类型,不是值,区别C++中的null。
type(X) == "nil" --true

数值

整数和小数都是number类型的,整数和小数的大小,则被认为是相等的。

相关数字常量与数字函数

  1. 常量
    math.pi,math.huge,math.maxinteger,math.mininteger
  2. 求值函数
    math.max( , values…),math.min( , values…),math.mod(value, modulus),math.pow(value, modulus),math.abs(value),math.sqrt(value)
    math.log(value) – 返回此值的自然对数(以e为底数)
    math.log10(value) – 返回以10为底数的值
    math.exp(value) --计算以e为底x次方值
    math.frexp(num) – 返回当前数字小数点后的数值以及小数点后的位数
    math.ldexp(value, 倍数) – 输出此数值的倍数
  3. 弧度函数
    deg(radians) – 弧度转换角度
    rad(degrees) – 角度转换弧度
    math.sin(value),math.cos(value),math.tan(value)
    math.asin(value),math.acos(value),math.atan(value),math.atan2(y, x)
  4. 随机函数
    a、math.random() --[0, 1)
    b、math.random(n)n整数 --[1, n]整数
    c、math.random(l, u)lu整数 --[l, u]整数
    设置随机数的种子math.randomseed(os.time() / seed)
  5. 凑整函数
    a、math.floor(value) --向下取整,即向负无穷取整,floor(x+0.5)可实现四舍五入
    b、math.ceil(value) --向上取整,即向正无穷取整
    c、math.modf(value) --舍去小数部分,即向0取整

数值相关补充知识

由于Lua中整数和小数都是number类型,如果我们想要区分整数和小数这两种类型,可以使用math.type函数,如:
math.type(3) != math.type(3.0)

为了避免两个整型数值和两个浮点型相除导致不一致,lua中的除法运算操作数强制为双精度浮点数且结果为浮点数。
floor函数,新定义的除法运算符,floor除法会对得到的商向负无穷取整,从而保证结果是一个整数。操作数都是整型数,结果也会是整型数,否则结果为一个值为整型的浮点型数。
3 // 2 -> 1
3.0 // 2 -> 1.0
6 // 2 -> 3.0
6.0 // 2.0 -> 3.0
-9 // 2 -> -5
1.5 // 0.5 -> 3.0

关于取模运算:
a % b == a - ((a // b) * b) --取模运算的定义, (a // b) * b,把整除后余下部分转化掉,即a // b不满足1的部分被擦除
对于实数型操作数,取模可以用来保留小数位,如x % 0.01为保留两位小数的结果
对于角度或弧度操作数,可用来判断拐过某个特定角度后,是否能够原路返回。
角度为单位:

local tolerance = 10 --容错角度   
function isturnback(angle)   
  angle = angel % (2 * math.pi) --%2π使得负弧度也适用   
  return(math.abs(angle - math.pi) < tolerance)   
end

弧度为单位:

local tolerance = 0.017 --容错弧度  
function isturnback(angle)  
  angle = angel % 360 --%360使得负角度也适用   
  return(math.abs(angle - 180) < tolerance)   
end   

字符串

字符串常用函数:

  1. :获得字符串的长度

  2. … :链接两个字符串
  3. string.upper(arg) : 字符串全部转为大写字母。
  4. string.lower(arg) : 字符串全部转为小写字母。
  5. string.gsub(mainString, findString, replaceString, num):字符串替换。mainString为要替换的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(如果忽略,则全部替换)。
  6. string.find(str, substr, [init, [end]]) :在一个指定的目标字符串中搜索指定的子串(init和end为索引起始和索引结束)。函数返回搜索成功的具体位置,不存在则返回 nil。
  7. string.reverse(arg) :字符串反转
  8. string.format(…) :格式化输出字符串,类似C语言的printf,格式化转义码与C语言标准相同
  9. string.char(arg)和string.byte(arg[,int]) :char 将整型数字转成对应字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。
  10. string.len(arg) : 求字符串长度,可以用#替换。
  11. string.rep(string, n) :返回字符串string的n个拷贝
  12. string.match(str, pattern, init) :返回str中的第一个和pattern匹配的子串. 参数init可选,指定搜寻过程的起点,默认为1。函数将返回配对表达式中的所有捕获结果,没有成功配对返回nil。
  13. string.gmatch(str, pattern) :迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil。
    补充说明:
string.sub(s, i, j) --i, j都表示第几个字符(这里是索引,从1开始),如果索引是负数,表示倒数第几个字符  
s = “[in brackets]”   
string.sub(s, 2, -2) --从第二个字符到倒数第二个字符  
string.sub(s, 1, 1)从第一个字符到第一个字符  
string.sub(s, -1, -1)从倒数第一个字符数到倒数第一个字符  

string.char(97) --把一个ASCII值转换成对应的字符   
string.char(97, 98, 99)  
string.byte("abc") --把字符串里面的第一个字符转换成对应的ASCII值  
string.byte("abc", 2) --把第二个字符转换成对应的ASCII值  
string.byte("abc", -1) --把倒数第一个字符转换成对应的ASCII值  
string.byte("abc", 2, 3) --把第二个和第三个转换成对应的ASCII值  

匹配模式

  1. 单个字符(除 ^$()%.[]*±? 外): 与该字符自身配对
  2. . : 与任何字符配对
  3. %a: 与任何字母配对
  4. %c: 与任何控制符配对(例如\n)
  5. %d: 与任何数字配对
  6. %l: 与任何小写字母配对
  7. %u: 与任何大写字母配对
  8. %p: 与任何标点(punctuation)配对
  9. %s: 与空白字符配对
  10. %w: 与任何字母/数字配对
  11. %x: 与任何十六进制数配对
  12. %z: 与任何代表0的字符配对
  13. [数个字符类]: 与任何[]中包含的字符类配对. 例如[%w_]与任何字母/数字, 或下划线符号(_)配对
  14. 当上述的字符类用大写书写时, 表示与非此字符类的任何字符配对. 例如, %S表示与任何非空白字符配对.例如,’%A’非字母的字符

匹配条目

  1. 单个字符类匹配该类别中任意单个字符;
  2. 单个字符类跟一个 ‘*’, 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串;
  3. 单个字符类跟一个 ‘+’, 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串;
  4. 单个字符类跟一个 ‘-’, 将匹配零或更多个该类的字符。 和 ‘*’ 不同, 这个条目总是匹配尽可能短的串;
  5. 单个字符类跟一个 ‘?’, 将匹配零或一个该类的字符。 只要有可能,它会匹配一个;
  6. %n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物的子串。
  7. %bxy, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。
  8. %f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于set。

数值和字符串的互相运用的一些补充

  1. 数值和字符串进行算法运算
    在对一个数字字符串上进行算术操作时,Lua会尝试将这个数字字符串转成一个数字:
print("2" + 6) --8.0
print("2" + "6") --8.0
print("2 + 6") --2 + 6
print("2e2" * "6") --1200.0
print("error" + 1) --error
  1. 数值和字符串进行转换
    数值字符串转数字,直接返回数字,忽略前面的0,如010将输出10。含有非数字型字符的字符串转数字,返回nil。
  2. 数值和字符串进行比较
    和~=是比较两个值,如果两个值类型不同,Lua认为两者不同。
    字符串和数字比较,只能比较是否
    和~=,大小比较不可行。但是字符串和数字一起操作,会处理成number。
    数字和nil比较,只能比较是否==和~=,注意nil要用引号"nil",大小比较不可行。

表的基础知识

Lua中的表(table)是一个"关联数组",数组的索引可以是数字、字符串、函数等等任意类型。
不同于其他语言,在Lua里表的默认初始索引一般以1开始。
对table的索引使用方括号[]。Lua也提供了. 操作。一般为t[i]和t.i。
当我们为table a设置元素,然后将a赋值给b,则a与b都指向同一个内存。当a设置为nil,b同样能访问table中的元素。如果a没有再指向,Lua的垃圾回收机制会清理a相对应的内存。

表的常用方法

  1. table.concat(table [, sep [, start [, end]]]) : 该函数将连接table的各数组部分,可规定从start位置到end位置, 元素间以指定的分隔符(sep)进行连接。
  2. table.insert(table, [pos,] value) : 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾。
  3. table.remove (table [, pos]) :删除pos位置的元素并返回,其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。
  4. table.sort (table [, comp]) : 对给定的table进行升序排序,也可以通过comp确定排序规则。Lua5.2去除了table.Maxn(),但可以先使用sort排序后直接获取最大或最小的值。
  5. table.maxn(table) :指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。 你可以直接使用#符号.使用#(table)代替table.getn(table)
    (Lua5.2之后该方法已经不存在了,以下重写该函数的实现)
function table_maxn(t)
  local mn = nil;
  for k, v in pairs(t) do 
    if(mn==nil) then
      mn=v
    end
    if mn < v then
      mn = v
    end
  end
  return mn
end

函数

指定参数默认值

function Update(x)
   x = x or 0 --指定参数默认值
    if x == 0 then  
      print("x 没有指定参数值")
    else
      print("x 指定的参数值为" .. x)
    end
end

可变数目参数

Lua 函数可以接受可变数目的参数,和 C语言类似,在函数参数列表中使用三点 … 表示函数有可变的参数。
我们也可以通过 select("#",…) 来获取可变参数的数量,通过select(n, …)来进行可变参数的访问。

function add(...)  
local s = 0  
  for i, v in ipairs{...} do   -- {...} 表示一个由所有变长参数构成的数组  
    s = s + v  
  end  
  for i = 1, select('#', ...) do  --避免遍历变长参数时访问到变长参数中存在的nil
    s = s + selcct(i, ...) 
  end  
  return s  
end  

有时候我们可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:

function fwrite(fmt, ...)  --固定的参数fmt
    return io.write(string.format(fmt, ...))     
end

使用变量存储函数

函数可以在可以存在变量里:

function factorial1(n)
    if n == 0 then
        return 1
    else
        return n * factorial1(n - 1)
    end
end

print(factorial1(5))
factorial2 = factorial1 --函数存放在变量里
print(factorial2(5))

函数当作参数进行传递

Lua 中我们可以将函数作为参数传递给函数:

myprint = function(param)
   print("这是打印函数 -   ##", param, "##")
end

function add(num1,num2,functionPrint)
   result = num1 + num2
   functionPrint(result)  -- 调用传递的函数参数
end

myprint(10) -- myprint 函数作为参数传递
add(2, 5, myprint)

function可以用匿名函数的方式通过参数传递:

function testFun(tab,fun)
        for k ,v in pairs(tab) do
           print(fun(k, v));
        end
end

tab = {key1="val1", key2="val2"};

testFun(tab, function(key,val)    --匿名函数当参数
          return key.."="..val;
        end
    );

输入输出

由于Lua强调可移植性和可嵌入性,所以Lua本身没有提供太多于外部交互的机制,一般通过宿主程序实现或使用外部库。
Lua只提供基本文件操作功能,Lua的I/O库提供了两种模型,分为简单I/O模型和完整I/O模型。

  1. 简单I/O模型
    简单模型虚拟了一个当前输入流和一个当前输出流,I/O操作通过这些流实现。
    I/O库把当前输入流初始化为进程的标准输入,把当前的输出流初始化为进程的标准输出。
io.input()/io.output() --用于改变当前的输入输出流。  
--[[
调用io.input(filename)会以只读方式打开指定文件,并将文件设置为当前的输入流,之后所有的输入都将来自当前文件,除非再次调用io.input(),io.output()与之相似。当出现错误,两个函数都会抛出异常,如果想直接处理这些异常,需要用到完整I/O模型。
--]]

io.write()/io.read() --从标准输入中写入/读取一行  
--[[
io.write()可以读取任意数量的字符串或数字并将其写入当前输出流。调用io.write()可以使用多个参数,因此需要避免使用io.write(a..b..c),应该使用io.write(a,  b, b),后者可以减少资源的使用,避免给多的连接动作。
区别于print(),io.write()可以完全控制输出并对输出进行重定向,而print仅仅是适合用于一些用后即弃的代码或调试代码时的标准输出。此外,io.write()不会再最终的输出结果中添加诸如制表符或换行符这样的额外内容。但print()可以自动为参数调用tostring,一定程度上有利于调试。

io.read()可以从当前输入流中读取字符串。
关于参数意义,"a"从当前位置读取完整个文件,"l"读取下一行并丢弃换行符(默认参数,当为最后一行,返回nil),"L"读取下一行并保留换行符(当为最后一行,返回nil),num以字符串读取num个字符。
io.read(n),从输入流中读取n个字符,如果无法读取到字符(处于文件末尾),则返回nul,否则返回一个由流中最多n个字符组成的字符串。
io.read(0),常用于测试是否到达了文件结尾,如果还有数据能读取,返回空字符串,否则返回nil。
--]]


io.lines() --返回一个不断读取内容的迭代器
--[[
如果给io.lines提供一个文件名,会以只读方式打开对应该文件的文件流,并在到达文件末尾后关闭改输入流。若调用时不带参数,就从当前输入流读取。
--]]
local count = 0;
local lines = {}
for line in io.lines() do -->使用io.lines()迭代器
  count = count + 1
  lines[#lines + 1] = line
  io.write(string.format("%6d", count), line, "\n")
end
  1. 完整I/O模型
    对读写的需求变得复杂,需要同时读写多个文件等更复杂的操作时,需要用到完整I/O模型。
    Lua的I/O库提供了三个预定义的C语言流的句柄:io.stdin、io.stdout、io.stderr。
    如:io.stderr : write(message)
io.open(filename, mode) --打开文件操作
--[[
mode参数意义,r表示只读,w表示只写也可以用来删除文件中原有的内容),a表示追加,b表示打开二进制文件。  
io.open()函数返回对应文件的流,当发生错误时,返回nil的同时返回一条错误信息及一个系统相关的错误码。

打开文件成功后,可以使用方法read()和write()从流中读取或向流中写入,与函数io.read()和io.write()类似。
--]]
local f = assert(io.open(filename, "r") )
local t = f:read("a")
f:close()

io.input()和io.output()被允许混用完整I/O模型和简单I/O模型。调用无参数的io.input()可以获得当前输入流,调用io.input(handle)可以设置当前输入流。   
local temp = io.input() --保存当前输入流
io.input("newinput")  --打开一个新的当前输入流
--对新的输入流进行某些操作
io.input() : close()  --关闭当前流
io.input(temp)  --恢复此前的当前输入流

需要了解的是,io.read(args)实际上是io.input() : read(args)的简写,即函数read是用在当前输入流上。
同样,io.write(args)是io.output() : write(args)的简写。

  1. 其他操作
    1)函数io.tmpfile
    返回一个临时文件的句柄,以读/写模式打开,当程序运行结束后,该临时文件会自动删除。
    2)函数flush()
    将所有缓冲数据写入文件。可以当做io.flush()使用,以刷新当前输出流,或当做f : flush()使用,以刷新流f。

编程实操

闭包

系统函数库

assert(value) - 检查一个值是否为非nil, 若不是则(如果在wow.exe打开调试命令)显示对话框以及输出错误调试信息
collectgarbage() - 垃圾收集器. (新增于1.10.1)
date(format, time) - 返回当前用户机器上的时间.
error(“error message”,level) - 发生错误时,输出一条定义的错误信息.使用pcall() (见下面)捕捉错误.
gcinfo() - 返回使用中插件内存的占用量(kb)以及当前垃圾收集器的使用量(kB).
getfenv(function or integer) - 返回此表已获取函数的堆栈结构或者堆栈等级
getmetatable(obj, mtable) - 获取当前的元表或者用户数据对象.
loadstring(“Lua code”) - 分析字符串中的lua代码块并且将结果作为一个函数返回
next(table, index) - 返回下一个key,一对表值.允许遍历整个表
pcall(func, arg1, arg2, …) - 受保护调用. 执行函数内容,同时捕获所有的异常和错误.
select(index, list) - 返回选择此列表中的商品数值.或者是此件物品在列表中的索引值
setfenv(function or integer, table) - 设置此表已获取函数的堆栈结构或者堆栈等级
setmetatable(obj, mtable) - 设置当前表的元表或者用户数据对象
time(table) - 返回从一个unix时间值
type(var) - 判断当前变量的类型, “number”, “string”, “table”, “function” 或者 “userdata”.
unpack(table) - 解压一个表.返回当前表中的所有值.
xpcall(func, err) - 返回一个布尔值指示成功执行的函数以及调用失败的错误信息.另外运行函数或者错误的返回值

语言特性

迭代器和泛型for

元表和元方法

原表的一些方法:

_index: 用于对表的访问。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。
当我们通过Lua查找表中元素时的候,将分为三个步骤执行:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则从1重复,如果 __index 方法是一个函数,则返回该函数的返回值。

_newindex:用于对表的更新。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法,如果存在则调用这个函数而不进行赋值操作。
以下实例使用了 rawset 函数来更新表:

mytable = setmetatable({key1 = "value1"}, {
  __newindex = function(mytable, key, value)
                rawset(mytable, key, "\""..value.."\"")

  end
})

mytable.key1 = "new value"
mytable.key2 = 4

print(mytable.key1,mytable.key2)  --new value   "4"

元表的操作符:

__add :对应的运算符 ‘+’.
__sub : 对应的运算符 ‘-’.
__mul : 对应的运算符 ‘*’.
__div :对应的运算符 ‘/’.
__mod :对应的运算符 ‘%’.
__unm : 对应的运算符 ‘-’.
__concat : 对应的运算符 ‘…’.
__eq : 对应的运算符 ‘==’.
__lt : 对应的运算符 ‘<’.
__le : 对应的运算符 ‘<=’.

使用元表实现集合的并集和交集

Set = {}
Set.mt = {}   --将所有集合共享一个metatable
function Set.new (t)   --新建一个表
    local set = {}
    setmetatable(set,Set.mt)
    for _, l in ipairs(t) do set[l] = true end
    return set
end
function Set.union(a,b)   --并集
    local res = Set.new{}  --注意这里是大括号
    for i in pairs(a) do res[i] = true end
    for i in pairs(b) do res[i] = true end
    return res
end
function Set.intersection(a,b)   --交集
    local res = Set.new{}   --注意这里是大括号
    for i in pairs(a) do
        res[i] = b[i]
    end
    return res
end
function Set.tostring(set)  --打印函数输出结果的调用函数
    local s = "{"
    local sep = ""
    for i in pairs(set) do
        s = s..sep..i
        sep = ","
    end
    return s.."}"
end
function Set.print(set)   --打印函数输出结果
    print(Set.tostring(set))
end

--[[
Lua中定义的常用的Metamethod如下所示:
算术运算符的Metamethod:
__add(加运算)、__mul(乘)、__sub(减)、__div(除)、__unm(负)、__pow(幂),__concat(定义连接行为)。
关系运算符的Metamethod:
__eq(等于)、__lt(小于)、__le(小于等于),其他的关系运算自动转换为这三个基本的运算。
库定义的Metamethod:
__tostring(tostring函数的行为)、__metatable(对表getmetatable和setmetatable的行为)。
]]


Set.mt.__add = Set.union

s1 = Set.new{1,2}
s2 = Set.new{3,4}
print(getmetatable(s1))
print(getmetatable(s2))   
s3 = s1 + s2 
Set.print(s3)

Set.mt.__mul = Set.intersection   --使用相乘运算符来定义集合的交集操作
Set.print((s1 + s2) * s1)

如上所示,用表进行了集合的并集和交集操作。
Lua 选择 metamethod 的原则:如果第一个参数存在带有 __add 域的 metatable,Lua 使用它作为 metamethod,和第二个参数无关;
否则第二个参数存在带有 __add 域的 metatable,Lua 使用它作为 metamethod, 否则报错。

面向对象编程

使用元表实现类的封装与继承。


Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end
-- 基础类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()



Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
  o = o or Shape:new(o,side)
  setmetatable(o, self)
  self.__index = self
  return o
end

-- 派生类方法 printArea
function Square:printArea ()
  print("正方形面积为 ",self.area)
end

-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()



Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
  o = o or Shape:new(o)
  setmetatable(o, self)
  self.__index = self
  self.area = length * breadth
  return o
end

-- 派生类方法 printArea
function Rectangle:printArea ()
  print("矩形面积为 ",self.area)
end

-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()

协同程序

在Lua里,最主要的线程是协同程序(coroutine)。
它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。

猜你喜欢

转载自blog.csdn.net/a834595603/article/details/90143112