用戶自定義類型

userdata:函數

userdata機制可讓咱們在lua中使用c中的自定義數據類型。userdata表示一塊動態分配的內存,這塊內存就存儲的自定義類型的數據,在lua腳本中使用userdata,並配合c提供的函數,就能夠操做userdata了。lua

 

定義一個player類型:spa

typedef struct _Player {
    int id; 
    char name[20];
    int account;
} Player;

定義player的全部操做:指針

static int _index = 1;
static int player_new (lua_State* L) {
    const char* name = luaL_checkstring(L, 1);
    int len = strlen(name);

    Player* player = (Player*)lua_newuserdata(L, sizeof(Player)); // 使用lua_newuserdata建立userdata,並將其入棧
    player->id = _index++;
    memcpy(player->name, name, len + 1); // 須要拷貝一份字符串,不然棧在彈出的時候,字符串會被銷燬
    player->account = 0;
    
    return 1;
}
static int player_print (lua_State* L) {
    Player* player = (Player*)lua_touserdata(L, 1);
    
    printf("player data: %d %s acount:%d \n", player->id, player->name, player->account);
    
    return 0;
}
static int player_charge (lua_State* L) {
    Player* player = (Player*)lua_touserdata(L, 1);
    int add = luaL_checkint(L, 2);
    
    player->account += add;
    
    return 0;
}

lua代碼:code

local player = Player.new("xiaoming")
local player1 = Player.new("xiaoqiang")

Player.charge(player1, 20)
Player.charge(player, 101)
Player.print(player)
Player.print(player1)

 

元表:對象

1. 相同的元表表明相同的類型,所以,咱們也使用元表來爲userdata標示類型:blog

爲userdata設置元表:內存

const char* CLASS_NAME_PLAYER = "Player_Class";作用域

Player* player = lua_newuserdata(L, sizeof(Player));字符串

lua_newmetatable(L, CLASS_NAME_PLAYER); // 建立一個新的元表,名字爲player_class,併入棧

lua_setmetatable(L, -2); // 爲位置在-2的userdata,設置元表,元表出棧

如何使用元表來進行類型判斷:

Player* player = (Player*)lua_touserdata(L, 1);

改成

Player* player = (Player*)lua_checkudata(L, 1, CLASS_NAME_PLAYER); 若是userdata的類型不匹配,將拋出錯誤

2. 在lua中,元表除了能夠標示類型,更重要的是模擬面向對象,和普通lua對象同樣,userdata一樣可使用元表機制來模擬面向對象:

咱們首先建立一個元表,只須要把對象的方法放在元表上,最重要的是設置元表的__index元方法:

luaL_newmetatable(L, CLASS_NAME_PLAYER); // 建立一個新的元表,併入棧,該元表是存放在全局做用域中的

...... // 設置一些對象方法

lua_pushvalue(L, -1); // 複製元表

lua_setfield(L, -2, "__index"); // 將元表的__index元方法設置爲本身

在建立新對象的時候,只須要將新對象的元表設置爲已經建立好的元表:

luaL_getmetatable(L, CLASS_NAME_PLAYER); // 將元表入棧

lua_setmetatable(L, -2); // 設置元表,元表出棧

上面的例子改成:

c代碼:

static int _index = 1;
const char* CLASS_NAME_PLAYER = "PLAYER_CLASS";
static int player_new (lua_State* L) {
    dump(L);
    
    const char* name = luaL_checkstring(L, 1);
    int len = strlen(name);
    
    dump(L);
    
    Player* player = (Player*)lua_newuserdata(L, sizeof(Player));
    dump(L);
    player->id = _index++;
    memcpy(player->name, name, len + 1);
    player->account = 0;
    
  // 使用已經建立好的元表 luaL_getmetatable(L, CLASS_NAME_PLAYER); lua_setmetatable(L,
-2); return 1; } static int player_print (lua_State* L) { Player* player = (Player*)luaL_checkudata(L, 1, CLASS_NAME_PLAYER); printf("player data: %d %s acount:%d \n", player->id, player->name, player->account); return 0; } static int player_charge (lua_State* L) { Player* player = (Player*)luaL_checkudata(L, 1, CLASS_NAME_PLAYER); int add = luaL_checkint(L, 2); player->account += add; return 0; } int libopen_player (lua_State* L) {
  // 建立元表 luaL_newmetatable(L, CLASS_NAME_PLAYER); lua_pushcfunction(L, player_print); lua_setfield(L,
-2, "print"); lua_pushcfunction(L, player_charge); lua_setfield(L, -2, "charge"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); lua_settop(L, 0);
  // 模塊只有一個new方法了 lua_newtable(L); lua_pushcfunction(L, player_new); lua_setfield(L,
-2, "new"); lua_setglobal(L, "Player"); return 1; }

lua代碼:

local player = Player.new("xiaoming")
local player1 = Player.new("xiaoqiang")

player1:charge(30)
player:charge(20)
player:print()
player1:print();

 

輕量級的userdata:

對比徹底的userdta,輕量級的userdata只是c對象的一個指針,沒有元表,就是一個普通的lua對象,就像number同樣,所以輕量級的userdata不受lua垃圾回收機制的控制,必須本身管理內存。

c代碼:

Player* player = nullptr;
static int player_pointer (lua_State* L) {
    player = new Player();
    player->id = 12;
    memcpy(player->name, "wulin", 6);
    player->account = 0;
    
    lua_pushlightuserdata(L, player);
    
    return 1;
}

lua代碼:

local player1 = Player.pointer();
local player2 = Player.pointer();

print(player1);
print(player2);

 

userdata的內存回收:

userdata屬於lua的內存管理機制,所以無須關係userdata的內存問題,但若是userdata使用了一些c內存中的對象,而且須要在userdata被刪除的時候,同時刪除這些對象,那麼lua的內存回收機制就無能力爲。這種狀況下,lua爲咱們提供了一個__gc元方法(只針對userdata),當userdata被刪除時,會調用這個元方法,並將userdata做爲參數傳入,這樣咱們就能夠刪除userdata中引用的c對象了。

在player中添加一個__gc的元方法:

static int player_delete (lua_State* L) {
    Player* player = (Player*)luaL_checkudata(L, 1, CLASS_NAME_PLAYER);
    
    printf("delete something not in lua memory... player name:%s \n", player->name);
    
    return 0;
}
int libopen_player (lua_State* L) {
    luaL_newmetatable(L, CLASS_NAME_PLAYER);
    lua_pushcfunction(L, player_print);
    lua_setfield(L, -2, "print");
    lua_pushcfunction(L, player_charge);
    lua_setfield(L, -2, "charge");
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    lua_pushcfunction(L, player_delete);
    lua_setfield(L, -2, "__gc"); // 添加__gc元方法
    lua_settop(L, 0);
    
    lua_newtable(L);
    lua_pushcfunction(L, player_new);
    lua_setfield(L, -2, "new");
    lua_pushcfunction(L, player_pointer);
    lua_setfield(L, -2, "pointer");
    lua_setglobal(L, "Player");
    
    return 1;
}

lua代碼:

local player = Player.new("xiaoming")
player:charge(20)
player:print()

player = nil

collectgarbage(); // 強制進行垃圾回收
相關文章
相關標籤/搜索