好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

编写C函数的技巧

编写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   --  ]]  

 

分类:  Lua编程

 

 

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于编写C函数的技巧的详细内容...

  阅读:54次