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. xlrd、xlwt 库

    1. 安装与介绍 2. xlrd 3. xlwt 1. 安装与介绍 xlrd 模块实现对excel文件内容读取,xlwt 模块实现对excel文件的写入. 模块安装: pip install xlrd ...

  2. 一文读懂eBPF/XDP

    XDP概述 XDP是Linux网络路径上内核集成的数据包处理器,具有安全.可编程.高性能的特点.当网卡驱动程序收到数据包时,该处理器执行BPF程序.XDP可以在数据包进入协议栈之前就进行处理,因此具有 ...

  3. ZOJ3715 竞选班长求最小花费

    题意:       有n个小朋友竞选班长,一号想当班长,每个人都必须选择一个人当班长,并且不可以选择自己,并且每个人都有一个权值ai,这个权值就是如果1想让这个人改变主意选择自己当班长就得给他ai个糖 ...

  4. Windows中动态磁盘管理

    目录 动态磁盘 基本磁盘和动态磁盘的转换 简单卷 跨区卷 带区卷 镜像卷 RAID-5卷 相关文章:硬盘分区形式(MBR.GPT).系统引导.文件系统.Inode和Block 动态磁盘 Windows ...

  5. 手撸了一个starter,同事直夸我666~

    Spring Boot starter原理 Spring Boot 将常见的开发功能,分成了一个个的starter,这样我们开发功能的时候只需要引入对应的starter,而不需要去引入一堆依赖了!st ...

  6. 使用docker-compose.yml快速搭建Linux/Mac开发/生产环境

    传送门 点击进入Github,https://github.com/ovim/dockerfiles 简单介绍 Dockerfiles 搭建基础公用环境包,方便使用,支持业务不断的提升而需要一些软件的 ...

  7. springboot添加操作

    更多精彩关注微信公众号 Mybaits技术连接数据库 resources #update tomcat port server.port=8888 #config datasource(mysql) ...

  8. 国家密码标准-商密SM2官方文档整理

    SM2官方文档整理 算法原理 SM2算法介绍 我国自主知识产权的商业密码算法,是ECC(椭圆加密算法)的一种,基于椭圆曲线离散对数问题(公钥密码体制所依据的难题主要为大素数分解问题.离散对数问题.椭圆 ...

  9. JAVA中Abstract到底有什么用?都用在哪些方面比较合适?

    功能向上聚合 Abstract作为抽象类和抽象方法,第一种情况是在聚合子类的通用性上起到作用,往往出现在重构过程中自然而然形成的一种层次结构-希望将多个子类的通用方法和逻辑提取到父层的抽象类. 这种重 ...

  10. BUAA-OO-第三单元总结

    面向对象第三单元JML总结 JML理论基础及工具链梳理 JML语言理论基础 JML语言是对于JAVA进行规格化设计的一种表述语言,他能以一种统一化语言,逻辑性强的格式,向程序设计者描述这一方法实现的功 ...