Lua教程(二十一):编写C函数的技巧
1. 数组操作:
在lua中,“数组”只是table的一个别名,是指以一种特殊的方法来使用table。出于性能原因,lua的c api为数组操作提供了专门的函数,如:
void lua_rawgeti(lua_state* l, int index, int key);
void lua_rawseti(lua_state* l, int index, int key);
以上两个函数分别用于读取和设置数组中的元素值。其中index参数表示待操作的table在栈中的位置,key表示元素在table中的索引值。由于这两个函数均为原始操作,比涉及元表的table访问更快。通常而言,作为数组使用的table很少会用到元表。
见如下代码示例和关键性注释:
#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
extern "c" int mapfunc(lua_state* l)
{
//检查lua调用代码中传递的第一个参数必须是table。否则将引发错误。
lual_checktype(l,1,lua_ttable);
lual_checktype(l,2,lua_tfunction);
//获取table中的字段数量,即数组的元素数量。
int n = lua_objlen(l,1);
//lua中的数组起始索引习惯为1,而不是c中的0。
for (int i = 1; i <= n; ++i) {
lua_pushvalue(l,2); //将lua参数中的function(第二个参数)的副本压入栈中。
lua_rawgeti(l,1,i); //压入table[i]
lua_call(l,1,1); //调用function(table[i]),并将函数结果压入栈中。
lua_rawseti(l,1,i); //table[i] = 函数返回值,同时将返回值弹出栈。
}
//无结果返回给lua代码。
return 0;
}
2. 字符串操作:
当一个c函数从lua收到一个字符串参数时,必须遵守两条规则:不要在访问字符串时从栈中将其弹出,不要修改字符串。在lua的c api中主要提供了两个操作lua字符串的函数,即:
void lua_pushlstring(lua_state *l, const char *s, size_t l);
const char* lua_pushfstring(lua_state* l, const char* fmt, ...);
第一个api用于截取指定长度的子字符串,同时将其压入栈中。而第二个api则类似于c库中的sprintf函数,并将格式化后的字符串压入栈中。和sprintf的格式说明符不同的是,该函数只支持%%(表示字符%)、%s(表示字符串)、%d(表示整数)、%f(表示lua中的number)及%c(表示字符)。除此之外,不支持任何例如宽度和精度的选项。
#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
extern "c" int splitfunc(lua_state* l)
{
const char* s = lual_checkstring(l,1);
const char* sep = lual_checkstring(l,2); //分隔符
const char* e;
int i = 1;
lua_newtable(l); //结果table
while ((e = strchr(s,*sep)) != null) {
lua_pushlstring(l,s,e - s); //压入子字符串。
//将刚刚压入的子字符串设置给table,同时赋值指定的索引值。
lua_rawseti(l,-2,i++);
s = e + 1;
}
//压入最后一个子串
lua_pushstring(l,s);
lua_rawseti(l,-2,i);
return 1; //返回table。
}
lua api中提供了lua_concat函数,其功能类似于lua中的".."操作符,用于连接(并弹出)栈顶的n个值,然后压入连接后的结果。其原型为:
void lua_concat(lua_state *l, int n);
参数n表示栈中待连接的字符串数量。该函数会调用元方法。然而需要说明的是,如果连接的字符串数量较少,该函数可以很好的工作,反之,则会带来性能问题。为此,lua api提供了另外一组函数专门解决由此而带来的性能问题,见如下代码示例:
#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
extern "c" int strupperfunc(lua_state* l)
{
size_t len;
lual_buffer b;
//检查参数第一个参数是否为字符串,同时返回字符串的指针及长度。
const char* s = lual_checklstring(l,1,&len);
//初始化lua的内部buffer。
lual_buffinit(l,&b);
//将处理后的字符依次(lual_addchar)追加到lua的内部buffer中。
for (int i = 0; i < len; ++i)
lual_addchar(&b,toupper(s[i]));
//将该buffer及其内容压入栈中。
lual_pushresult(&b);
return 1;
}
使用缓冲机制的第一步是声明一个lual_buffer变量,并用lual_buffinit来初始化它。初始化后,就可通过lual_addchar将一个字符放入缓冲。除该函数之外,lua的辅助库还提供了直接添加字符串的函数,如:
void lual_addlstring(lual_buffer* b, const char* s, size_t len);
void lual_addstring(lual_buffer* b, const char* s);
最后lual_pushresult会更新缓冲,并将最终的字符串留在栈顶。通过这些函数,就无须再关心缓冲的分配了。但是在追加的过程中,缓冲会将一些中间结果放到栈中。因此,在使用时要留意此细节,只要保证压入和弹出的次数相等既可。lua api还提供一个比较常用的函数,用于将栈顶的字符串或数字也追加到缓冲区中,函数原型为:
void lual_addvalue(lual_buffer* b);
3. 在c函数中保存状态:
lua api提供了三种方式来保存非局部变量,即注册表、环境和upvalue。
1). 注册表:
注册表是一个全局的table,只能被c代码访问。通常用于保存多个模块间的共享数据。我们可以通过lua_registryindex索引值来访问注册表。
#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
void registrytestfunc(lua_state* l)
{
lua_pushstring(l,"hello");
lua_setfield(l,lua_registryindex,"key1");
lua_getfield(l,lua_registryindex,"key1");
printf("%s\n",lua_tostring(l,-1));
}
int main()
{
lua_state* l = lual_newstate();
registrytestfunc(l);
lua_close(l);
return 0;
}
2). 环境:
如果需要保存一个模块的私有数据,即模块内各函数需要共享的数据,应该使用环境。我们可以通过lua_environindex索引值来访问环境。
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
//模块内设置环境数据的函数
extern "c" int setvalue(lua_state* l)
{
lua_pushstring(l,"hello");
lua_setfield(l,lua_environindex,"key1");
return 0;
}
//模块内获取环境数据的函数
extern "c" int getvalue(lua_state* l)
{
lua_getfield(l,lua_environindex,"key1");
printf("%s\n",lua_tostring(l,-1));
return 0;
}
static lual_reg myfuncs[] = {
{"setvalue", setvalue},
{"getvalue", getvalue},
{null, null}
};
extern "c" __declspec(dllexport)
int luaopen_testenv(lua_state* l)
{
lua_newtable(l); //创建一个新的表用于环境
lua_replace(l,lua_environindex); //将刚刚创建并压入栈的新表替换为当前模块的环境表。
lual_register(l,"testenv",myfuncs);
return 1;
}
lua测试代码如下。
require "testenv"
print(testenv.setvalue())
print(testenv.getvalue())
--输出为:hello
3). upvalue:
upvalue是和特定函数关联的,我们可以将其简单的理解为函数内的静态变量。
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
extern "c" int counter(lua_state* l)
{
//获取第一个upvalue的值。
int val = lua_tointeger(l,lua_upvalueindex(1));
//将得到的结果压入栈中。
lua_pushinteger(l,++val);
//赋值一份栈顶的数据,以便于后面的替换操作。
lua_pushvalue(l,-1);
//该函数将栈顶的数据替换到upvalue(1)中的值。同时将栈顶数据弹出。
lua_replace(l,lua_upvalueindex(1));
//lua_pushinteger(l,++value)中压入的数据仍然保留在栈中并返回给lua。
return 1;
}
extern "c" int newcounter(lua_state* l)
{
//压入一个upvalue的初始值0,该函数必须先于lua_pushcclosure之前调用。
lua_pushinteger(l,0);
//压入闭包函数,参数1表示该闭包函数的upvalue数量。该函数返回值,闭包函数始终位于栈顶。
lua_pushcclosure(l,counter,1);
return 1;
}
static lual_reg myfuncs[] = {
{"counter", counter},
{"newcounter", newcounter},
{null, null}
};
extern "c" __declspec(dllexport)
int luaopen_testupvalue(lua_state* l)
{
lual_register(l,"testupvalue",myfuncs);
return 1;
}
lua测试代码如下。
require "testupvalue"
func = testupvalue.newcounter();
print(func());
print(func());
print(func());
func = testupvalue.newcounter();
print(func());
print(func());
print(func());
--[[ 输出结果为:
1
2
3
1
2
3
--]]
上一篇: Lua中算术运算符的使用示例
下一篇: Lua中关系运算符的使用教程