一步步进行 LUA的OOD封装 (一)

同python不同, LUA并没那么庞大也没提供众多的功能库,适合做相对对立的系统而非整个应用。LUA官方版本只包括一个精简的核心和最基本的库。这使得它体积小、启动速度快,从而适合嵌入在别的程序里同时也方便移植。5.0版本以前的LUA对面向对象支持不多,这个始于1993年的产物核心思想是用虚拟栈作为与宿主语言交互的手段,所以主流的封装方式还是以注册函数为主。但能直接对对象操作一直作为高级语言的一个特征,所以不少lua的第三方库以对象化封装作为扩充lua的封装方式,有代表性的是 LuaPlus LuaBind  toLua++  LuaTinker。 其中我觉得最能体现lua 精简精髓的应该是luatinker,原因无他,就一个.h和cpp文件。 不像其他的有借助boost这种庞大的库来实现一个对原本袖珍的lua库进行扩展的。所以接下来的讨论依然围绕luatinker对这种方式的实现为主。


一般意义下对OOD中关键的类的理解无非是 数据+方法的 域 集合,还有其继承 虚函数等。

从下面列举的一一对应来看看lua是如何一步步向C++的类迈进的

C++  
LUA
Class
Table
this
lightuserdata
Member data
metatable
Member function
metatable
Virtual Member function
metatable

转换前的基础

Table做为lua对数据集合的表述手段,其地位也正如class在C++中的一样,多半用户定义的数据都是它。简单来看 表名 和类名 ,表项和 类成员数据,成员方法(lua的表项可以是数据也可以是function,key对应的名字而val就是真正的函数) 很多都感觉很像,那么作为ood的第一步,让lua中使用对象的载体,Table无疑是是最适合的(其实在lua中也没有太多其他类型可选)

Lua中的另外一中数据 Lightuserdata 类型用来将任意 C 数据保存在 Lua 变量中。 这个类型相当于一块原生的内存,除了赋值和相同性判断,Lua 没有为之预定义任何操作。 然而,通过使用 metatable (元表) ,用户可以为 userdata 自定义一组操作。 这也正如this指针在c++中地位一样,表述默认的一块特定地址而已

Lua 中的每个值都可以用一个 metatable。 这个 metatable 就是一个原始的 Lua table , 它用来定义原始值在特定操作下的行为。可以通过在 metatable 中的特定域设一些值来改变拥有这个 metatable 的值 的指定操作之行为。metatable 中的键名称为为 事件 (event) ,把其中的值叫作 元方法 (metamethod)
一个 metatable 可以控制一个对象做数学运算操作、比较操作、连接操作、取长度操作、取下标操作时的行为,metatable 中还可以定义一个函数,让 userdata作垃圾收集时调用它(这点对于想在LUA环境中直接创建一个Userdata对象而非LightUserdata时很重要)对于这些操作,Lua 都将其关联上一个被称作事件的指定健。 当 Lua 需要对一个值发起这些操作中的一个时, 它会去检查值中 metatable 中是否有对应事件。 (这种操作既可以是一个指定的函数调用 .func(...)也能是一个opreator 比如[], =,  :)如果有的话,键名对应的值(元方法)将控制 Lua 怎样做这个操作。

Class-> Table

一个Class 对应一个 Table
通常约定这个Table名和Class 名对应
此Table 的Metatable对应的事件做以下修改

__index 事件: 对应脚本: 和 []词法

eg:  temp = a:num1 or  temp = a[num1]

__index事件的元方法(metamethod)一般作为对象成员函数的调用标,如 player:levelup(2)  其返回值为通过函数名(key)取到的对象调用的成员函数,这块在有继承关系的class上需要额外实现一套递归查询的方法

__newindex 事件: 对应脚本中对: 和 []操作项赋新值

eg:a={num1=1} or  a:num1 = 1 or a[num1]  = 1

__gc事件: 对应class 的析构函数

__call事件:对应class 的构造函数

*  该Table会作为所有此类型的class变量 在lua环境中对应 lightuserdata 的 metatable,因为metatable可以嵌套,所以该metatable的事件会自然为lightuserdata所用

          

 this->data       

  先来看段简单的C++代码,其作用是用类成员数据指针替代成员变量名对成员变量的调用,因为变量名是给编译器看的,运行期的要想使用就得靠指针了
class A {int m_nN1; float m_fSpeed;}
float A::*pfl = &A:: m_fSpeed;
A a1;  a1.*pfl = 0.5f;

理解上面的代码时怎么作用的了,接下来就是把这种成员变量的操作权传递给lua环境
  1. 把a1的地址作为lightuserdata 的val 传入LUA
  2.  把pfl 及其对应的string-key “Speed”传入LUA
  3.  当脚本中调用user:Speed 的时候通过lightuserdata上的metatable映射成宿主语言 (&a1)->pfl
  4.  通过push和pop操作将宿主对象的值传入或赋值
  

  this-> function

同理成员的函数调用也可以通过成员函数指针向lua传递

class A{
const char* name(int a) 
{ return “cos”;} 
}
const char* (A::*pfunc)();
pfunc = &A::name;
A a2;  
(a2.*pfunc )(11);
老办法,先传数据再传递需要调用的函数地址

  1. 把a2的地址作为lightuserdata 的val 传入LUA
  2.  把pfunc及其对应的string-key “Name”传入LUA
  3.  当脚本中调用user. Name 的时候通过lightuserdata上的metatable映射成宿主语言 (&a2)->pfunc
  4.  根据function 的参数调用等次数的 push 和区分返回值是否为void调用0次或1次pop 

this-> virtual function

原理同this->function一样,只是.*pfunc 取到的是虚表里的地址;宿主语言已经保证b1.*pfunc 的调用会映射到正确的函数上了
class A{
virtual const char* name(int a)
       {return “class a”;}
 }
class B: public A { 
const char* name(int a)
       {return “class b”;}
 }

const char* (A::*pfunc)();
pfunc = &A::name;
B b1;  (b1.*pfunc )(11);

  1. 把a2的地址作为lightuserdata 的val 传入LUA
  2.  把pfunc及其对应的string-key “Name”传入LUA
  3.  当脚本中调用user. Name 的时候通过lightuserdata上的metatable映射成宿主语言 (&a2)->pfunc
  4.  根据function 的参数调用等次数的 push 和区分返回值是否为void调用0次或1次pop 

Inherit

在这里实现继承的目的是为了注册一个基类的接口,不必再为其子类再注册同样的接口,纯粹是为了少写一些注册代码。原理如下:
对metatable 实现一个 __parent的事件,该事件不会在LUA语法环境中被调用到,但在宿主中对取得meta_table的封装里加入递归逻辑,在本层找不到对应data和function的key所对应时,调用其parent的meta_table去找,直到找到某个父类对应的val,否则返回nil




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值