1. 程式人生 > >C開發lua模組(二) --- 操作table和呼叫lua函式

C開發lua模組(二) --- 操作table和呼叫lua函式

C語言沒有類似 lua_pushtable 和 lua_totable 的方法,這也很容易理解,因為C語言中沒有一種資料型別可以和lua中的table相對應的,那C函式怎麼操作table型別的引數或者返回table型別的結果呢,lua API中提供了一系列的操作table的函式。

void lua_createtable (lua_State *L, int narr, int nrec);  //建立一個空的table並壓入棧中,並預分配narr個array元素的空間和預分配nrec個非array元素的空間
void lua_newtable (lua_State *L); // lua_createtable的特例版,相當於呼叫 lua_createtable(L, 0, 0)

以上兩個方法用於建立一個lua table並壓入棧中,所以,想要返回一個table,就可以如下操作:

static int returntable(lua_State *L)
{
	lua_newtable(L);
	return 1;
}

上面程式碼返回的是一個空的table,如果需要向這個table中新增元素,API提供了幾個用於設定table元素的方法:
void lua_settable (lua_State *L, int index);
void lua_rawset (lua_State *L, int index);

void lua_setfield (lua_State *L, int index, const char *k);
void lua_rawseti (lua_State *L, int index, int n);

lua_settable在呼叫之前需要先壓入你要設定的 key 和 value,例如你要設定table["foo"] = "bar",先把 "foo" 壓入棧中,再把 "bar" 壓入棧中,然後再呼叫lua_settable,index取你要設定的table在棧中的索引,呼叫完該方法後,壓入的key和value會自動被彈出棧。lua_rawset除了設定時不會觸發元表操作外和lua_settable基本相同,修改示例程式碼為table新增元素:

static int returntable(lua_State *L)
{
	lua_newtable(L);
	
	lua_pushstring(L, "foo");
	lua_pushstring(L, "bar");
	
	lua_settable(L, -3);  //由於壓入了key和value,所以此時的table索引為-3
	//lua_rawset(L, -3); 也可以使用lua_rawset
	
	return 1;  //設定後,會彈出 "foo"和 "bar", 所以此時table仍舊處在棧頂
}

lua_setfield簡化了lua_settable的呼叫,key不再需要壓入棧中,而是直接當作函式的第三個引數傳入,所以只需要壓入value然後呼叫方法即可,呼叫後同樣會把value從棧中彈出,lua_rawseti 和 lua_setfield類似,但是隻能設定整數型別的key,可以對於純數字索引的陣列使用。

static int returntable(lua_State *L)
{
	lua_newtable(L);
	
	lua_pushstring(L, "foo");
	lua_pushstring(L, "bar");
	
	lua_settable(L, -3);  //由於壓入了key和value,所以此時的table索引為-3
	//lua_rawset(L, -3); 也可以使用lua_rawset
	
	lua_pushstring(L, "Tom");
	lua_setfield(L, -2, "username");  //由於只壓入了value,所以table此時的索引為-2
	
	return 1; 
}

接下來實現一個用指定字元切割字串返回陣列的函式:
static int split(lua_State *L)
{
        int len;
        const char *str = lua_tolstring(L, 1, &len);
        const char *sep = lua_tostring(L, 2);
        int lastpos = -1, i = 0, key = 1;

        lua_newtable(L); //建立一個table作為返回值
        for(;i < len; i++)
        {
                if (str[i] == *sep)
                {
                        lua_pushlstring(L, str + lastpos + 1, i - lastpos - 1);  //壓入子串
                        lua_rawseti(L, -2, key);  //把子串新增進table
                        lastpos = i;
                        key++;
                }
        }
        //處理最後一個子串
        lua_pushlstring(L, str + lastpos + 1, len - lastpos - 1);
        lua_rawseti(L, -2, key);
        return 1;
}


上面程式碼使用了lua_tolstring 和 lua_pushlstring 兩個方法,可以使用第三個引數,來獲取或設定字串的長度,這個方法可以在lua中像 split("aaa,bbb,ccc", ",") 這樣呼叫,並返回一個字串陣列。

同樣,C API也提供了獲取table值得方法:

void lua_gettable (lua_State *L, int index); //獲取索引為index的table中指定key的value,key要預先壓入棧,函式呼叫結束key會被彈出棧,並把value壓入棧中
void lua_rawget (lua_State *L, int index);  // lua_gettable類似,但不涉及元表

void lua_getfield (lua_State *L, int index, const char *k); //key不需要壓入棧,直接當作第三個引數傳入,得到的value同樣會被壓入棧中
void lua_rawgeti (lua_State *L, int index, int n); // 除了不涉及元表,並且key只能為整數

下面實現一個求陣列所有元素和的方法:
static int array_sum(lua_State *L)
{
        //檢查引數是否為一個table, 如果不是返回nil
        if (!lua_istable(L, 1))
        {
                lua_pushnil(L);
                return 1;
        }
        int sum = 0;
        int len = lua_objlen(L, 1);  //返回陣列的長度

        int i = 1;  //lua table 索引從1開始
        for(; i <= len; i++)
        {
                lua_rawgeti(L, 1, i);
                sum += lua_tointeger(L, -1);
                lua_pop(L, 1);  //將剛剛獲取的元素值從棧中彈出,其實也可以不用彈棧 因為table的索引始終是1,新讀取的值始終在棧頂,也就是索引是-1,不過這是一個好的習慣
        }

        lua_pushinteger(L, sum);
        return 1;
}

lua_istable 可以檢查一個值是否為table,是返回1,否則返回0, lua_objlen 可以獲取字串或陣列的長度

以上就是C API中關於table操作的一些方法,接下來介紹一下在C程式碼中呼叫lua函式。

假設現在lua指令碼中有這樣一種需求,要求有一個array_map方法,該方法接受兩個引數,第一個引數為一個數組,第二個引數為一個函式f,用數組裡的所有元素當作引數呼叫函式f,用返回值組成一個新的陣列並返回,大致的lua指令碼像這樣:

local function double(num)
	return num * 2
end

local arr = {1, 2, 3, 4, 5}

local res = array_map(arr, double) --期望得到 {2, 4, 6, 8, 10}

那該如何用C實現這個array_map方法呢,這就需要在C程式碼中呼叫lua函式,為此,C API提供了兩個用來呼叫lua函式的方法:
void lua_call (lua_State *L, int nargs, int nresults);
int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

這兩個方法都相對比較複雜,lua_call要求首先壓入要呼叫的lua函式,然後依次按順序壓入nargs個引數, 這時呼叫lua_call,之後這個函式和所有的引數都會從棧中彈出,最後把函式返回的nresults個結果依次壓入棧中。lua_pcall在保護模式下執行lua函式,當執行出錯時這個錯誤會被捕獲,而不會直接終止程式執行,lua_pcall的返回值是一個錯誤碼,當返回0時表示沒有錯誤,此時效果和lua_call一樣,當返回不為0時表示有錯誤,並將錯誤訊息壓入棧中,第四個引數還可以指定一個錯誤處理函式的索引,如果不指定可以傳入0值。

下面實現前面提到的array_map方法:

static int array_map(lua_State *L)
{
        //校驗引數型別
        if (!lua_istable(L, 1) || !lua_isfunction(L, 2))
        {
                lua_pushnil(L);
                return 1;
        }
        int len = lua_objlen(L, 1); //陣列的大小
        int i = 1;

        for(; i <= len; i++)
        {
                //由於方法呼叫之後會被彈出,所以呼叫之前先複製一份,pushvalue會將指定索引的值複製一份到棧頂
                lua_pushvalue(L, 2);
                lua_rawgeti(L, 1, i); //獲取陣列元素值並會壓入棧中
                lua_call(L, 1, 1);    //呼叫函式,一個引數 一個返回值,此時複製的函式和壓入的引數都會彈出,然後壓入結果
                lua_rawseti(L, 1, i);  //再把結果替換掉陣列中的原值。
        }
        lua_pushvalue(L, 1);  //把陣列複製一份放到棧頂 當作返回值
        return 1;
}

現在把本文中的所有方法放到上節中的clib.c中,重新編譯成clib.so
#include <lauxlib.h>

//編寫C函式 static使外部無法直接訪問這個函式
static int sum(lua_State *L)
{       
        int a = lua_tointeger(L, 1); //第一個加數,函式的第一個引數總是索引1
        int b = lua_tointeger(L, 2);  //第二個加數
        lua_pushinteger(L, a + b);  //壓入結果          
        return 1;                                       //返回1表示該方法只有一個返回值
}

static int split(lua_State *L)
{       
        size_t len;
        const char *str = lua_tolstring(L, 1, &len);
        const char *sep = lua_tostring(L, 2);
        int lastpos = -1, i = 0, key = 1;
        
        lua_newtable(L); //建立一個table作為返回值
        for(;i < len; i++)
        {       
                if (str[i] == *sep)
                {       
                        lua_pushlstring(L, str + lastpos + 1, i - lastpos - 1);  //壓入子串
                        lua_rawseti(L, -2, key);  //把子串新增進table
                        lastpos = i;
                        key++;
        //處理最後一個子串
        lua_pushlstring(L, str + lastpos + 1, len - lastpos - 1);
        lua_rawseti(L, -2, key);
        return 1; //返回table
}

static int array_sum(lua_State *L)
{
        //檢查引數是否為一個table, 如果不是返回nil
        if (!lua_istable(L, 1))
        {
                lua_pushnil(L);
                return 1;
        }
        int sum = 0;
        int len = lua_objlen(L, 1);  //返回陣列的長度

        int i = 1;  //lua table 索引從1開始
        for(; i <= len; i++)
        {
                lua_rawgeti(L, 1, i);
                sum += lua_tointeger(L, -1);
        }

        lua_pushinteger(L, sum);
        float aver = (float)sum / len; //平均值
        lua_pushnumber(L, aver);

        return 2;
}


static int array_map(lua_State *L)
{
        //校驗引數型別
        if (!lua_istable(L, 1) || !lua_isfunction(L, 2))
        {
                lua_pushnil(L);
                return 1;
        }
        int len = lua_objlen(L, 1); //陣列的大小
        int i = 1;

        for(; i <= len; i++)
        {
                //由於方法呼叫之後會被彈出,所以呼叫之前先複製一份,pushvalue會將指定索引的值複製一份到棧頂
                lua_pushvalue(L, 2);
                lua_rawgeti(L, 1, i); //獲取陣列元素值並會壓入棧中
                lua_call(L, 1, 1);    //呼叫函式,一個引數 一個返回值,此時複製的函式和壓入的引數都會彈出,然後壓入結果
                lua_rawseti(L, 1, i);  //再把結果替換掉陣列中的原值。
        }
        lua_pushvalue(L, 1);  //把陣列複製一份放到棧頂 當作返回值
        return 1;
}


//宣告一個luaL_Reg結構體陣列
static const struct luaL_Reg funcs[] = {
        {"sum", sum},
        {"split", split},
        {"array_sum", array_sum},
        {"array_map", array_map},
        {NULL, NULL}    // 該陣列最後一個元素始終是 {NULL, NULL}
};

int luaopen_clib(lua_State *L)
{
        luaL_register(L, "clib", funcs);        //第二個引數要和你的模組名一致
        return 1;
}

編寫lua測試指令碼:
local clib = require "clib"

local s = clib.sum(3, 99)
print(s)

print("---------------------")

local str = "aaa,bbbb,cccc,dddd,eeee"
local arr = clib.split(str, ",")

for i = 1, #arr do
        print(arr[i])
end

print("---------------------")

local arr1 = {1, 2, 3, 10}
local sum = clib.array_sum(arr1)
print("sum=", sum)

print("---------------------")

local function double(num)
        return num * 2
end
local arr2 = {1, 2, 3, 4, 5}
local res = clib.array_map(arr2, double)

for i = 1, #res do
        print(res[i])
end

執行結果:
102
---------------------
aaa
bbbb
cccc
dddd
eeee
---------------------
sum=	16
---------------------
2
4
6
8
10

篇幅所限,就寫到這裡,如果想更深入瞭解lua擴充套件的開發技術,歡迎關注後續文章。

相關推薦

C開發lua模組 --- 操作table呼叫lua函式

C語言沒有類似 lua_pushtable 和 lua_totable 的方法,這也很容易理解,因為C語言中沒有一種資料型別可以和lua中的table相對應的,那C函式怎麼操作table型別的引數或者返回table型別的結果呢,lua API中提供了一系列的操作table

嵌入式C++開發詳解

面向物件程式設計(一) 一、面向物件程式設計介紹 (一)什麼是面向物件?       面向將系統看成通過互動作用來完成特定功能的物件的集合。每個物件用自己的方法來管理資料。也就是說只有物件內部的程式碼

Java開發學習心得:MybatisUrl路由

rac 處理 2.4 報錯 localhost insert 實體 tips control Java開發學習心得(二):Mybatis和Url路由 序號接上一篇Java開發學習心得(一):SSM環境搭建 1.3 Mybatis MyBatis 本是apache的一個開源項

C語言陣列篇指標陣列陣列指標

陣列指標 和 指標陣列           這兩個名詞可以說是經常搞混了         陣列指標--> 陣列的

C++動態記憶體:過載newdelete

一、過載的原因     用new建立動態物件時會發生兩件事:(1)使用operatoe

我的C#跨平臺之旅開發一組標準的Restful API

ref 運行 mar margin bruce ora soft left 啟用 添加NuGet引用:Microsoft.AspNet.WebApi.Owin 在啟動類啟用WebApi; 添加一個Controller類,代碼如下: 運行程序

Smobiler實現美觀登入介面——C# 或.NET Smobiler例項開發手機app

目錄 一、 本文目標 二、 準備工作 1、 資料庫 2、 材料 三、 介面佈局 1、設定控制元件的屬性值 (1) 輸入框 (2) 圖片屬性 (3) HandElectricity的標題的label屬性 (4)登入按鈕 (5)版權申明  (

C#框架程式設計動態載入模組

本文系原創,轉載請註明出處: 在上一篇部落格中,我完成了介面的設計部分,下面我接著來講具體的程式碼實現。先來看模組配置頁面的實現,看程式碼: private void LoadItem() { string sq

C#.架構設計 資料c# 專案中包含了多個模組或多個功能,如何靈活開啟/關閉、新增/刪除某個模組或功能

一、簡介       不知不覺,短短几個月的時間,我已經寫了大大小小100篇部落格。短短几個月的時間,見證了我的努力、我的收穫、我的學習效率。從一開始的零基礎,到現在我需要了解整個專案的設計架構,才能來滿足我的設計需求。      

c++實戰開發詳解類與物件

一、面向物件程式設計介紹 (一)什麼是面向物件?      面向將系統看成通過互動作用來完成特定功能的物件的集合。每個物件用自己的方法來管理資料。也就是說只有物件內部的程式碼能夠操作物件內部的資料。

C語言擴充套件lua模組入門

#include <lua.h> #include <lauxlib.h> #include <lualib.h> static void encode_sha(const char* src, char* des) { /* *sha-hash /

配置 vim C/C++集成開發環境實踐

可能 默認 sorl tgui ace ast gvim evm arch 主要參考GitHub項目:所需即所獲:像 IDE 一樣使用 vim 其教程講解已相當詳細,故此處文章僅簡要記錄個人按照上述項目學習配置vim的過程。 可能是個人理解不到位或者項目久未更新的原因,實踐

C語言天天練】statickeyword

修飾 weight () main函數 class code keyword spa tail 引言: statickeyword不僅能夠修飾變量。並且能夠修飾函數。了解它的使用方法,不僅對閱讀別人的代碼有幫助,也有助於自己寫出更加健壯的

C++構造函數

frame 筆記 自動轉換 數據類型 public clas 並不是 調用 這樣的 本篇是介紹C++的構造函數的第二篇(共二篇),屬於讀書筆記,對C++進行一個系統的復習。 復制構造函數 復制構造函數是構造函數的一種,也被稱為拷貝構造函數,他只有一個參數,參數類型是本類的引

PythonC|C++的混編:利用Cython進行混編

cde uil 有時 當前 class def 將在 python 混編 還能夠使用Cython來實現混編 1 下載Cython。用python setup.py install進行安裝 2 一個實例 ① 創建helloworld文件夾創建hellowor

C++ 模板詳解

創建 規則 error ++ 例如 public err iostream () 四、類模板的默認模板類型形參   1、可以為類模板的類型形參提供默認值,但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認值。   2、類模板的類型形

Python開發簡單爬蟲---爬取百度百科頁面數據

class 實例 實例代碼 編碼 mat 分享 aik logs title 一、開發爬蟲的步驟 1.確定目標抓取策略: 打開目標頁面,通過右鍵審查元素確定網頁的url格式、數據格式、和網頁編碼形式。 ①先看url的格式, F12觀察一下鏈接的形式;② 再看目標文本信息的

Spring Data 開發環境搭建

是不是 lns utf-8 void ext for 實體類 connect domain 首先咱們先創建一個maven工程 在pom.xml加入以下 依賴 <!--Mysql 驅動包--> <dependency> <

學習Opencv 2.4.9 ---操作像素

椒鹽噪聲 window align 是個 簡單 ++ ima opencv2 mar 作者:咕唧咕唧liukun321來自:http://blog.csdn.net/liukun321本質上說一張圖像就是由數值組成的矩陣。Opencv 2.x由 cv::Mat 這個數據

讀書筆記--C陷阱與缺陷

ase 結果 erro bit 使用 功能 錯誤 多層 gnu 第二章 1. 理解函數聲明 書中分析了復雜的類型聲明方式,也說明了使用typedef聲明會更好理解,推薦大家使用typedef進行函數聲明。 書中類型分析一層一層挖掘,讓讀者可以理解多層嵌套的類型含義,有