当前位置: 首页 > 科技观察

一个Lua面向对象编程基本原理的例子,你了解多少?

时间:2023-03-18 01:16:59 科技观察

一些废话Lua是一门小而美的语言,用户不多。估计看这篇文章的人不会太多,就当做笔记吧。本文主要讲述如何在Lua语言中通过表结构实现面向对象编程。主要原因是某鸟的教程错误较多,估计是示例代码没有自己测试过;关于Lua语言中table和metatable的基础知识,这里就不赘述了,官方手册中的描述已经很清楚了。测试代码1#!/usr/bin/lua23--------------------------classA4A={5a=1,6funcA=function()7print("thisisfuncA")8end9}1011functionA:new(t)12localt=tor{}13self.__index=self14setmetatable(t,self)15返回t16end1718函数A:myadd(num)19self.a=self.a+num20end2122objA=A:new()23print("objA.a="..objA.a)24print(objA.funcA())25print(string.rep("-",20))2627------------------------------classB28B=A:new({29b=2,30funcB=function()31print("thisisfuncB")32end33})3435objB=B:new()36print("objB.a="..objB.a)37打印("objB.b="..objB.b)38打印(objB.funcA())39打印(objB.funcB())4041objB:myadd(10)42打印("objA.a="..objA.a)43print("objB.a="..objB.a)执行结果如下:$./oop.luaobjA.a=1thisisfuncA-------------------objB.a=1objB.b=2这是funcA这是funcBobjA.a=1objB.a=11代码说明Baseclass(父类)A首先分析下4-25行代码4-9行:定义父类A的成员变量和函数(按照C++中的习惯,可以称为方法),可见函数在Lua语言中是“一等公民”,可以赋值给变量。第11-16行:相当于构造函数,用于创建父类A的对象。第18-20行:为父类A添加一个函数,后面分析子类B时会讲到。第22行:调用A:new()函数创建类A的对象并将其分配给变量objA。在A:new()函数中,关键是第13行代码:此时self等于A,相当于A.__index=A,是合法的。因为函数的调用方式是A:new(),Lua的语法糖会将A作为第一个参数传递给new()函数的第一个隐参self。然后在第14行执行setmetatable(t,self),相当于把表t的metatable设置为A。上面两行理解后,第23-24行的打印语句就简单了:第23行:因为有表objA中没有成员a,但是objA被设置为元表A,而这个元表A有__index属性,该属性的值是表A本身,所以我去A中查找是否有成员a,然后打印出:objA.a=1__index属性的值可以是表也可以是函数;但这里有一些特别的东西:__index设置为A本身;第24行:查找函数的过程同理,查找元表A的__index属性的值,即表A本身的funcA函数,然后调用,打印出来:thisisfuncAderivedclass(childClass)B28-33行:定义了子类B,但它也是一个对象。在创建函数A:new(t)中,参数t的值是:localt={b=2,funcB=function()print("thisisfuncB")end}此时,self仍然是父类A,B的创建过程与objA相同,只是为参数t设置了子类B的成员变量和函数。因此,B的元表设置为A(14行代码信用),当然A的__index仍设置为A本身。关键是第35行:objB=B:new(),你要小心了。子类B并没有自己的new函数,但是B类的metatable(也是一张表)设置为A,并且A.__index=A,所以最终找到了A中的new函数,也就是11-16行代码。进入该函数时,第一个隐藏参数self设置为B,因为函数调用形式为:B:new()。所以:第13行self.__index=self等价于设置B.__index=B14行etmetatable(t,self)等价于将表t的metatable设置为Bnew()函数,然后将t赋值给objB。我们来看看第36-39行的打印语句:36print("objB.a="..objB.a)37print("objB.b="..objB.b)38print(objB.funcA())39print(objB.funcB())第36行:objB中没有成员a,但是objB的metatable是B,B.__index=B,所以去B中找a。虽然B中没有a,但是B的元表是A,A.__index=A,所以在A中找到成员a,打印出来:objB.a=line137:thereisnomemberbinobjB,但是objB的metatable是B,而B.__index=B,所以在B中找到了成员b,所以打印出来:objB.b=237and38查找过程类似,只是换成了一个函数.子类对象操作自己的变量第41行:objB:myadd(10)。查找myadd函数的过程和查找obj.a的过程一样,这里重复一遍:objB中没有函数myadd,但是objB的metatable是B,B.__index=B,所以转到B搜索我的地址;B中虽然没有myadd,但是B的元表是A,A.__index=A,所以在A中找到myadd函数;然后调用函数:18functionA:myadd(num)19self.a=self.a+num20endself等于objB,所以函数体等于:objB.a=objB.a+10加法表达式中objB.a的读取过程上面已经介绍过了,最终定位到父类A中的a,即:1.1+10=11,然后将11赋值给objB.a。在赋值操作中,赋值的objB.a不再是父类A中的a!因为objB本质上是一张表,所以在为objB设置键值对时:如果key已经存在,则直接设置key的值;如果key不存在,lua会检查其metatable中是否有__newindex字段(可以是表也可以是函数);2-1.如果有__newindex字段,则调用__newindex(如果是函数),或者在__newindex中添加键值对(如果是表);2-2.如果没有__newindex字段,则直接将键值对存入objB;根据上面的规则,设置objB.a=11。理解了上面的内容之后,42行和43行的打印语句就不复杂了。42行:objA最终找到的a是父类A的成员a,打印出:objA.a=1。43行:ObjB已经有成员a,所以打印出:objB.a=11。继续以上面的基础继承,然后从子类B派生出类C,从C派生出类D……是没有问题的,如下图:C=B:new()objC=C:new()print("objC.a="..objC.a)print("objC.b="..objC.b)print(objC.funcA())print(objC.funcB())感兴趣的读者可以测试一下他们自己。