首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Lua 與 C++ 交互深度解析(二):堆棧操作 API 詳解

     編輯:關於C++

Lua 與 C/C++ 通信的核心機制是一個虛擬堆棧。Lua 提供完整的 C API 對堆棧進行操作,實現數據雙向流動。本文詳細解析堆棧核心操作函數、索引機制、類型判斷與轉換、以及實戰代碼示例。

一、堆棧基礎模型回顧

  • 堆棧索引:正數從底向上(1 = 棧底),負數從頂向下(-1 = 棧頂)
  • lua_State* 可視為指向堆棧的句柄
  • 所有 Lua 值(數值、字符串、表、函數、userdata 等)統一用 TValue 結構存儲:{值, 類型}

二、核心堆棧操作 API

2.1 壓入操作

函數說明
lua_pushnil(L)壓入 nil
lua_pushboolean(L, b)壓入布爾值
lua_pushnumber(L, n)壓入數值(double)
lua_pushinteger(L, n)壓入整數(lua_Integer)
lua_pushstring(L, s)壓入字符串(複製一份)
lua_pushlstring(L, s, len)壓入指定長度字符串
lua_pushcfunction(L, f)壓入 C 函數(作為 Lua 函數可調用)
lua_pushlightuserdata(L, p)壓入輕量用戶數據(裸指針,無 GC)

2.2 類型檢查與轉換

檢查函數轉換函數說明
lua_isnil(L, idx)—是否為 nil
lua_isboolean(L, idx)lua_toboolean(L, idx)布爾值(非 0 為 true)
lua_isnumber(L, idx)lua_tonumber(L, idx)數值(double)
lua_isinteger(L, idx)lua_tointeger(L, idx)整數
lua_isstring(L, idx)lua_tostring(L, idx)字符串(返回 const char*,生命周期綁定堆棧)
lua_istable(L, idx)—表(無直接轉換,需 API 操作)
lua_isfunction(L, idx)—函數(Lua/C)
lua_isuserdata(L, idx)lua_touserdata(L, idx)用戶數據指針
lua_type(L, idx)—返回類型枚舉(LUA_TNIL, LUA_TBOOLEAN...)

2.3 堆棧結構操作

int lua_gettop(lua_State* L);           // 返回棧頂索引 = 棧大小
void lua_settop(lua_State* L, int idx); // 設置棧頂:idx > 當前頂 → 補 nil;idx < 當前頂 → 截斷棄元素
                                        // lua_settop(L, 0) 可清空棧

void lua_pushvalue(lua_State* L, int idx);   // 將 idx 處值的副本壓入棧頂
void lua_remove(lua_State* L, int idx);      // 移除 idx 處元素,上方元素下移
void lua_insert(lua_State* L, int idx);      // 彈出棧頂元素,插入 idx 位置
void lua_replace(lua_State* L, int idx);     // 彈出棧頂元素,替換 idx 位置值

2.4 表操作

void lua_newtable(lua_State* L);           // 創建新表並壓入棧頂
void lua_createtable(lua_State* L, int narr, int nrec); // 預分配陣列/哈希部分大小

// 獲取表字段:相當於 lua_pushstring(L, k) + lua_gettable(L, -2)
void lua_getfield(lua_State* L, int idx, const char* k);

// 設置表字段:相當於 lua_pushstring(L, k) + lua_insert(L, -2) + lua_settable(L, -3)
void lua_setfield(lua_State* L, int idx, const char* k);

// 通用獲取/設置:鍵值均在棧上
void lua_gettable(lua_State* L, int idx);    // t = table; k = stack top; pop k, push t[k]
void lua_settable(lua_State* L, int idx);    // t = table; k = stack[-2]; v = stack[-1]; pop k,v; t[k]=v

// 原始操作(不觸發元方法)
void lua_rawget(lua_State* L, int idx);
void lua_rawset(lua_State* L, int idx);
void lua_rawgeti(lua_State* L, int idx, int n); // 獲取整數鍵 n
void lua_rawseti(lua_State* L, int idx, int n); // 設置整數鍵 n

2.5 函數調用

// 調用 Lua 函數:func, arg1, arg2... 在棧上(func 在底,參數在上)
// nargs: 參數個數;nresults: 期望返回值個數(LUA_MULTRET 表示全部);errfunc: 錯誤處理函數索引(0 表示無)
int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc);

// 受保護調用返回碼:0 成功;非 0 錯誤碼(LUA_ERRRUN, LUA_ERRMEM, LUA_ERRERR, LUA_ERRGCMM)
// 錯誤信息會被壓入棧頂

三、完整實戰示例

3.1 基礎棧操作演示

#include 
#include 
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}

int main() {
    // 1. 創建 Lua 狀態機
    lua_State* L = luaL_newstate();
    if (!L) return 1;

    // 2. 壓入各類型值
    lua_pushstring(L, "I am so cool~");
    lua_pushnumber(L, 20);
    lua_pushboolean(L, 1);
    lua_pushnil(L);

    // 3. 遍歷棧並打印類型與值
    int top = lua_gettop(L);
    std::cout << "Stack size: " << top << "\n";
    for (int i = 1; i <= top; ++i) {
        int t = lua_type(L, i);
        std::cout << "Index " << i << " (top-rel " << i - top - 1 << "): type=" << lua_typename(L, t);
        switch (t) {
            case LUA_TSTRING: std::cout << " value=" << lua_tostring(L, i); break;
            case LUA_TNUMBER: std::cout << " value=" << lua_tonumber(L, i); break;
            case LUA_TBOOLEAN: std::cout << " value=" << (lua_toboolean(L, i) ? "true" : "false"); break;
            case LUA_TNIL: std::cout << " value=nil"; break;
            default: std::cout << " (non-scalar)";
        }
        std::cout << "\n";
    }

    // 4. 測試 lua_settop 截斷/擴展
    lua_settop(L, 2); // 保留底部 2 個
    std::cout << "After settop(2), size=" << lua_gettop(L) << "\n";

    lua_settop(L, 5); // 擴展到 5,新增 nil
    std::cout << "After settop(5), size=" << lua_gettop(L) << "\n";

    lua_close(L);
    return 0;
}

3.2 表操作綜合示例

void table_demo(lua_State* L) {
    // 創建表 {name="Alice", age=20, "hobby"}
    lua_newtable(L);                       // stack: table
    lua_pushstring(L, "name");             // stack: table, "name"
    lua_pushstring(L, "Alice");            // stack: table, "name", "Alice"
    lua_settable(L, -3);                   // stack: table  (t["name"]="Alice")

    lua_pushstring(L, "age");
    lua_pushinteger(L, 20);
    lua_settable(L, -3);

    // 陣列部分
    lua_pushinteger(L, 1); lua_pushstring(L, "reading");  lua_settable(L, -3);
    lua_pushinteger(L, 2); lua_pushstring(L, "coding");   lua_settable(L, -3);

    // 簡寫:lua_setfield / lua_getfield
    lua_pushstring(L, "city");
    lua_pushstring(L, "Beijing");
    lua_setfield(L, -2, "city");  // 等價於上面 settable 組合

    // 讀取驗證
    lua_getfield(L, -1, "name");  // stack: table, "Alice"
    std::cout << "name: " << lua_tostring(L, -1) << "\n";
    lua_pop(L, 1);

    lua_getfield(L, -1, "age");
    std::cout << "age: " << lua_tointeger(L, -1) << "\n";
    lua_pop(L, 1);

    // 遍歷陣列部分
    std::cout << "hobbies:\n";
    for (int i = 1; ; ++i) {
        lua_rawgeti(L, -1, i);  // 不觸發元方法
        if (lua_isnil(L, -1)) { lua_pop(L, 1); break; }
        std::cout << "  [" << i << "] " << lua_tostring(L, -1) << "\n";
        lua_pop(L, 1);
    }
}

3.3 C++ 調用 Lua 函數完整流程

// 假設 Lua 腳本已加載並定義了 function add(a,b) return a+b end
lua_getglobal(L, "add");      // 1. 函數壓棧
lua_pushnumber(L, 10);        // 2. 參數 1
lua_pushnumber(L, 20);        // 3. 參數 2

int ret = lua_pcall(L, 2, 1, 0); // 4. 調用:2 參數,1 返回值
if (ret != LUA_OK) {
    std::cerr << "Lua error: " << lua_tostring(L, -1) << "\n";
    lua_pop(L, 1);
} else {
    double sum = lua_tonumber(L, -1);
    std::cout << "10 + 20 = " << sum << "\n";
    lua_pop(L, 1); // 彈出返回值
}

四、關鍵注意事項

  1. 棧平衡:每次操作後務必確保棧狀態符合預期,lua_pop(L, n) 彈出 n 個元素(實為 lua_settop(L, -n-1))
  2. 索引有效性:傳遞給 API 的索引必須在當前棧範圍內,否則行為未定義
  3. 字符串生命周期:lua_tostring 返回指針指向 Lua 內部緩衝區,不得在彈出/修改棧後繼續使用,需自行 strdup 或複製到 C++ std::string
  4. 錯誤處理:所有 lua_pcall 非零返回碼均表示錯誤,錯誤信息在棧頂,必須 lua_pop 清理
  5. GC 交互:壓入的字符串/表/函數/userdata 均由 Lua 管理生命周期,C++ 僅持有棧索引引用

五、總結

掌握 Lua 堆棧 API 的壓入/檢查/轉換/結構操作/表操作/函數調用六大類核心接口,是實現高效、穩健的 Lua-C++ 交互的基礎。關鍵在於理解棧為唯一通信介質、索引雙向寻址、類型標籤統一值存儲、生命周期歸 Lua 管理這四大核心機制。下一篇將深入講解 C++ 註冊模塊、用戶數據綁定、元表機制等進階主題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved