简介
1. lua面向对象编程是基于元表metatable,元方法__index来实现的,具体元表和元方法的介绍
2. 语法糖
语法糖是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用
举例说明:数组char test[100],如果要访问第11个元素,可以这样写:char c = *(test+10),但是用语法糖的话就简单了,直接char c = test[10],看到没?语法糖就是一些简便写法
lua中的语法糖
lua中的函数默认都是有self传递进去的,self相当于C++类中函数的this指针,语法糖会自动给我们传递 self
举例说明:
local a = { x = 99 } -- 打印函数,注意这里要访问表a中的变量x,必须指明self.x或者a.x,不然会报错 function a:Print() print("function a:test() " ..self.x) end -- 想调用a的Print()函数,我们可以这样写,注意参数是a,否则调用出错 a.Print(a) -- 也可以这样写,即用:代替. 且不用传入参数a a:Print()
明显第二种方法更简便
2. lua面向对象的原理(基于元表metatable和元方法__index)
如果访问了lua表中不存在的元素时,就会触发lua的一套查找机制,也是凭借这个机制,才能够实现面向对象的
举例说明:
test = { } -- 访问表test中不存在的变量a print(test.a)
打印结果:nil
原因很简单:表test中不存在变量a,所以打印为nil,但是如果表test有元表metatable的话,情况就不一样了
元表像是一个备用查找表,假设表A的元表是B,那么在A中找不到的东西就会尝试在B中去找,设置元表的函数如下
setmetatable(A, B),这样表B就被设置为A的元表,当A中查找不到某个变量时就会到B中进行查找
举例说明:
-- 表A A = { } -- 表B B = { a = 99 } -- 设置表B为表A的元表 setmetatable(A,B) -- 再访问表A中不存在的变量a print(A.a)
打印结果依然为:nil
why?因为表B 的元方法__index没有赋值。按照笔者的理解,元方法__index是用来确定一个表在被作为元表时的查找方法
我们做如下更改,即对表B的元方法进行赋值
代码如下:
-- 表A A = { } -- 表B B = { a = 99 } -- 给表B的元方法__index进行赋值 B.__index = B -- 设置表B为表A的元表 setmetatable(A,B) -- 再访问表A中不存在的变量a print(A.a)
打印结果:99
查找过程:访问A.a时,表A中没有a这个变量,但是lua发现表A有元表,即表B,于是再到表B中进行查找,但是lua并不是直接在表B中查找变量a,而是调用表B的元方法__index,如果__index为nil,那就会返回nil。如果__index被赋值为一个表(上面的例子就是__index被赋值为表B自己),那么就会到__index指向的那个表(即表B)中进行查询,于是找到了变量a;如果__index被赋值为一个函数,那么查找时就会返回该函数的返回值
代码如下:
-- 表A A = { } -- 表B B = { a = 99 } -- 给表B的元方法__index进行赋值,这里赋值为一个函数 B.__index = function(table, key) print("在元表中访问了变量"..key) return 88 end -- 设置表B为表A的元表 setmetatable(A,B) -- 再访问表A中不存在的变量a print(A.a)
打印结果:
在元表中访问了变量a
88
总结元表的查找步骤:
步骤1.在表中查找,如果找到,返回该元素,找不到则继续步骤2
步骤2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续步骤3
步骤3.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复步骤1、2、3;如果__index方法是一个函数,则调用该函数,并返回该函数的返回值
3. 面向对象的封装
-- 类Class的声明,其实就是个table,这里有两个成员变量x,y Class = { x = 1, y = 2 } -- 设置metatable的元方法__index,指向表Class自己 Class.__index = Class -- 构造函数,叫什么名字无所谓,这里采用了C++的new名字 function Class:new(x, y) print("Class:模拟构造函数new()") -- 新建一个对象,这样通过Class:new()函数创建的每一个实例都是独立的 local tempObj = {} tempObj.x = x tempObj.y = y -- 设置新对象的metatable,谨记:这一步非常重要 setmetatable(tempObj,Class) -- 返回这个新创建的对象 return tempObj end -- 类的其他成员函数1 function Class:Print() print("Class:Print()") print("x = "..self.x..", y = "..self.y) end -- 类的其他成员函数2 function Class:Add(val) print("Class:Add()") self.x = self.x + val self.y = self.y + val end -- 类的其他成员函数3 function Class:Modify() print("Class:Modify()") self.x = 11 self.y = 22 end -- 下面是测试代码 -- 新构造一个类实例 local Obj = Class:new(11,22) -- 调用函数Print()进行打印 Obj:Print() -- 调用函数Add()进行加操作 Obj:Add(5) -- 再次调用函数Print()进行打印,会发现调用Add()函数确实成功了 Obj:Print() -- 做修改 Obj:Modify() -- 再次调用函数Print()进行打印,会发现调用Modify()函数确实成功了 Obj:Print() -- 这里打印出Class本身的数据,会发现数据没有改动,说明是新建的类实例互不影响 print("Class Class.x = "..Class.x..", Class.y = "..Class.y)
测试结果如下:
4. 面向对象的继承和多态
-------------------------------------- 基类Class ------------------------------------------ -- 类Class的声明,其实就是个table,这里有两个成员变量x,y Class = { x = 0, y = 0 } -- 设置metatable的元方法__index,指向表Class自己 Class.__index = Class -- 构造函数,叫什么名字无所谓,这里采用了C++的new名字 function Class:new(x, y) print("Class:模拟构造函数") -- 新建一个对象,这样通过Class:new()函数创建的每一个实例都是独立的 local tempObj = {} tempObj.x = x tempObj.y = y -- 设置新对象的metatable,谨记:这一步非常重要 setmetatable(tempObj,Class) -- 返回这个新创建的对象 return tempObj end -- 类的其他成员函数1 function Class:Print() print("Class:Print() x = "..self.x..", y = "..self.y) end -- 类的其他成员函数2 function Class:Add(val) print("Class:Add()") self.x = self.x + val self.y = self.y + val end -- 类的其他成员函数3 function Class:Modify() print("Class:Modify()") self.x = 111 self.y = 222 end -------------------------------------- 子类SubClass --------------------------------------- -- 子类SubClass的声明,这里又声明了一个新的变量z SubClass = { z = 0 } -- 设置元表为Class setmetatable(SubClass, Class) -- 设置metatable的元方法__index,指向表SubClass自己 SubClass.__index = SubClass -- 构造函数 function SubClass:new(x,y,z) print("模拟构造函数:SubClass") -- 先调用父类的构造函数,构造出一个父类的实例 local tempObj = Class:new(x,y) -- 将该对象的元表指向SubClass,谨记:这步非常重要,一定不要弄错了,是SubClass setmetatable(tempObj,SubClass) -- 新属性z赋值,有了子类自己的数据,这样就是子类实例了 tempObj.z = z return tempObj end -- 定义一个新的成员函数 function SubClass:SubPrint() print("SubClass:SubPrint() x = "..self.x..", y = "..self.y..", z = "..self.z) end -- 重定义父类的函数Add(),注意:和父类的不同,这里是增加了2倍的val function SubClass:Add(val) print("SubClass:Add()") self.x = self.x + 2*val self.y = self.y + 2*val end ------------------------------------- 下面是测试代码 ----------------------------------- -- 构造一个基类实例 local Obj = Class:new(11,22) -- 调用函数Print()进行打印 Obj:Print() -- 调用函数Add()进行加操作 Obj:Add(5) -- 再次调用函数Print()进行打印,会发现调用Add()函数确实成功了 Obj:Print() -- 做修改 Obj:Modify() -- 再次调用函数Print()进行打印,会发现调用Modify()函数确实成功了 Obj:Print() -- 这里打印出Class本身的数据,会发现数据没有改动,说明是新建的类实例互不影响 print("Class Class.x = "..Class.x..", Class.y = "..Class.y) print("\n") -- 构造一个子类实例 local SubObj = SubClass:new(1,2,3) -- 访问父类的函数 SubObj:Print() -- 访问子类自己的函数 SubObj:SubPrint() -- 调用Add(),这里会发现实际调用的是子类的Add()函数,即实现了多态 SubObj:Add(5) -- 再次调用自己的函数,会发现调用自己的Add()函数确实成功了 SubObj:SubPrint()
测试结果如下: