Lua 中的每个值都可以有一个 元表。 这个 元表 就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。 如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。 例如,当你对非数字值做加操作时, Lua 会检查该值的元表中的 "__add" 域下的函数。 如果能找到,Lua 则调用这个函数来完成加这个操作。
表的简单使用可参考 :Lua学习笔记:Lua里table表的使用例及介绍
元表的简单使用可参考:Lua学习笔记:Lua里metatable元表的使用
而熟悉Lua表和元表的都知道,通过元表的 __index 字段可以让表 t 获得一些本身没有的字段, 通过这样的一个形式,我们就可以达到从实例中调用类的方法,这样我们就可以把Lua的元表比作C++中的纯虚类,通过把Lua的元表当做一个普通表的 方法类,去实现Lua Class.😈
#include <iostream>
#include <lua.hpp>
int main()
{
// 创建一个虚拟机
lua_State* L = luaL_newstate();
// 加载一些常用的系统库
luaL_openlibs(L);
// 加载lua文件并执行
if (luaL_dofile(L, "LuaClass.lua"))
{
// 在lua中 -1表示栈顶 如果出错 出错结果会放置在栈顶中
printf("%s\n", lua_tostring(L, -1));
}
// 关闭虚拟机
lua_close(L);
return 0;
}
面向对象三大特性包括:封装、继承、多态。
通过元表的 __index 字段可以让表 t 获得一些本身没有的字段, 通过这样的一个形式,我们就可以达到从实例中调用类的方法,但是实例的成员变量又是相互独立的。另外,__index 也可以是方法。
-- LuaClassP.lua
-- 定义一个Lua Class生成器
local Class = function(className)
local class = {_className = className}
class.ctor = false -- 构造函数声明
-- ... 是可变参数
class.new = function(...)
local tab = {}
setmetatable(tab, {__index = class})
tab:ctor(...)
return tab
end
return class
end
-- 定义一个类 可以看做C++中的类继承纯虚类
local C1 = Class("C1")
-- 构造函数ctor具体实现
function C1:ctor(x, y, z)
-- 记录成员数据
self.x = x
self.y = y
self.z = z
end
-- 定义一个方法 可以看做C1类自己的方法
function C1:Print()
-- 遍历成员变量
local values = {}
for k, v in pairs(self) do
if type(v) ~= 'function' then
table.insert(values, tostring(v))
end
end
print(table.concat(values, ' '))
end
-- new一个C1对象实例 可以看做C++中的new
local c1 = C1.new(1, 2, 3)
c1:Print() -- 输出 1 2 3
print(c1._className) -- 输出C1
类的继承通过自定义一个super参数配合元表的__index实现
如果提供了 super 参数,则设置类的元表为父类,以便在当前类中找不到方法或属性时可以去__index域查找父类。
-- LuaClassP.lua
-- 定义一个类生成器函数
local Class = function(className, super)
local class = {_className = className, super = super}
if super then
-- 设置类的元表,此类中没有的,可以查找父类是否含有
setmetatable(class, {__index = super})
end
class.ctor = false -- 构造函数声明
-- ... 是可变参数
class.new = function(...)
local tab = {}
setmetatable(tab, {__index = class})
class:ctor(...)
return tab
end
return class
end
-- 定义一个类 可以看做C++中的类继承抽象类接口
local BaseClass = Class("BaseClass")
-- 基类的构造函数
function BaseClass:ctor(x, y)
self.x = x
self.y = y
end
-- 基类的方法
function BaseClass:PrintBase()
print("BaseClass: x =", self.x, "y =", self.y)
end
-- 创建一个 BaseClass 对象实例
local baseObj = BaseClass.new(1, 2)
baseObj:PrintBase() -- 输出 BaseClass: x = 1 y = 2
-- 打印类名
print(baseObj._className) -- 输出 BaseClass
-- 定义一个派生类
local DerivedClass = Class("DerivedClass", BaseClass)
-- 派生类的构造函数
function DerivedClass:ctor(x, y, z)
-- 调用基类的构造函数
self.super:ctor(x, y)
self.z = z
end
-- 派生类重载基类方法
function DerivedClass:PrintBase()
print("DerivedClass: x =", self.x, "y =", self.y, "z =", self.z)
end
-- 派生类的方法
function DerivedClass:PrintDerived()
print("DerivedClass: z =", self.z)
end
-- 创建一个 DerivedClass 对象实例
local derivedObj = DerivedClass.new(1, 2, 3)
derivedObj:PrintBase() -- 输出 DerivedClass: x = 1 y = 2 z = 3
derivedObj:PrintDerived() -- 输出 DerivedClass: z = 3
print(derivedObj._className) -- 输出 DerivedClass
从C/C++层面去实现也是要借助元表的形式,如果使用了依附于 Lua 绑定库(如 sol2、tolua++、luabind 等),这些库提供了 C++ 代码与 Lua 脚本交互的能力,实现起来会更加方便简洁。
以下提供一个无库函数使用的简单Lua Class实现使用例。
Tips:一般最好自己实现一个__gc字符段对应的析构函数,这样方便释放在C/C++创造的一些内存使用,毕竟Lua只会释放自己的内存使用,C/C++层面的内存使用需要自己释放。
Lua垃圾自动回收机制文章可参考:
#include <stdio.h>
#include <lua.hpp>
#define LUA_TEST "LUA_TEST"
//元表成员变量声明
struct tagTest
{
int a;
};
//成员方法类比于析构函数,lua是垃圾自动回收机制,当对象不再使用时会自动删除
static int f_gc(lua_State* L)
{
struct tagTest* p = (struct tagTest*)luaL_checkudata(L, 1, LUA_TEST);// 测试是否属于该元表 失败返回NULL
printf("调用gc销毁了对象");
return 0;
}
static int f_tostring(lua_State* L)
{
struct tagTest* p = (struct tagTest*)luaL_checkudata(L, 1, LUA_TEST);// 测试是否属于该元表 失败返回NULL
lua_pushstring(L, "f_tostring LUA_TEST");
return 1;// 返回值表示压入的几个参数
}
static int f_GetA(lua_State* L)
{
struct tagTest* p = (struct tagTest*)luaL_checkudata(L, 1, LUA_TEST);// 测试是否属于该元表 失败返回NULL
lua_pushinteger(L, p->a);
return 1;// 返回值表示压入的几个参数
}
static int f_SetA(lua_State* L)
{
printf("%d\n", lua_gettop(L));
struct tagTest* p = (struct tagTest*)luaL_checkudata(L, 1, LUA_TEST);// 测试是否属于该元表 失败返回NULL
int a = luaL_checkinteger(L, 2);
p->a = a;
return 0;// 返回值表示压入的几个参数
}
/*
** 元表处理方法
*/
static const luaL_Reg flib[] = {
{"SetA",f_SetA},
{"GetA",f_GetA},
{"__gc", f_gc},//删除时调用
{"__tostring", f_tostring},
{NULL, NULL}
};
static void createmeta(lua_State* L) {
luaL_newmetatable(L, LUA_TEST); /* 创建一个元表*/
lua_pushvalue(L, -1); /* 复制指定位置的元素至栈顶 */
lua_setfield(L, -2, "__index"); /* metatable.__index = metatable 会弹出栈顶元素 */
luaL_setfuncs(L, flib, 0); /* 将方法添加进入元表 */
lua_pop(L, 1); /* 弹出newmetabhle的元表 */
}
static int createTest(lua_State* L)
{
//创建一个用户数据 lua内部会自动释放
struct tagTest* p = (struct tagTest*)lua_newuserdata(L, sizeof(struct tagTest));
luaL_setmetatable(L, LUA_TEST);// 和元表进行关联
return 1;
}
int LuaTest8()
{
// 创建一个虚拟机
lua_State* L = luaL_newstate();
// 加载一些常用的系统库
luaL_openlibs(L);
// 创建一个元表
createmeta(L);
lua_pushcfunction(L, createTest);
lua_setglobal(L, "createTest");
// 加载lua文件并执行
if (luaL_dofile(L, "Test8.lua"))
{
// 在lua中 -1表示栈顶 如果出错 出错结果会放置在栈顶中
printf("%s\n", lua_tostring(L, -1));
}
// 关闭虚拟机
lua_close(L);
return 0;
}
-- Test8.lua
local test = createTest()
test:SetA(10)
print(test:GetA())
print(test)
禁止Lua创造全局变量
-- 通过设置全局表的元表 禁止创造全局变量
setmetatable(_G, {
__newindex = function(t, key, value)
print("Cannot create global variable '" .. key .. "'")
end
})
最近在试着找工作/(ㄒoㄒ)/~~,本篇文章纰漏难以细究,若有差错,还望指出,笔者感激不尽。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。