0%

lua面向对象

Lua实现面向对象

Lua实现面向对象

首先Lua本身是面向过程语言,不存在封装、多态等特性,但是提供了元表元方法供技术人员自己实现面向对象。

 

元表与元方法

Lua中的每一个值都可以拥有一个元表,元表中定义了这些值在一定条件下的特定方法,如__add等。元表本身的内容是以键值对的形式存储的,“事件”为键,“元方法”为值。

对于面向对象模拟,关键要找__ index这个元方法,这个方法的意思是,如果在查找表中不存在的字段时,会查询元表中是否含有__ index,如果含有,则调用__ index。

__ index本身可以赋值为值,也可以赋值为函数,也可以赋值为表。

为值则返回值,为函数则调用该函数,为表则向这个表中查找字段。

 

从CPP面向对象向Lua发展

首先Lua的一大特性就是只有值和表,表包含万物,所以我们可以将表作为面向对象中的封装单位,下意识将成员与方法都放在表中。

local tb = {a=0,b=1}

对于函数,在C++中成员函数可以直接放在类中的,然而总所周知在编译器层面C++成员函数实际是这样定义的

Class::Function(){};

也就是说实际上函数是写在类外的,这个和C++的内存结构是一致的,因为对象中的成员参数要么存放在栈区,要么堆区,而函数是存放在代码段的,八竿子打不着。

C++通过这种方式可以调用到类本身的函数,实际是运用了隐藏的一个this指针:

Class::Function(Class *this){};//这个this是隐藏的,和下面的&a一样,编译器会自动填上

Class a;
a.Function(&a);

这样,函数调用的时候就会得知到是要处理哪一个对象的参数了。

在Lua中,this指针的传递成为了可选项。(lua中this指针叫做self)

local tb = {a=0,b=1}
function tb.add(a,b)
    return a+b
end
print(tb.add(tb.a,tb.b)) --直接传递参数
**********************
function tb.add(self)
    return self.a+self.b
end
print(tb.add(tb)) --传递表自己
**********************
function tb:add()
    return self.a+self.b
end

print(tb:add()) –用冒号省略self指针
–print(tb.add(tb))
print(tb.add(tb1))

 

这样的话,在lua中就可以模拟C++定义成员函数的方式来定义函数了。

local a = {name="lisi"}	

function a:MyPrint()
print(self.name)
end

–当然等同于
local a = {
name="lisi",
function MyPrint(self)
print(self.name)
end
}

 

这个类是需要能够继承的,特点是子类会继承父类的所有属性和函数,那么我们可以使用元表元方法来实现,我们将子表的元表设置成父表,再将父表的__ index设置为父表自己,这样就能实现当子表查不到属性的时候查到父表的__ index,然后直接就开始查父表中的属性了。

-- 定义父类 a
local a = {name = "lisi"}

function a:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end

function a:MyPrint()
print(self.name)
end

– 定义子类 b,继承自 a
local b = a:new() – 子类 b 继承父类 a
b.name = "zhangsan" – 子类有自己独立的属性

– 创建子类实例
local obj = b:new()

obj:MyPrint() – 输出 "zhangsan"

这样的面向对象还是比较牵强的,因为“类”b本质上也可以视为类a的对象,具备a对象的所有功能。

lua多继承

多继承意味着不能直接通过单个基类来直接派生出子类,所以要单独使用一个额外的不与任何类关联的函数(A)来处理继承。

这个函数中首先是收集要继承的所有父类,存在一个表(B)里,然后再走单继承的流程,先定义子类,再设置子类元表,此时这个元表先设置为空,在空元表中将__index设置为另一个函数search,这个函数在A函数内的表现是查找表B的key值,如果找到了就将value返回。

 

lua面向对象的封装

常用的处理方法是定义两个表,一个表存私有成员,一个表存调用私有成员的方法。

方法直接控制私有成员值,或者返回出去,而只有方法表才暴露在外界供调用,这样来模拟面向对象封装。

闭包

lua中的闭包也是其一个独有的特性,最明显的就是lua中的函数是可以通过返回值的方式来返回的。

function createCounter()
local count = 0  -- 外部局部变量

– 返回一个闭包,持有 count 变量的引用
return function()
count = count + 1
return count
end

count是一个局部变量,返回的函数持有这个count的引用。理论上退出createCounter这个作用域后count也会失效,但是他被返回的函数持有,当其他地方调用createCounter得到这个函数的时候也就得到了count,这个得到的count未被析构,他仍然能够被访问甚至修改

local counter1 = createCounter()
print(counter1())  -- 输出 1
print(counter1())  -- 输出 2
print(counter1())  -- 输出 3

local counter2 = createCounter() – 创建新的闭包
print(counter2()) – 输出 1
print(counter2()) – 输出 2

更复杂的例子:

function createAdder(x)
    return function(y)
        return x + y  -- 这里的 x 来自外部作用域
    end
end

local add5 = createAdder(5) – 创建一个闭包,x 固定为 5
print(add5(3)) – 输出 8 (5 + 3)
print(add5(10)) – 输出 15 (5 + 10)

local add10 = createAdder(10) – 创建另一个闭包,x 固定为 10
print(add10(3)) – 输出 13 (10 + 3)
print(add10(10)) – 输出 20 (10 + 10)

除了使用两个表来模拟封装外,也可以使用闭包来实现,将属性作为局部变量封装在某函数内,然后设置返回函数为控制该属性的函数,这样就能更实际地实现这个属性的封装。

 

lua面向对象的多态

lua中子类重写父类函数,调用函数会直接执行重写过后的逻辑。

C++中经常说静态多态,也就是不同参数列表,lua中其实也不支持这样做,不过可以强行从逻辑层面对重写函数进行参数列表的if判断(是否为nil),来实现逻辑重载。

从另一个角度上讲,因为lua本身就支持函数直接接受任意类型的参数,所以lua本身就支持多态。