Lua 與 C/C++ 通信的核心機制是一個虛擬堆棧。Lua 提供完整的 C API 對堆棧進行操作,實現數據雙向流動。本文詳細解析堆棧核心操作函數、索引機制、類型判斷與轉換、以及實戰代碼示例。
lua_State* 可視為指向堆棧的句柄TValue 結構存儲:{值, 類型}| 函數 | 說明 |
|---|---|
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) |
| 檢查函數 | 轉換函數 | 說明 |
|---|---|---|
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...) |
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 位置值
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
// 調用 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)
// 錯誤信息會被壓入棧頂
#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;
}
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);
}
}
// 假設 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); // 彈出返回值
}
lua_pop(L, n) 彈出 n 個元素(實為 lua_settop(L, -n-1))lua_tostring 返回指針指向 Lua 內部緩衝區,不得在彈出/修改棧後繼續使用,需自行 strdup 或複製到 C++ std::stringlua_pcall 非零返回碼均表示錯誤,錯誤信息在棧頂,必須 lua_pop 清理掌握 Lua 堆棧 API 的壓入/檢查/轉換/結構操作/表操作/函數調用六大類核心接口,是實現高效、穩健的 Lua-C++ 交互的基礎。關鍵在於理解棧為唯一通信介質、索引雙向寻址、類型標籤統一值存儲、生命周期歸 Lua 管理這四大核心機制。下一篇將深入講解 C++ 註冊模塊、用戶數據綁定、元表機制等進階主題。