Lua教程(二十一):編寫C函數的技巧_Lua

來源:互聯網
上載者:User

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很少會用到元表。

    見如下程式碼範例和關鍵性注釋:

複製代碼 代碼如下:

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

extern "C" int mapFunc(lua_State* L)
{
    //檢查Lua調用代碼中傳遞的第一個參數必須是table。否則將引發錯誤。
    luaL_checktype(L,1,LUA_TTABLE);
    luaL_checktype(L,2,LUA_TFUNCTION);
    //擷取table中的欄位數量,即數組的元素數量。
    int n = lua_objlen(L,1);
    //Lua中的數組起始索引習慣為1,而不是C中的0。
    for (int i = 1; i <= n; ++i) {
        lua_pushvalue(L,2);  //將Lua參數中的function(第二個參數)的副本壓入棧中。
        lua_rawgeti(L,1,i);  //壓入table[i]
        lua_call(L,1,1);     //調用function(table[i]),並將函數結果壓入棧中。
        lua_rawseti(L,1,i);  //table[i] = 函數傳回值,同時將傳回值彈出棧。
    }

    //無結果返回給Lua代碼。
    return 0;
}

 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(表示字元)。除此之外,不支援任何例如寬度和精度的選項。

複製代碼 代碼如下:

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

extern "C" int splitFunc(lua_State* L)
{
    const char* s = luaL_checkstring(L,1);
    const char* sep = luaL_checkstring(L,2); //分隔字元
    const char* e;
    int i = 1;
    lua_newtable(L); //結果table
    while ((e = strchr(s,*sep)) != NULL) {
        lua_pushlstring(L,s,e - s);  //壓入子字串。
        //將剛剛壓入的子字串設定給table,同時賦值指定的索引值。
        lua_rawseti(L,-2,i++);      
        s = e + 1;
    }
    //壓入最後一個子串
    lua_pushstring(L,s);
    lua_rawseti(L,-2,i);
    return 1; //返回table。
}

 Lua API中提供了lua_concat函數,其功能類似於Lua中的".."操作符,用於串連(並彈出)棧頂的n個值,然後壓入串連後的結果。其原型為:
    void  lua_concat(lua_State *L, int n);
    參數n表示棧中待串連的字串數量。該函數會調用元方法。然而需要說明的是,如果串連的字串數量較少,該函數可以很好的工作,反之,則會帶來效能問題。為此,Lua API提供了另外一組函數專門解決由此而帶來的效能問題,見如下程式碼範例:

複製代碼 代碼如下:

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

extern "C" int strUpperFunc(lua_State* L)
{
    size_t len;
    luaL_Buffer b;
    //檢查參數第一個參數是否為字串,同時返回字串的指標及長度。
    const char* s = luaL_checklstring(L,1,&len);
    //初始化Lua的內部Buffer。
    luaL_buffinit(L,&b);
    //將處理後的字元依次(luaL_addchar)追加到Lua的內部Buffer中。
    for (int i = 0; i < len; ++i)
        luaL_addchar(&b,toupper(s[i]));
    //將該Buffer及其內容壓入棧中。
    luaL_pushresult(&b);
    return 1;
}

  使用緩衝機制的第一步是聲明一個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索引值來訪問註冊表。

 

複製代碼 代碼如下:

 #include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

void registryTestFunc(lua_State* L)
{
    lua_pushstring(L,"Hello");
    lua_setfield(L,LUA_REGISTRYINDEX,"key1");
    lua_getfield(L,LUA_REGISTRYINDEX,"key1");
    printf("%s\n",lua_tostring(L,-1));
}

int main()
{
    lua_State* L = luaL_newstate();
    registryTestFunc(L);
    lua_close(L);
    return 0;
}
 

 2). 環境:
    如果需要儲存一個模組的私人資料,即模組內各函數需要共用的資料,應該使用環境。我們可以通過LUA_ENVIRONINDEX索引值來訪問環境。
 

複製代碼 代碼如下:

 #include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

//模組內設定環境資料的函數
extern "C" int setValue(lua_State* L)
{
    lua_pushstring(L,"Hello");
    lua_setfield(L,LUA_ENVIRONINDEX,"key1");
    return 0;
}

//模組內擷取環境資料的函數
extern "C" int getValue(lua_State* L)
{
    lua_getfield(L,LUA_ENVIRONINDEX,"key1");
    printf("%s\n",lua_tostring(L,-1));
    return 0;
}

static luaL_Reg myfuncs[] = {
    {"setValue", setValue},
    {"getValue", getValue},
    {NULL, NULL}
};


extern "C" __declspec(dllexport)
int luaopen_testenv(lua_State* L)
{
    lua_newtable(L);  //建立一個新的表用於環境
    lua_replace(L,LUA_ENVIRONINDEX); //將剛剛建立並壓入棧的新表替換為當前模組的環境表。
    luaL_register(L,"testenv",myfuncs);
    return 1;
}
 

Lua測試代碼如下。

複製代碼 代碼如下:

 require "testenv"
 
 print(testenv.setValue())
 print(testenv.getValue())
 --輸出為:Hello

    3). upvalue:
    upvalue是和特定函數關聯的,我們可以將其簡單的理解為函數內的靜態變數。
複製代碼 代碼如下:

#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

extern "C" int counter(lua_State* L)
{
    //擷取第一個upvalue的值。
    int val = lua_tointeger(L,lua_upvalueindex(1));
    //將得到的結果壓入棧中。
    lua_pushinteger(L,++val);
    //賦值一份棧頂的資料,以便於後面的替換操作。
    lua_pushvalue(L,-1);
    //該函數將棧頂的資料替換到upvalue(1)中的值。同時將棧頂資料彈出。
    lua_replace(L,lua_upvalueindex(1));
    //lua_pushinteger(L,++value)中壓入的資料仍然保留在棧中並返回給Lua。
    return 1;
}

extern "C" int newCounter(lua_State* L)
{
    //壓入一個upvalue的初始值0,該函數必須先於lua_pushcclosure之前調用。
    lua_pushinteger(L,0);
    //壓入閉包函數,參數1表示該閉包函數的upvalue數量。該函數傳回值,閉包函數始終位於棧頂。
    lua_pushcclosure(L,counter,1);
    return 1;
}

static luaL_Reg myfuncs[] = {
    {"counter", counter},
    {"newCounter", newCounter},
    {NULL, NULL}
};


extern "C" __declspec(dllexport)
int luaopen_testupvalue(lua_State* L)
{
    luaL_register(L,"testupvalue",myfuncs);
    return 1;
}

    Lua測試代碼如下。

複製代碼 代碼如下:

require "testupvalue"

func = testupvalue.newCounter();
print(func());
print(func());
print(func());

func = testupvalue.newCounter();
print(func());
print(func());
print(func());

--[[ 輸出結果為:
1
2
3
1
2
3
--]]

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.