Step By Step(编写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很少会用到元表。
见如下代码示例和关键性注释:
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern "C" int mapFunc(lua_State* L) 8 { 9 //检查Lua调用代码中传递的第一个参数必须是table。否则将引发错误。 10 luaL_checktype(L,1,LUA_TTABLE); 11 luaL_checktype(L,2,LUA_TFUNCTION); 12 //获取table中的字段数量,即数组的元素数量。 13 int n = lua_objlen(L,1); 14 //Lua中的数组起始索引习惯为1,而不是C中的0。 15 for (int i = 1; i <= n; ++i) { 16 lua_pushvalue(L,2); //将Lua参数中的function(第二个参数)的副本压入栈中。 17 lua_rawgeti(L,1,i); //压入table[i] 18 lua_call(L,1,1); //调用function(table[i]),并将函数结果压入栈中。 19 lua_rawseti(L,1,i); //table[i] = 函数返回值,同时将返回值弹出栈。 20 } 21 22 //无结果返回给Lua代码。 23 return 0; 24 }
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(表示字符)。除此之外,不支持任何例如宽度和精度的选项。
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern "C" int splitFunc(lua_State* L) 8 { 9 const char* s = luaL_checkstring(L,1); 10 const char* sep = luaL_checkstring(L,2); //分隔符 11 const char* e; 12 int i = 1; 13 lua_newtable(L); //结果table 14 while ((e = strchr(s,*sep)) != NULL) { 15 lua_pushlstring(L,s,e - s); //压入子字符串。 16 //将刚刚压入的子字符串设置给table,同时赋值指定的索引值。 17 lua_rawseti(L,-2,i++); 18 s = e + 1; 19 } 20 //压入最后一个子串 21 lua_pushstring(L,s); 22 lua_rawseti(L,-2,i); 23 return 1; //返回table。 24 }
Lua API中提供了lua_concat函数,其功能类似于Lua中的".."操作符,用于连接(并弹出)栈顶的n个值,然后压入连接后的结果。其原型为:
void lua_concat(lua_State *L, int n);
参数n表示栈中待连接的字符串数量。该函数会调用元方法。然而需要说明的是,如果连接的字符串数量较少,该函数可以很好的工作,反之,则会带来性能问题。为此,Lua API提供了另外一组函数专门解决由此而带来的性能问题,见如下代码示例:
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 extern "C" int strUpperFunc(lua_State* L) 8 { 9 size_t len; 10 luaL_Buffer b; 11 //检查参数第一个参数是否为字符串,同时返回字符串的指针及长度。 12 const char* s = luaL_checklstring(L,1,&len); 13 //初始化Lua的内部Buffer。 14 luaL_buffinit(L,&b); 15 //将处理后的字符依次(luaL_addchar)追加到Lua的内部Buffer中。 16 for (int i = 0; i < len; ++i) 17 luaL_addchar(&b,toupper(s[i])); 18 //将该Buffer及其内容压入栈中。 19 luaL_pushresult(&b); 20 return 1; 21 }
使用缓冲机制的第一步是声明一个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索引值来访问注册表。
1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 void registryTestFunc(lua_State* L) 8 { 9 lua_pushstring(L,"Hello"); 10 lua_setfield(L,LUA_REGISTRYINDEX,"key1"); 11 lua_getfield(L,LUA_REGISTRYINDEX,"key1"); 12 printf("%s ",lua_tostring(L,-1)); 13 } 14 15 int main() 16 { 17 lua_State* L = luaL_newstate(); 18 registryTestFunc(L); 19 lua_close(L); 20 return 0; 21 }
2). 环境:
如果需要保存一个模块的私有数据,即模块内各函数需要共享的数据,应该使用环境。我们可以通过LUA_ENVIRONINDEX索引值来访问环境。
1 #include <lua.hpp> 2 #include <lauxlib.h> 3 #include <lualib.h> 4 5 //模块内设置环境数据的函数 6 extern "C" int setValue(lua_State* L) 7 { 8 lua_pushstring(L,"Hello"); 9 lua_setfield(L,LUA_ENVIRONINDEX,"key1"); 10 return 0; 11 } 12 13 //模块内获取环境数据的函数 14 extern "C" int getValue(lua_State* L) 15 { 16 lua_getfield(L,LUA_ENVIRONINDEX,"key1"); 17 printf("%s ",lua_tostring(L,-1)); 18 return 0; 19 } 20 21 static luaL_Reg myfuncs[] = { 22 {"setValue", setValue}, 23 {"getValue", getValue}, 24 {NULL, NULL} 25 }; 26 27 28 extern "C" __declspec(dllexport) 29 int luaopen_testenv(lua_State* L) 30 { 31 lua_newtable(L); //创建一个新的表用于环境 32 lua_replace(L,LUA_ENVIRONINDEX); //将刚刚创建并压入栈的新表替换为当前模块的环境表。 33 luaL_register(L,"testenv",myfuncs); 34 return 1; 35 }
Lua测试代码如下。
1 require "testenv" 2 3 print(testenv.setValue()) 4 print(testenv.getValue()) 5 --输出为:Hello
3). upvalue:
upvalue是和特定函数关联的,我们可以将其简单的理解为函数内的静态变量。
1 #include <lua.hpp> 2 #include <lauxlib.h> 3 #include <lualib.h> 4 5 extern "C" int counter(lua_State* L) 6 { 7 //获取第一个upvalue的值。 8 int val = lua_tointeger(L,lua_upvalueindex(1)); 9 //将得到的结果压入栈中。 10 lua_pushinteger(L,++val); 11 //赋值一份栈顶的数据,以便于后面的替换操作。 12 lua_pushvalue(L,-1); 13 //该函数将栈顶的数据替换到upvalue(1)中的值。同时将栈顶数据弹出。 14 lua_replace(L,lua_upvalueindex(1)); 15 //lua_pushinteger(L,++value)中压入的数据仍然保留在栈中并返回给Lua。 16 return 1; 17 } 18 19 extern "C" int newCounter(lua_State* L) 20 { 21 //压入一个upvalue的初始值0,该函数必须先于lua_pushcclosure之前调用。 22 lua_pushinteger(L,0); 23 //压入闭包函数,参数1表示该闭包函数的upvalue数量。该函数返回值,闭包函数始终位于栈顶。 24 lua_pushcclosure(L,counter,1); 25 return 1; 26 } 27 28 static luaL_Reg myfuncs[] = { 29 {"counter", counter}, 30 {"newCounter", newCounter}, 31 {NULL, NULL} 32 }; 33 34 35 extern "C" __declspec(dllexport) 36 int luaopen_testupvalue(lua_State* L) 37 { 38 luaL_register(L,"testupvalue",myfuncs); 39 return 1; 40 }
Lua测试代码如下。
1 require "testupvalue" 2 3 func = testupvalue.newCounter(); 4 print(func()); 5 print(func()); 6 print(func()); 7 8 func = testupvalue.newCounter(); 9 print(func()); 10 print(func()); 11 print(func()); 12 13 --[[ 输出结果为: 14 1 15 2 16 3 17 1 18 2 19 3 20 --]]
镜像下载python包
Jupyter 快捷键汇总
牛顿法(Newton's method)VS梯度下降法(Gradient Descent)
L-BFGS算法详解(逻辑回归的默认优化算法)
np.bincount()频率统计函数
异常值检验实战3_NBA球员表现稳定性分析
异常值检测方法(Z-score,DBSCAN,孤立森林)
浅谈压缩感知(十六):感知矩阵之RIP
浅谈压缩感知(十五):感知矩阵之spark常数
- 最新文章
-
Permutation Sequence leetcode java
Text Justification leetcode java
Edit Distance leetcode java
Scramble String leetcode java
Decode Ways leetcode java
Interleaving String leetcode java
Distinct Subsequences leetcode java
Max Points on a Line leetcode java
Valid Number leetcode java
Wildcard Matching leetcode java
- 热门文章
-
RouterOS 之带宽管理及 QOS --HTB (1 1 )
RouterOS S 之带宽管理 及 QOS- 简单队列Simple queue
ros脚本,取整数或者字符串的最后一位
公司的一个ros需求创建10个ssid,走10根pptp线路,同时更换mac
今天阿里云1C0.5G装zabbix又装mysql发现内存不够了,增加swap虚拟内存
中行企业版U盾以及打款的实施步骤
ROS的Fasttrack,可以极大的减少ROS的CPU使用率已经带宽!特别注意:如果开启了 fasttrack,simple queue 将失效。
访问ros的5000端口,就在ip firewall address-list增加一个IP,实现重新拨号切换IP
RouterOS利用aliyun的API接口实现DDNS动态解析
ROS SCript课程系列4