Table的使用,面向对象思想
(关于table在lua脚本中的定义以及语法,不做介绍,baidu以及google非常多。本文将介绍,lua在C++面向对象思想的应用)
一(metatable)
lua的metatable的官方解释,
1. 每个table和每个full user data都可以有自己的metatable,并可通过setmetatable和getmetatable进行访问
2. 其他任何lua类型,每种类型共享一个metatable;比如number类型共享一个metatable;string类型共享一个metatable;这些类型的metatable无法更改,除非用C API
3. metatable的key叫事件(event),value叫元方法(metamethod)
4. 除了__index,__newindex可以是function或table外,其他元方法都必须是function
举个例子说明,对于一个非数值类型的值,我们可以修改元表里的"__add"字段里的函数,来定义其加法的行为。这样,对该类型值进行加法运算,就会自动调用__add对应的元方法。 如果调用该类型一个未定义的key或者未注册的函数,就会查找__index事件。等等。
二(C++中类的成员函数)
类,是某些类型的集合体,即,内存块。所有的类成员地址,相对于类对象首地址都有一定的便宜量,尽管你声明成protected或者private,也可以根据偏移量得到该成员。(懂汇编语言的,这句话不难理解)。类成员函数,他相对于所有的类对像,地址是相同的,可以理解成他是类的静态成员。也可以理解成某种特殊意义的表。
举例说明,某个类C1成员函数Fn,计算两个数相加,可以得到以下汇编代码:
// C1 a;
_asm
{
mov eax, dword ptr[y];
push eax; // 参数压栈
mov eax, dword ptr[x];
push eax; // 参数压栈
lea ecx, [a]; // ecx保存变量a的地址
call C1::Fn; // 调用成员函数
} (这里仅仅列举一种情况,对于不同类型的C++函数,对应的汇编代码不相同,比如使用__stdcall声明的函数)
三(面向对象思想在lua脚本中的应用)
Win32窗口API中,每个函数都有HWND窗口句柄参数,可以理解成这个参数就是我们的“窗口类对象”。所以,可以模拟这样的函数实现lua脚本的面向对象应用。
例如,
l=CreateLine();
l.Initialize(l, 0, 0, 100, 100);
l.SetColor(l, 0);
该脚本意思是说,绘制0,0到100,100的一条黑色直线。不过每次都传递 l 参数,太麻烦了。好在,lua脚本有:(冒号)操作符,l.Initialize(l, 0, 0, 100, 100);等同于l:Initialize(0, 0, 100, 100);这样就如同C++中调用类成员函数一样。
四(举例说明)
还是拿上面划线的例子说明:(看半天理论,不如看一行代码。如同使用图的表达方式比文字表达方式快一样)
[In C++]
class CLine
{
public:
void Initialize(int bx, int by, int ex, int ey);
}
int lua_LineInitialize(lua_State* L)
{
if (lua_gettop(L) < 5)
return 0;
lua_getfield(L, 1, "inskey");
int bx = luaL_checkint(L, 2);
int by = luaL_checkint(L, 3);
int ex = luaL_checkint(L, 4);
int ey = luaL_checkint(L, 5);
DWORD_PTR dwLine = luaL_checkint(L, -1);
reinterpret_cast<CLine*>(dwLine)->Initialize(bx, by, ex, ey);
return 0;
}
// int lua_LineSetColor(lua_State* L);
int lua_CreateLine(lua_State* L)
{
static bool bHasInit = false;
if (!bHasInit)
{
bHasInit = true;
lua_newtable(L); // 定义操作函数表
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index"); // __index指向table(自己)
lua_pushcfunction(L, lua_LineInitialize);
lua_setfield(L, -2, "Initialize");
lua_pushcfunction(L, lua_LineSetColor);
lua_setfield(L, -2, "SetColor");
lua_setglobal(L, "MetaLine");
}
CLine* p = new CLine;
lua_newtable(L);
lua_pushnumber(L, (DWORD_PTR)p);
lua_setfield(L, -2, "inskey"); // 保存CLine指针对象为inskey
lua_getglobal(L, "MetaLine");
lua_setmetatable(L, -2); // 为新创建的table设置操作函数表,即,设置名称为MetaLine的 metatable.
return 1; // 一个返回值,table
}
// 注册代码,省略。。。
// lua_register(L, "CreateLine", lua_CreateLine);
[In lua]
l=CreateLine();
l:Initialize(0, 0, 100, 100);
l:SetColor(0);
(感觉是否在写C++程序呢?)
五(说明)
lua中,每个table都是不同的类型,所以为不同的table设置不同的metatable,不会搞乱。网上有的例子,使用luaL_ref(L, LUA_REGISTRYINDEX)函数得到一个注册key,该key为数据类型,然后设置该key的metatable为MetaLine,这样就为lua所有的数据类型设置了metatable。那么,我如果再定义一个Rect类型,再次注册得到一个数据类型的key,然后设置该key的metatable为MetaRect,又把所有的数据类型重新设置了一次metatable,这样,刚刚定义的line就不能调用line的方法了,因为数据类型的metatable已经为rect的方法。