Lua:面向对象/C之间的交互

前段时间对平台的任务感兴趣,其要求是一周内12篇博文,尝试了之后发现还是太敷衍了,之后还是回归到内容本身上来,尽量保证一篇博文的内容能涵盖足够多的知识点或者足够深的思考成分。

面向对象

面向对象主要有三个方面:封装、继承和多态。Lua若做到了这三点,则认为是也具有面向对象的特征。Lua可以通过表来实现上面三个特征。

类本身的概念就是创建对象的模板。然而Lua本身不存在类的概念,但是可以创建一个原型(prototype)对象,当调用不属于对象的某些操作时,会最先到prototype中查找这些操作。在lua中若想要对象b作为对象a的prototype只需要以下代码:

Lua 复制代码
setmetatable(a, {__index = b}) 

继承

有了index后,在a域找不到的相关属性就会选择在b域查找。如若有更多的对象选择继承b,则覆写以上代码即可,此时的b就被用来当作是prototype了。比如如下实例代码,可选择将Account作为prototype对象:

Lua 复制代码
Account = {}
Account.balance = 0
function Account.new (o)
    o = o or {} -- create object if user does not provide one 
    setmetatable(o, Account)
    Account.__index = Account
   return o
end

function Account:deposit (v)
    self.balance = self.balance + v
end

a = Account.new{balance = 100}
a:deposit(100)
print(a.balance) --200

lua面向对象有一个有趣的方式就是不需要创建新类去指定新的对象行为,可以直接在对象中实现相关操作即可,比如上面的a对象,可以直接赋予其新的函数:

Lua 复制代码
function a:NewBalance()
    self.balance = 0
end
print(a.balance) -- 200
a:NewBalance()
print(a.balance) -- 0

也可以用相同方式实现多重继承,即实现index metamethod,使其在多个父类中查找子类不存在的域:

Lua 复制代码
function search (k, plist) 
    for i=1, #plist do
        local v = plist[i][k] -- try 'i'-th superclass 
        if v then return v end
    end
end

function createClass(...)
    local arg = {...}
    local c = {}
    setmetatable(c, {__index = function (t, k)
        return search(k, arg)
    end})
end

privacy封装

lua本身不具有私有性访问机制。lua设计初衷是小型到中型的程序设计,避免太冗余和太多的人为限制。但是我们依然可以有方案实现私有封装操作:创建一个函数工厂,内设状态和闭包,外部只能获取到return的函数table而没有状态:

Lua 复制代码
function newAccount (initialBalance) 
    local self = {balance = initialBalance} 
    local withdraw = function (v) 
     self.balance = self.balance - v 
    end 
    local deposit = function (v) 
     self.balance = self.balance + v 
    end 
    local getBalance = function () return self.balance end
    return { 
        withdraw = withdraw, 
        deposit = deposit, 
        getBalance = getBalance 
    } 
end

如上所示,除了函数newAccount内部可控制balance变量以外,都无法再对balance进行操作,外部只能调用函数接口。

C API

C API是一个C代码与Lua进行交互的函数集,分为以下几个部分:读写Lua全局变量的函数,调用Lua函数的函数,运行Lua代码片段的函数,注册C函数然后可在Lua中被调用的函数等。

在 C 和 Lua 之间通信关键内容在于一个虚拟的栈。几乎所有的 API 调用都是对栈上的值进行操作,所有 C 与 Lua 之间的数据交换也都通过这个栈来完成。另外,你也可以使用栈来保存临时变量。栈的使用解决了 C 和 Lua 之间两个不协调的问题:第一,Lua 会自动进行垃圾收集,而 C 要求显式的分配存储单元,两者引起的矛盾。第二,Lua 中的动态类型和 C 中的静态类型不一致引起的混乱。

堆栈

Lua和C之间交互主要面临两个问题,一个是内存管理,一个是静态类型和动态类型的不一致性。比如a[k] = v这一段代码,k和v很可能是好几种不同的类型,而且由于metatable的存在,a也可能会有不同的类型。如果不同类型之间的赋值函数组合在一起,很有可能需要几十个不同的函数来完成这一个操作。

当然我们可以在C侧声明union类型解决该问题,倘若我们采用settable函数来实现a[k] = v这段代码:

cpp 复制代码
void lua_settable (lua_Value a, lua_Value k, lua_Value v);

但是这会面临另一个问题:内存管理。如果把lua值保存在C侧,lua引擎便不清楚这个值是否还在使用,很可能会误认为某个值是垃圾从而收集它。并且,lua的这个动态类型是非常复杂的,映射到其他语言会非常困难,lua设计初衷并非只是C/C++,还要和其他主流语言比如Java进行交互,不可能每一个语言都要完成类似这种的转换。

于是Lua API实现了一个抽象的栈在Lua和C之间交换值。无论你何时想要从 Lua 请求一个值(比如一个全局变量的值),调用 Lua,被请求的值将会被压入栈。无论你何时想要传递一个值给 Lua,首先将这个值压入栈,然后调用 Lua(这个值将被弹出)。虽然我们需要函数将C类型压入栈和函数从栈上取值,但是我们至少是避免了组合爆炸。而且这个栈由lua管理,垃圾回收器知道哪个值在被C使用。

Lua侧严格采用先进后出的方式操作栈,它只会改变栈顶部分;C侧却可以查询、删除、插入栈上的任何元素。

压入元素

可以将每种可用C来描述的Lua类型压栈:

cpp 复制代码
void lua_pushnil (lua_State *L); 
void lua_pushboolean (lua_State *L, int bool); 
void lua_pushnumber (lua_State *L, double n); 
void lua_pushlstring (lua_State *L, const char *s, size_t length); 
void lua_pushstring (lua_State *L, const char *s); 

任意的字符串(char*类型,允许包含'\0'字符)用 lua_pushlstring,它要求一个明确的长度作为参数。以'\0'结束的字符串(const char*)用 lua_pushstring。

无论何时压入一个元素到栈上,都需要确保在栈上有空间来做这件事情。可以调用下面函数来检测栈上是否有足够需要的空间。

cpp 复制代码
int lua_checkstack (lua_State *L, int sz);

查询元素

API用索引访问栈内元素。第一个入栈的索引是1,栈顶是-1,-2指的是栈顶下方的那个元素。通过lua_isnumber/lua_isstring等等函数来判断栈内某个元素是否是指定类型:

cpp 复制代码
int lua_is... (lua_State *L, int index);

lua_isnumber 和 lua_isstring 函数不检查这个值是否是指定的类型,而是看它是否能被转换成指定的那种类型。例如,任何数字类型都满足 lua_isstring。

另外,通过lua_tonumber/lua_tostring等函数实现对类型的转换:

cpp 复制代码
int lua_toboolean (lua_State *L, int index); 
double lua_tonumber (lua_State *L, int index); 
const char * lua_tostring (lua_State *L, int index); 
size_t lua_strlen (lua_State *L, int index); 

注意const修饰符,包括上方lua_pushstring函数,都意味着lua不允许将指向字符串的指针保存到访问他们的外部函数中。

其他堆栈操作

通过以下函数完成普通的堆栈操作:

cpp 复制代码
int lua_gettop (lua_State *L); 
void lua_settop (lua_State *L, int index); 
void lua_pushvalue (lua_State *L, int index); 
void lua_remove (lua_State *L, int index); 
void lua_insert (lua_State *L, int index); 
void lua_replace (lua_State *L, int index);

顾名思义,gettop获取堆栈元素个数,settop设置栈大小,pushvalue复制索引index位置上的元素到栈顶,remove移除指定index位置上的元素,insert将栈顶元素插入到index位置上,replace将栈顶元素弹出并设置到指定index位置上。

相关推荐
ephemerals__5 分钟前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞9 分钟前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货10 分钟前
Rust 的简介
开发语言·后端·rust
湫ccc18 分钟前
《Python基础》之基本数据类型
开发语言·python
Matlab精灵19 分钟前
Matlab函数中的隐马尔可夫模型
开发语言·matlab·统计学习
Microsoft Word20 分钟前
c++基础语法
开发语言·c++·算法
数据小爬虫@22 分钟前
如何利用java爬虫获得淘宝商品评论
java·开发语言·爬虫
qq_1728055930 分钟前
RUST学习教程-安装教程
开发语言·学习·rust·安装
wjs202437 分钟前
MongoDB 更新集合名
开发语言
monkey_meng41 分钟前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust