《Lua程序设计》笔记之三——标准库

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

      

 

你可能感兴趣的:(《Lua程序设计》笔记之三——标准库)