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\n",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\n",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 --]] 
 
 
 
 

Step By Step(编写C函数的技巧)的更多相关文章

  1. Lua 学习 chapter30 编写c函数的技巧 - Jow的博客

    目录 数组操作 字符串操作 在c函数中保存状态 生活总需要一点仪式感,然后慢慢的像那个趋向完美的自己靠近. 数组操作 Lua中的数组就是以特殊的方式使用边.像lua_setttable and lua ...

  2. MDX Step by Step 读书笔记(七) - Performing Aggregation 聚合函数之 Max, Min, Count , DistinctCount 以及其它 TopCount, Generate

    MDX 中最大值和最小值 MDX 中最大值和最小值函数的语法和之前看到的 Sum 以及 Aggregate 等聚合函数基本上是一样的: Max( {Set} [, Expression]) Min( ...

  3. Step By Step(Lua函数)

    Step By Step(Lua函数) 一.函数:    在Lua中函数的调用方式和C语言基本相同,如:print("Hello World")和a = add(x, y).唯一的 ...

  4. WPF Step By Step 系列 - 开篇 ·

    WPF Step By Step 系列 - 开篇 公司最近要去我去整理出一个完整的WPF培训的教程,我刚好将自己学习WPF的过程和经验总结整理成笔记的方式来讲述,这里就不按照书上面的东西来说了,书本上 ...

  5. 精通initramfs构建step by step

    (一)hello world  一.initramfs是什么  在2.6版本的linux内核中,都包含一个压缩过的cpio格式 的打包文件.当内核启动时,会从这个打包文件中导出文件到内核的rootfs ...

  6. Metrics.NET step by step使用Metrics监控应用程序的性能

    使用Metrics监控应用程序的性能 在编写应用程序的时候,通常会记录日志以便事后分析,在很多情况下是产生了问题之后,再去查看日志,是一种事后的静态分析.在很多时候,我们可能需要了解整个系统在当前,或 ...

  7. Neural Networks and Deep Learning(week4)Building your Deep Neural Network: Step by Step

    Building your Deep Neural Network: Step by Step 你将使用下面函数来构建一个深层神经网络来实现图像分类. 使用像relu这的非线性单元来改进你的模型 构建 ...

  8. 课程一(Neural Networks and Deep Learning),第四周(Deep Neural Networks)——2.Programming Assignments: Building your Deep Neural Network: Step by Step

    Building your Deep Neural Network: Step by Step Welcome to your third programming exercise of the de ...

  9. pycharm 的 使用 设置智能目录 Pycharm 断点跳转及 Step Over/Step Into/Step Out 等的区别

    pycharm  右键点击文件夹  有个mark directiory as  根据需要给目录进行设置 Pycharm调试程序时,有时需要直接从第一个断点跳转至第二个断点,如果还是用单步调试的话就非常 ...

随机推荐

  1. Java程序设计基础第4章习题与自总

    怎么说呢?不论什么编程题,都有很多种方法解决问题,最后能解决我们所提出的问题才是关键的东西,也是未来成为工程师所需要的能力.解决问题是关键, 当我们做好了问题解决的关键途径的时候,如果有着profou ...

  2. C语言-内存函数的实现(二)之memmove

    C语言中的内存函数有如下这些 memcpy memmove memcmp memset 下面看看memmove函数 memmove 为什么会需要memmove函数? int main() { int ...

  3. 锁定项目的 node 版本

    一些老项目对 node 版本是有要求的,往往使用默认的新版本包安装不上,scripts 也跑不起来. 之前就遇到过运行一个小程序项目时,根据文档来,第一步安装就出错.本着办法总比问题多的理念,来一个解 ...

  4. Python中的时间日期模块(time、datetime)

    目录 Datetime 获取当前时间 获取当前日期 获取当前时间的tuple元组 格式化日期和时间 时间移动 获取两个时间的时间差 时间格式转换 Time 获取距元年(1970.1.1)的秒数 当时时 ...

  5. 针对缓冲区保护技术(ASLR)的一次初探

    0x01 前言 ASLR 是一种针对缓冲区溢出的安全保护技术,通过对堆.栈.共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一 ...

  6. (3) arm 指令

    跳转指令1.1 B 跳转指令 B{cond} label 说明:如果条件cond满足,arm处理器将立即跳转到label指定的地址处继续执行.1.2 BL 带链接的跳转指令 BL{cond} labe ...

  7. Day004 Scanner对象

    Scanner对象 作用 ​ 我们可以通过Scanner类来获取用户的输入 所属包 ​ java.util.Scanner java 工具类下 基本语法 Scannner s=new Scanner( ...

  8. JDBC相关配置和操作

    获取数据库连接的几种方式 ps.数据库URL : String url = "jdbc:mysql://localhost:3306/dailytext?useSSL=false&s ...

  9. 查询某软件所连接的外网IP地址

    一:背景环境: 1>:某机械公司用的某些特殊软件,需要实现所有使用某软件的屏蔽其软件所连接的外网ip,其他上网功能不做限制. 二:需求分析:可以查出此软件所连接的外网ip,在路由器的ip过滤中将 ...

  10. [MySQL数据库之数据库相关概念、MySQL下载安装、MySQL软件基本管理、SQL语句]

    [MySQL数据库之数据库相关概念.MySQL下载安装.MySQL软件基本管理.SQL语句] 数据库相关概念 数据库管理软件的由来 数据库管理软件:本质就是个C/S架构的套接字程序. 我们在编写任何程 ...