18. 数学库
数学库由算术函数的标准集合组成:三角函数(sin,cos,tan,asin,acos,etc),幂指函数(exp,log, log10),舍入函数(floor,ceil),max,min加上常量pi。
数学库也定义了一个幂操作符(^)
所有的三角函数都在弧度单位下工作,可以使用deg和rad函数在度和弧度之间转换。如果想在degree下使用三角函数,可以重新定义三角函数。
localsin, asin, … = math.sin, math.asin, …
localdeg, rad = math.deg, math.rad
math.sin= function (x) return sin(rad(x)) end
math.asin= function (x) return deg(asin(x)) end
math.random用来产生伪随机数:
不带参数,将产生[0,1)范围内的随机数
带一个参数n,将产生 1<= x <= n范围内的随机数x
带两个参数a和b,将产生 a <= x<=b范围内的随机函数x。
使用randomseed设置随机数发生器的种子,只接受一个数字参数。可以使用当前时间作为随机种子,那么产生的序列将会不一样。math.randomseed( os.time());
19. table库
table库由一些操作table的辅助函数组成,主要作用之一是对Lua的array的大小给出一个合理的解释。
数组大小:Lua中假定array在最后一个非nil元素处结束。那么我们不能拥有nil元素。当需要array有nil元素时,需要一种方法来明确的表明array的大小
Table库定义了两个函数操纵array的大小:getn,返回array的大小。setn,设置array的大小。
默认的,setn和getn使用内部表存储表的大小。
remove和insert
table库提供一个从list的任意位置插入和删除元素的寒素。table.insert在array指定位置插入一个元素,并将后面所有的其他的元素后移。insert改变array的大小。
a是一个数组{10, 20, 30},调用table.insert(a, 1, 15)后,a变为了{ 15, 10, 20, 30}。
不带位置参数调用insert,将会在array最后位置插入元素。
table.remove函数删除数组中指定位置的元素,并返回这个元素。后面的元素前移,并改变数组大小。
使用这两个函数,很容易实现栈,队列,和双端队列。
排序:
排序函数 table.sort,两个元素:存放元素的array和排序函数。不提供排序函数,则默认使用小于操作符进行比较
常见的错误是企图对下标域进行排序。在一个表中,所有下标组成一个集合,但是无序。如果想对它进行排序,必须将他们复制到一个array,然后对这个array进行排序。
例如如下的例子,想对函数名进行排序:
lines = { luaH_set = 10, luaH_get = 24, luaH_present = 48, }; a = {} for n in pairs(lines) do table.insert(a, n) end table.sort( a); for i, n in ipairs(a) do print(n) end 输入结果: luaH_get luaH_present luaH_set
对于Lua来说,数组也是无序的,但是我们知道如何进行计数,只要使用排序号的下标访问数组,就可以得到排序号的函数名。ipairs使用key的顺序1,2,…,后者表的自然存储顺序。
20. String库
Lua解释器对字符串的支持有限,一个程序可以穿件字符串并连接字符串,但不能截取子串,检查字符串的大小,检查字符串的内容。在Lua中操纵字符串的功能基本来自于string库。
String库中一些函数非常简单:
string.len(s)返回字符串s的长度,string.rep(s, n)返回重复n次字符串s的串;string.lower(s)将s中的大写字母转换为小写(string.upper将小写转换成大写)。
string.sub(s,i, j)函数截取字符串s的从第i个字符到第j个字符之间的串。字符串第一个字符索引从1开始。本函数并不会改变字符串的值,致死将变量赋给一个新的字符串。
string.char函数和string.byte函数用来将字符在字符和数字之间转换。string.char获取0个或多个整数,将每一个整数转换成字符,然后返回所有这些字符链接起来的字符串。string.byte(s, i)将字符串s的第i个字符转换成整数。i默认值为1.
print(string.char(97));
i = 99; print(string.char(i ,i+1, i+2));
print(string.byte("abc"))
print(string.byte("abc", 2))
print(string.byte("abc", -1))
运行结果:
a
cde
97
98
99
使用负数索引访问字符串的最后一个字符
函数string.format在用来对字符串格式化的时候,功能很强大。使用和C语言的printf函数几乎一模一样,完全可以参考C语言的printf来使用这个函数
模式匹配函数:
string.find字符串查找,string.gsub全局字符串, string.gfind全局字符串查找
模式:
捕获:
转换的技巧:
21. IO库
I/O库为文件操作提供了两种模式:简单模式拥有一个当前输入文件盒一个当前输出文件,提供针对这些文件相关的操作。
完全模式使用外部的文件句柄来实现,以一种面向对象的形式,将所有的文件操作定义为文件句柄的方法。
I/O库的所有函数都放在了表io中。
简单I/O模式:
简单模式的所有操作都在两个当前文件之上,I/O库将当前输入文件作为标准输入(stdin),当前输出文件作为标准输出(stdout),执行io.read就在标准输入中读入一行。可以使用io.input和io.output函数来改变当前文件。
io.input(filename)就是打开给定文件(以读模式),并将其设置为当前输入文件。接下来的所有输入都来自于该文件,直到再次调用io.read()。io.output函数与input类似,一旦产生错误,两个函数都会产生错误。
如果想直接控制错误,必须使用完全模式中io.read()函数。
io.write("sin(3)= ", math.sin(3), "\n");
io.write(string.format("sin(3) = %.4f\n",math.sin(3)));
输出结果:
sin(3) = 0.14112000805987
sin(3) = 0.1411
编写代码的时候应尽量避免io.write(a .. b .. c);这样的写法,比较耗资源。
read函数从当前输入文件读取串,由它的参数控制读取的内容:
"*all" 读取整个文件 "*line" 读取下一行
"*number" 从串中转换出一个数值 num 读取num个字符到串中
io.read("*all")函数从当前位置读取整个输入文件,如果当前位置在文件末尾,或者文件为空,函数将返回空串。
io.read("*line");函数返回当前输入文件的下一行,到达文件末尾,返回值为nil
local count = 1;
while true do
local line = io.read()
if line == nil thenbreak end
io.write(string.format("%6d ", count), line, "\n");
count =count + 1;
end
为了在整个文件中逐行迭代,最好使用io.lines迭代器:
local lines = {};
-- read the line in table 'lines'
for line in io.lines() do
table.insert( lines,line);
end
--sort
table.sort(lines);
-- write all the lines
for i, l in ipairs(lines) do io.write(l, "\n") end
完全I/O模式:
为了输入输出的更全面的控制,可以使用完全模式。完全漠视的核心在于文件句柄,类似C的文件流FILE*,呈现一个打开文件以及当前存取位置。
打开文件的函数时io.open,模仿C的fopen,模式可以使”r”读模式,“w”写模式,对数据进行覆盖,或者”a”附加模式,“b”可以附加在后面,表示以二进制形式打开文件。
经典的方式: local f =assert(io.open(filename, mode));
文件打开以后使用read和write方法进行读写操作。
读取文件:
localf = assert( io.open(filename, “r”));
localt = f:read(“*all”);
f:close();
I/O库提供了三种预定义的句柄:io.stdin,io.stdout,io.stderr。使用如下的代码直接发送信息到错误流:
io.stderr:write(message);
I/O优化的小技巧:
Lua中读取整个文件要比一行一行的读取一个文件快得多,如果是比较大的文件,几十,几百兆,要处理这样的文件可以一段一段地读取,为了避免切割文件中的行,还要每段后加上一行:
locallines, rest = f:read(BUFSIZE, “*line”);
这样确保每一个段都是以一个完整的行结尾。
local BUFSIZE = 2^13;
local f = io.input(arg[1]);
local cc, lc, wc = 0, 0, 0;
while true do
local lines, rest =f:read(BUFSIZE, "*line");
if not lines then breakend
if rest then lines =lines .. rest .. '\n' end
cc = cc +string.len(lines);
local _, t =string.gsub(lines, "%S+", "");
wc = wc + t;
_, t =string.gsub(lines, "\n", "\n");
lc = lc + t
end
print( lc, wc, cc);
二进制文件:
关于文件的其他操作
io.flush()或 f:flush() ,filehandle:seek(wherece,offset); file:seek(“end”);
22. 操作系统库
操作系统库包含了文件管理,系统时钟等与操作系统相关信息
Date和Time:time函数在没有参数时返回当前时钟的数值。
os.clock返回执行改程序CPU花去的时钟秒数
localx = os.clock()
locals = 0;
fori = 1, 100000 do s = s + i end
print(string.format(“elapsed time: %.2f\n”,os.clock()-x));
其他的系统调用:
os.exit()终止一个程序的执行。
os.getenv()获得环境变量的值,以变量名作为参数
os.execute执行一个系统命令,os.execute( “mkdir” ..dirname);
23. Debug库
debug库并不给一个可用的Lua调试器,而是提供一些为Lua写一个调试器的方便。这方面的接口是通过C API实现的。Debug库是在一个debug表中生命了所有的函数。
debug库中的一些函数性能比较低。
debug库分为两种函数,自省(introspective)函数和hooks。自省函数使我么你可以检查运行程序的某些方面,活动函数栈,当前执行代码的行号,本地变量的名和值。Hooks可以跟踪程序的执行情况。
自省:
Hooks;
Profile;
24. C API纵览
C语言与Lua有很强的关联性,C和Lua中间有两种交互方式:第一是C作为应用程序语言,Lua作为一个库使用。第二种Lua作为程序语言,C作为库使用。这两种方式,C都是用相同的API和Lua通信,C和Lua的交互部分称为C API。
CAPI是一个C代码的域Lua进行交互的函数集,由两部分组成:读写Lua库变量函数,调用Lua函数的函数,运行Lua代码片段的函数,注册C函数然后再Lua中被调用的函数等。
API中的大部分函数不检查他们参数的正确性:你需要在调用函数之前负责确保参数是有效的。API重点放在了灵活性和简洁性方面,以牺牲很多为代价。
C和Lua之间通信关键内容在于一个虚拟的栈,几乎所有的API调用都是对栈上值进行操作,C与Lua之间的数据交换也通过这个栈完成。栈的使用解决了C和Lua之间不协调的问题:第一,Lua会自动进行垃圾收集,而C要求显示的分配存储单元,两者引起的魔都。第二,Lua的动态类型和C中的静态类型不一致引起的混乱。
Lua库没有定义任何全局变量,所有的状态保存在动态结构lua_State中,而且指向这个结构的指针作为所有Lua函数的一个参数。
在VS中默认的是将代码以C++的方式进行编译,因此在编译时记得加上extern C的处理,否则链接库错误。
#ifdef __cplusplus extern "C"{ #endif #include <stdio.h> #include <string.h> #include <stdlib.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" #pragma comment( lib, ".\\lua51.lib") int main() { char buff[256]; int error; lua_State *L = lua_open(); luaL_openlibs(L); //luaopen_base(L); // open the basic library //luaopen_table(L); // open the table library //luaopen_io(L); // opens the I/O library //luaopen_string(L); // open the string lib //luaopen_math(L); // opens the math lib while ( fgets( buff, sizeof(buff), stdin) != NULL) { error = luaL_loadbuffer(L, buff, strlen(buff), "line") || lua_pcall(L, 0, 0, 0); if ( error) { fprintf(stderr, "%s", lua_tostring(L, -1)); lua_pop(L, 1); // pop error message from the stack } } lua_close( L); return 0; } #ifdef __cplusplus } #endif
当时如果在C++中,需要使用extern "C"将lua.h做处理
extern "C"{
#include<lua.h>
}
堆栈:
Lua和C之间交换数据面临两个问题:动态和静态类型系统的不匹配,自动与手动内存管理的不一致
对于Lua中类似 a[k] = v中,k和v有几种不同的类型,想在C中提供类似的操作,无法实现,操作表的函数必定有一个固定类型。C语言中生命了一些union类型来解决这个问题,称为lua_Value,能够描述所有类型的Lua值。可以按照如下方式声明settable
voidlua_settable( lua_Value a, lua_Value k, lua_Value v);
但是如此复杂的类型映射到其他的语言很困难。Lua负责垃圾回收,如果将Lua值保存在C变量中,Lua引擎没有办法了解这种用法,可能错误地认为某个值为垃圾并收集他。
LuaAPI没有定义任何类似lua_Value的类型,而是用一个抽象的栈在Lua域C之间交换数值。栈中每一条记录都可以保存任何Lua值,无论何时从Lua请求一个值,调用Lua,被请求的值都将会被压入栈。想要传递一个值给Lua,首先将这个值压入栈,然后调用Lua函数。那么需要一个不同的函数将每种C类型压入栈,和一个不同函数从栈上取值(只是取出不是弹出)。
由于栈是由Lua管理,垃圾回收器知道那个值被C使用,几乎所有的API函数都要用到栈。luaL_loadbuffer把结果留在栈上,lua_pcall从栈上获取要被调用的函数并把任何临时的错误信息放在这里。
Lua以一个严格的LIFO规则操作栈,当调用Lua时,只会改变栈顶部分。C代码有更多的自由:可以查询栈上的任何元素,甚至是在任何一个位置插入和删除元素。
压入元素:API有一系列压栈的函数:
空值nil,lua_pushnil(),数值型(double)用lua_pushnumber(),布尔型用lua_pushboolean(),任意的字符串,用lua_pushlstring():
void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, doublen);
void lua_pushlstring (lua_State *L, const char*s, size_t length);
void lua_pushstring (lua_State *L, const char*s);
无论何时压入一个元素到栈上,有责任确保在栈上有空间来做这件事情。Lua在起始以及Lua调用C的时候,栈上至少有20个空闲记录。调用如下方法检测:
intlua_checkstack( lua_State *L, int sz);
API用索引来访问栈中的元素,栈中的第一个元素(第一个被压入栈的)有索引1,下一个有索引2,一次类推。
-1指栈顶元素,也即最后被压入的,-2指出它的前一个元素。
lua_tostring(L,-1); 以字符串的形式返回栈顶的值。提供了一套lua_is*函数来检查一个元素是否是一个指定的类型。*可以是任何Lua类型。lua_isnumber,lua_isstring,lua_istable。
lua_type函数返回栈中元素的类型。常用的有LUA_TNIL,LUA_TBOOLEAN,LUA_TNUMBER等
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);
tostring返回的是一个指向字符串的内部拷贝的指针,不能修改它。
其他的栈操作:
int lua_gettop (lua_State *L);
voidlua_settop (lua_State *L, intindex);
voidlua_pushvalue (lua_State *L, intindex);
voidlua_remove (lua_State *L, intindex);
voidlua_insert (lua_State *L, intindex);
voidlua_replace (lua_State *L, intindex);
CAPI的错误处理:
与C++和java不一样,C语言没有异常处理机制,Lua利用C的setjmp技巧构造了一个类似异常处理的机制。
应用程序中的错误处理:
应用程序出错时,只能调用一个panic函数退出应用。可以使用lua_atpanic函数设置你自己的panic函数。
如果不想退出应用,必须在保护模式下运行代码,大部分或者所有得代码通过嗲用lua_pcall来运行。如果想保护所有与Lua交互的C代码,可以使用lua_cpcall。
类库中的错误处理:
Lua是安全的语言,无论你写什么样的代码,也不管代码如何错误,可以根据Lua本身知道程序的行为。因此必须设法保证添加的插件对于Lua来讲是安全的,且提高比较好的错误处理。
C程序自己的错误处理方式可以参考。C函数发现错误只需要简单调用lua_error,或者luaL_error。lua_error函数会清理所有在Lua中需要被清理,然后和错误信息一起回到最初执行lua_pcal的地方
25. 扩展你的程序:
作为配置语言是Lua的一个重要应用,C程序有一个窗口界面,可以让用户指定窗口的初始化大小,可以采用Lua配置文件,这种简单的文本信息如下:
--configuration file for program
--define window size
width= 200
height= 300
调用Lua 的API函数去解析这个函数:
#ifdef __cplusplus extern "C"{ #endif #include <stdio.h> #include <string.h> #include <stdlib.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" #pragma comment( lib, ".\\lua51.lib") void error( lua_State *L, const char * fmt, ...) { va_list argp; va_start(argp, fmt); vfprintf( stderr, argp, NULL); va_end(argp); lua_close( L); exit( EXIT_FAILURE); } void load( char * filename, int * width, int * height) { lua_State *L = lua_open(); luaL_openlibs(L); if(luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0)) error(L, "cannot run configuration file: %s", lua_tostring(L,-1)); lua_getglobal(L, "width"); lua_getglobal(L, "height"); if( !lua_isnumber(L,-2)) error( L, "'width' should be a number!\n"); if( !lua_isnumber(L,-1)) error( L, "'height' should be a number!\n"); *width = (int) lua_tonumber(L, -2); *height = (int) lua_tonumber(L, -1); lua_close( L); } int main() { char buff[256]; int error; int h, w; load(".\\config.lua",&h, &w); printf("Height:%d, Width: %d", h, w); system("pause"); return 0; } #ifdef __cplusplus } #endif 表操作: 假如一个表示颜色的表: background= {r = 0.30, g = 0.10, b = 0}; 在C获取这些值: lua_getglobal(L, “background”); if( ! lua_istable( L, -1)) error(L, “’background’ is not a valid color table” ); red= getfield(“r”); green= getfield(“g”); blue= getfield(“b”); #define MAX_COLOR 255 /* assume that table is on the stack top*/ int getfield( constchar * key) { int result; lua_pushstring( L, key); lua_gettable( L,-2); if(!lua_isnumber( L, -1)) error( L,"invalid component in background color!"); result = (int) lua_tonumber( L,-1) * MAX_COLOR; lua_pop(L, 1); return result; } void setfield( constchar * index, int value) { lua_pushstring(L, index); lua_pushnumber( L,(double)value/ MAX_COLOR); lua_settable( L,-3); }
获取和设置栈上表的元素
调用Lua函数:
Lua作为配置文件的一个最大长处是它可以定义个被应用调用的函数。
使用API调用函数的方法很简单:首先被调用的函数入栈,第二依次将所有参数入栈,第三,使用lua_pcall调用函数,最后从栈中获取函数执行返回的结果
functionf ( x, y)
return(x^2 * math.sin(y)) / (1-x);
end
想在C中对于给定的x,y计算z=f( x, y)的值,加入已经打开了lua库,并运行了配置文件。可以将这个调用封装成下面的C函数。
double f( lua_State *L, double x, double y) { double z; /* push functions and arguments */ lua_getglobal(L,"f"); //function to be called lua_pushnumber( L, x); // push 1st argument lua_pushnumber( L, y); // push 2st argument /* do the call (2 arguments, 1 result) */ if( lua_pcall( L, 2, 1, 0) != 0) error( L,"error running function 'f': %s", lua_tostring(L, -1)); /* retrieve result */ if (!lua_isnumber( L, -1)) error(L,"function 'f' must return a number!"); z = lua_tonumber(L, -1); lua_pop( L, 1); return z; }
26. 调用C函数
提到的Lua可以调用C函数,不是指Lua可以调用任何类型的C函数。C调用Lua函数的时候,要遵循一些简单的协议来传递参数和获取返回结果。从Lua中调用C函数,也要遵循协议来传递参数和获取返回结果。
一个重要概念:用来交互的栈不是一个全局变量,每一个函数都有他自己的私有栈。当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置。甚至当一个C函数调用Lua代码,每一个C函数都有自己的独立的私有栈,并且第一个参数在index=1的位置。
从Lua中调用C函数,必须注册函数,必须把C函数的地址以一个适当的方式传递给Lua解释器。
Lua调用C函数,使用和C调用Lua相同类型的栈来交互。C函数从栈中获取参数,调用结束,将返回结果放到栈中。
实现一个简单函数返回给定数值的sin值:
static int l_sin(lua_State *L)
{
double d = lua_tonumber( L,1);
lua_pushnumber(L, sin(d));
return 1;
}
任何在Lua中注册的函数有同样的原型,这个原型定义在lua.h中的lua_CFunction:
typedefint (*lua_CFunction) (lua_State * L);
接收单一的参数Lua state,返回一个表示返回值个数的数字。
要在Lua中使用这个函数,必须注册这个函数,使用lua_pushcfuncton来实现:获取指向C函数的指针,并在Lua中创建一个function类型的值来表示这个函数。
lua_pushcfunction(L,l_sin);
lua_setglobal( L, “mysin”)
第一行将类型为function的值入栈,第二行将function赋值给全局变量mysin,重新编译Lua,就可以在Lua程序中使用新的mysin函数了。
C函数调用Lua函数,Lua函数调用C函数的完整例子:
#ifdef __cplusplus extern"C"{ #endif #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include"lua.h" #include"lauxlib.h" #include"lualib.h" #pragma comment( lib,".\\lua51.lib") // lua调用过程中的错误处理函数 void error( lua_State *L, const char * fmt, ...) { va_list argp; va_start(argp, fmt); vfprintf( stderr, argp, NULL); va_end(argp); lua_close( L); exit( EXIT_FAILURE); } // 获取和设置 Lua中的表内容 #define MAX_COLOR 255 /* assume that table is on the stack top*/ int getfield( lua_State*L, const char * key) { int result; lua_pushstring(L, key); lua_gettable(L, -2); if(!lua_isnumber( L,-1)) error( L, "invalid component in backgroundcolor!"); result = (int) lua_tonumber( L, -1) * MAX_COLOR; lua_pop(L, 1); return result; } void setfield( lua_State*L, const char * index, int value) { lua_pushstring(L, index); lua_pushnumber(L, (double)value / MAX_COLOR); lua_settable(L, -3); } double C_f( lua_State *L, double x, double y) { double z; /* push functionsand arguments */ lua_getglobal(L, "f"); // function to be called lua_pushnumber(L, x); // push 1st argument lua_pushnumber(L, y); // push 2st argument /* do the call (2arguments, 1 result) */ if( lua_pcall( L, 2, 1,0) != 0) error( L, "error running function 'f': %s", lua_tostring(L,-1)); /* retrieve result*/ if (!lua_isnumber( L,-1)) error(L, "function 'f' must return a number!"); z = lua_tonumber(L,-1); lua_pop( L, 1); return z; } // 加载Lua文件 void load( char * filename, int * width, int * height) { lua_State *L = lua_open(); luaL_openlibs(L); if(luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0)) error(L, "cannot run configuration file: %s", lua_tostring(L,-1)); lua_getglobal(L, "width"); lua_getglobal(L, "height"); if( !lua_isnumber(L,-2)) error( L,"'width' should be a number!\n"); if( !lua_isnumber(L,-1)) error( L, "'height' should be a number!\n"); *width = (int) lua_tonumber(L, -2); *height = (int) lua_tonumber(L, -1); lua_close( L); } static int l_sin( lua_State *L) { double d = lua_tonumber( L, 1); double num = sin(d); lua_pushnumber(L, num); return 1; } int main() { lua_State *L; // 加载Lua脚本,读取文件内容 int h, w; load(".\\config.lua",&h, &w); printf("Height:%d, Width: %d\n", h, w); L = lua_open(); luaL_openlibs(L); if(luaL_loadfile(L,".\\config.lua") || lua_pcall(L, 0, 0, 0)) error(L, "cannot run configuration file: %s", lua_tostring(L,-1)); /* 在C中调用Lua的函数 */ printf("CallLua function: %lf\n", C_f( L, 2, 1)); /* 在Lua中调用C的函数 */ lua_pushcfunction(L, l_sin); lua_setglobal(L, "mysin"); lua_getglobal(L, "g"); // 调用Lua的函数,确定它可以调用我们的C函数 if( lua_pcall( L, 0, 0,0) != 0) error( L, "error running function 'f': %s", lua_tostring(L,-1)); lua_close( L); system("pause"); return 0; } #ifdef __cplusplus } #endif
C函数库:
一个Lua库实际上是定义了一系列Lua函数的chunk,并将这些函数保存在适当的地方,通常用table的域来保存。
Lua的C库就是遮掩实现的。除了定义C函数之外,还必须定义一个特殊的用来和Lua库的主chunk通信的特殊函数。调用之后,这个函数会注册库中所有的C函数,将他们保存在适当的位置。
Lua通过注册过程,可以看到库中的C函数,这样在Lua程序中就可以直接引用它的地址来访问这个函数了。
当用C函数扩展Lua的时候,仅仅是注册一个C函数,将C代码设计为一个库也是个不错的思想。辅助库对这种实现提供了帮助,luaL_openlib函数接受一个C函数的列表和他们对应的函数名,作为一个库在一个table中注册所有这些函数。
第一,首先定义库函数,例如前面的l_dir函数
staticint l_dir( lua_State * L)
{
…// as before 获取函数参数,并调用函数处理,通过栈将结果返回
}
第二,声明一个数组,保存所有的函数很他们对应的名字,这个数组的元素类型为luaL_reg:带有两个域的结构体,一个字符串和一个函数指针。
staticconst struct luaL_reg mylib[] = {
{“dir”, l_dir}
{NULL, NULL}
}
注意数组最后必须用一对 { NULL, NULL}结束。
第三,使用luaL_openlie声明主函数:
intluaopen_mylib ( lua_State *L)
{
luaL_openlib(L, “mylib”, mylib, 0);
return1;
}
luaL_openlib的第二个参数是库的名称,这个函数按照指定的名字创建一个表,并使用数组mylib中的name-function键值对填充这个表,luaL_openlib还允许我们为库所有的函数注册公共的upvalues。
完成编码之后,必须将它连接到Lua解释器,常用方法是使用动态链接库。必须用代码创建动态链接库,可以在Lua中直接使用loadlib加载刚才定义的函数库:
mylib = loadlib(“fullname-of-your-library”,“luaopen_mylib”);
27. 撰写C函数的技巧
数组操作:
Lua中的数组就是一个特殊的方式使用table的别名。可以使用任何操纵table的函数对数组操作,即lua_settable和lua_gettable。API为数组提供了一些特殊的函数,处于性能考虑,在算法的循环的内层访问数组,这种内层操作的性能的提高对整体性能改善有很大影响。
voidlua_rawgeti( lua_State *L, int index, int key);
voidlua_rawseti( lua_State *L, int index, int key);
第一个索引index指向table在栈中的位置,key指向元素在table中的位置
lua_rawgeti(L,t, key)等价于:
lua_pushnumber(L,key);
lua_rawget(L, t);
字符串处理:
C函数接受一个来自Lua的字符串作为参数,有两个规则:当字符串正在北访问的时候,不要将其出栈;永远不要修改字符串
C函数需要创建一个字符串,返回给lua,情况比较复杂。由C代码来负责缓冲区的分配和释放,负责处理缓冲溢出等情况。Lua API提供了一些函数来帮助处理这些问题。
标准API提供了两种基本字符串操作支持:子串截取和字符串连接。
lua_pushlstring可以接受一个额外的参数,字符串的长度来实现字符串的截取。如将字符串s从i到j位置传递给lua
lua_pushstring(L,s+I,j-i+1);
例子:写一个函数,根据指定的分隔符分隔一个字符串,返回保存所有子串的table:
split(“hi,, there” , “,”);
函数不需要额外的缓冲区,可以处理字符串的长度没有限制。
static int l_split()
{
const char * s = luaL_checkstring(L,1);
const char * sep = luaL_checkstring(L,2);
const char * e;
int i = 1;
lua_newtable(L);
while( (e = strchr(s, *sep)) != NULL)
{
lua_pushlstring(L, s, e-s);
lua_rawseti(L, -2, i++);
s = e + 1;
}
// push lastsubstring
lua_pushstring(L, s);
lua_rawseti(L, -2, i);
return 1;
}
lua API中专门用来连接字符串的函数lua_concat,等价于Lua的.. 操作符。
lua_concat(L,n);将连接(同时会出栈)栈顶的n个值。将结果放到栈顶。
lua_pushfstring(lua_State *L, const char* fmt, …)类似C语言的sprintf函数。
The Registry表:
如果将C函数的非局部变量保存为Lua的全局变量,Lua程序有可能会修改它,从而影响到C语言程序,因此Lua提供了一个独立的registry表,用于保存C函数的非局部的数据,这个表C代码可以自由使用,Lua代码不能访问。
获取键值”key”保存在registry中的值:
lua_pushstring(L, “Key”);
lua_gettable(L, LUA_REGISTRYINDEX);
registry就是普通的Lua表,然后由于所有的C库共享相同的registry,必须注意使用什么样的值作为key,否则导致命名冲突。
28. User-Defined Types in C