程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> [Lua]在C函數中保存狀態--注冊表,環境表,upvalue

[Lua]在C函數中保存狀態--注冊表,環境表,upvalue

編輯:關於C語言

[Lua]在C函數中保存狀態--注冊表,環境表,upvalue


什麼叫做在C函數中保存狀態?比如你現在使用Lua調用了C函數Func1,但是Func1中有一些數據在調用完以後保存下來,供以後使用。而這些數據就是所謂的狀態,也就是我們需要保存的東東。有人就會說了,Lua調用C時,把所有的需要保存的狀態都返回到Lua中,當調用下一個函數時,將需要的狀態當做參數再傳進去,不錯,是一個辦法,但是很麻煩。方法一:注冊表;方法二:環境;方法三:upvalue。
注冊表是一個全局的table,它只能被C代碼訪問。通常,可以用它來保存那種需要在幾個模塊中共享的數據;
但是,如果需要保存一個模塊的私有數據,那麼應該使用環境,與Lua函數一樣,每個C函數都有自己的環境table,通常情況下,一個模塊內的所有函數共享同一個環境table,由此它們可以共享數據。
最後,C函數也可以擁有upvalue,upvalue是一種與特定函數相關聯的Lua值。

注冊表

注冊表總是位於一個“偽索引”上,這個索引值由LUA_REGISTRYINDEX定義。偽索引就像是一個棧中的索引,但它所關聯的值不在棧中;所完這句話,你想到了什麼?C++中,使用new開辟空間,這個空間是在堆上開辟的,而指向這個堆的變量卻是存放在棧上的。偽索引和這個意思差不多。Lua API中的大多數函數都能接受偽索引,但像lua_remove和lua_insert這種操作棧本身的函數卻只能使用普通索引。
注冊表是一個普通的Lua table,可以使用任何Lua值(nil除外)來索引它。
#include
using namespace std;
#include

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));//輸出為:Hello
}

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

環境表

從5.1開始,在Lua中注冊的所有C函數都有自己的環境table。一個函數可以像訪問注冊表那樣,通過一個偽索引來訪問它的環境table。環境table的偽索引是LUA_ENVIRONINDEX。
這種使用環境的方法與在Lua模塊中使用環境的方法差不多,都是先為模塊創建一個新的table,然後使模塊中的所有函數都共享這個table。只不過,在Lua中使用了一個setfenv函數,而在C模塊中,只不過是設置table為LUA_ENVIRONINDEX

#include
using namespace std;
#include
int setValue(lua_State* L)
{
    luaL_checkstring(L,-1);
    lua_pushvalue(L,-1);
    lua_setfield(L,LUA_ENVIRONINDEX,"key1");
    return 0;
}
 
int getValue(lua_State* L)
{
    lua_getfield(L,LUA_ENVIRONINDEX,"key1");
    return 1;
}

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;
    //這個注冊函數比以前寫的注冊函數要多兩行代碼,先要創建一個新的table,然後調用lua_replace將新的table作環境table。然後調用luaL_register時,所有新建的函數都會繼承當前環境。
}
test.lua文件內容
require "testenv"
local fun1 = function()
    local var = "Hello,world!!!"
    testenv.setValue(var)
    print(testenv.getValue()) --輸出Hello,world!!!
end
xpcall(fun,print)
os.execute("pause")
上面先將值設置到模塊環境table中。然後再從中取出來。這個和上面說的注冊表有很多的相似之處。盡管可能使用環境來代替注冊表,但是如果沒有在不同模塊之間共享數據的需要,就盡可能的不要使用注冊表;使用環境創建的引用,只是在本模塊中可見,這樣縮小了數據的使用范圍了,減小了數據被錯改的可能,增加了數據的安全性。

upvalue

upvalue是和特定函數關聯的,我們可以將其簡單的理解為函數內的靜態變量。
注冊表提供了全局變量的存儲,環境提供了模塊變量的存儲,而upvalue機制則實現了一種類似於C語言中靜態變量的機制。而這種upvalue機制,可以讓我們定義一個只在特定的函數中可見的變量。每當在Lua中創建一個函數時,都可以將任意數量的upvalue與這個函數相關聯。每個upvalue都可以保存一個Lua值。以後,在調用這個函數時,就可以通過偽索引來訪問這些upvalue了。將這種C函數與upvalue的關聯稱為closure(也叫閉包,多麼熟悉的名字)。一個C closure類似於Lua closure。closure可以用同一個函數代碼來創建多個closure,每個closure可以擁有不同的upvalue。
#include
using namespace std;
#include
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;
}

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;
}
test.lua文件內容
require "testupvalue"
local fun = function()
    func = testupvalue.newCounter();
    print(func());
    print(func());
    print(func());

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

    --[[ 輸出結果為:
    1
    2
    3
    1
    2
    3
    --]]
end
xpcall(fun,print)
os.execute("pause")

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved