C++ 与 Lua 数据交互载体——栈

一、栈

Lua 和 C 之间的通讯主要组件是无处不在的虚拟栈,两者间的数据交换都是通过这个栈进行

栈中可以保存 Lua 任意类型的值。

1、Lua 和 C 之间的数据交互存在的差异

  1. Lua 是动态类型,C 是静态类型,两者不匹配
  2. Lua 是自动内存管理,C 是手动内存管理

2、垃圾收集器

Lua 与 C/C++ 的数据交互,都是通过在一方压栈(存入数据),在另一方出栈(获取数据),所以栈是能感知到数据的。

而栈是 LuaState 的一部分,所以垃圾收集器能够知道 C/C++ 正在使用哪些数据,也就知道该如何管理内存。

3、栈操作原则

Lua 的栈严格遵守 LIFO (Last in first out,后进先出) , Lua 中只有栈顶的部分会发生改变。

C 语言代码则有更大的自由度,可以检视栈中的任何一个元素,甚至可以在栈的任意位置插入或删除元素。

二、C++ 压栈

针对每一种能用 C 语言直接表示的 Lua 数据类型,C API 中都有一个对应的压栈函数

下面是 C++ 所有的压栈函数

C API 函数 描述
void (lua_pushnil) (lua_State *L); 将 nil 压栈
void (lua_pushnumber) (lua_State *L, lua_Number n); 将 双精度浮点数 压栈
void (lua_pushinteger) (lua_State *L, lua_Integer n); 将 整型数 压栈
void (lua_pushboolean) (lua_State *L, int b); 将 布尔值 压栈
const char *(lua_pushstring) (lua_State *L, const char *s); 将 字符串(以 "\0" 结尾) 压栈
const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); 将 字符串(会结合 "\0" 和长度的参数决定字符串的长度) 压栈
const char *(lua_pushvfstring) (lua_State *L, const char *fmt,va_list argp); 将 字符串(格式化字符串,接收可变参数) 压栈
const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); 将 字符串(格式化字符串) 压栈
void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); 将 C 函数 压栈
void (lua_pushlightuserdata) (lua_State *L, void *p); 将 用户数据 压栈
int (lua_pushthread) (lua_State *L); 将 线程状态(即 lua_State ) 压栈

举个例子:

lua_pushcclosurelua_pushlightuserdata 后续文章进行分享

使用以上的所有 api ,进行相应数据类型的压栈

cpp 复制代码
lua_pushnil(L);
lua_pushnumber(L, 0.1);
lua_pushnumber(L, 0);
lua_pushinteger(L, 10);
lua_pushboolean(L, 1);
// lua_pushstring 会在 "\0" 终止
lua_pushstring(L, "lua_pushstring hello 江澎涌\0jiang peng yong");

// lua_pushlstring 同样会在 "\0" 终止,但会结合考虑长度参数
const char *sayHello = "lua_pushlstring hello jiang peng yong.\0 会被忽略的字符";
lua_pushlstring(L, sayHello, strlen(sayHello) - 6);

// lua_pushvfstring
const char *name = "jiang peng yong";
int age = 29;
showInfo(L, name, age);

lua_pushfstring(L, "lua_pushfstring name: %s, age: %d", name, age);

lua_pushthread(L);

showInfo 函数如下所示,为了能演示 va_listlua_pushvfstring 的结合使用

cpp 复制代码
void showInfo(lua_State *L, ...) {
    va_list args;
    // 用于开启遍历可变参数,第二个参数是可变参数列表的前一参数
    va_start(args, L);
    // 在这里可以使用 va_list 对象 args
    lua_pushvfstring(L, "lua_pushvfstring name: %s, age: %d", args);
    va_end(args);
}

最后通过以下的函数将栈打印出来,就一目了然了

这一函数主要使用了以下的 api ,先有一个大概了解,接下来的小节会更加详细的讲解

  • lua_gettop 获取栈中元素个数
  • lua_typename 获取类型名称
  • lua_toxxx 获取栈对应索引的元素值,并转为相应类型 xxx
cpp 复制代码
void stackDump(lua_State *L) {
    int i;
    // 获取栈中元素个数
    int top = lua_gettop(L);
    printf("栈顶\n");
    for (i = top; i >= 1; i--) {
        // 元素类型
        int t = lua_type(L, i);
        // 元素类型名称
        printf("^ typename: %s, ", lua_typename(L, t));
        switch (t) {
            case LUA_TSTRING:   // 字符串类型
                printf("value: '%s'", lua_tostring(L, i));
                break;
            case LUA_TBOOLEAN:  // 布尔类型
                printf(lua_toboolean(L, i) ? "value: true" : "value: false");
                break;
            case LUA_TNUMBER:   // 数值类型
                if (lua_isinteger(L, i)) {  // 是否整型
                    printf("value(integer): %lld", lua_tointeger(L, i));
                } else {   // 浮点类型
                    printf("value(number): %g", lua_tonumber(L, i));
                }
                break;
            default:     // 其他类型
                // 类型名称
                printf("value: %s", lua_typename(L, t));
        }
        printf("    \n");
    }
    printf("栈底\n");
    printf("\n");
}

打印之后,刚才一系列操作的栈内容如下:

cpp 复制代码
栈顶
^ typename: thread, value: thread    
^ typename: string, value: 'lua_pushfstring name: jiang peng yong, age: 29'    
^ typename: string, value: 'lua_pushvfstring name: jiang peng yong, age: 29'    
^ typename: string, value: 'lua_pushlstring hello jiang peng'    
^ typename: string, value: 'lua_pushstring hello 江澎涌'    
^ typename: boolean, value: true    
^ typename: number, value(integer): 10    
^ typename: number, value(number): 0    
^ typename: number, value(number): 0.1    
^ typename: nil, value: nil    
栈底

1、lua_pushlstring、lua_pushstring、lua_pushvfstring、lua_pushfstring 的区别

四个函数都是将字符串压入到栈中,只是参数上有些不同

lua_pushstring:传入的字符串则是以 \0 为终止符,第一个 "\0" 之后的内容会被忽略。

lua_pushlstring:相比 lua_pushstring 多了一个额外的长度参数(第三个参数),同样会以 \0 为终止符,第一个 "\0" 之后的内容会被忽略。

lua_pushvfstring:将 argp 参数按照 fmt 参数格式化字符串,argp 是一个 va_list 类型的变长参数指针。

lua_pushfstring:将 ... 参数按照 fmt 参数格式化字符串。

可以通过上面的例子感受到用法上的不同

Lua 和 C/C++ 的字符串不同:Lua 不是以 \0 作为结尾, 他可以包含任意的二进制数据。

值得注意: Lua 语言不会保留指向 "外部字符串" 或指向 "除静态的 C 语言函数外的任何外部对象" 的指针。对于不得不保留的字符串,Lua 要么生成一个内部副本,要么复用已有的字符串。所以一旦 lua_pushlstring 此类字符串压栈的函数执行完后,即使立即释放或修改缓冲区也不会出现任何问题。

2、lua_Number 类型

lua_Number 默认为 double

从 Lua 的源码默认的宏定义可以证明

cpp 复制代码
#define LUA_FLOAT_DEFAULT	LUA_FLOAT_DOUBLE
#define LUA_FLOAT_TYPE	LUA_FLOAT_DEFAULT

// 对 LUA_FLOAT_DEFAULT 的使用如下,其他的代码省略了
#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE	/* }{ double */
#define LUA_NUMBER	double 

可以通过改变 LUA_FLOAT_TYPE 的宏定义,达到 lua_Number 真正类型不同

LUA_FLOAT_TYPE 可以定义为

  • LUA_FLOAT_FLOATlua_Number 被定义为 float
  • LUA_FLOAT_DOUBLE (默认设定) 则 lua_Number 被定义为 double
  • LUA_FLOAT_LONGDOUBLElua_Number 被定义为 long double

这些配置是在 Lua 源文件的 luaconf.h 文件中

3、lua_Integer 类型

和 lua_Number 一样,lua_Integer 也可以进行默认类型的配置

lua_Integer 默认为 long long(有符号 64 位整型)

cpp 复制代码
#define LUA_INT_DEFAULT		LUA_INT_LONGLONG
#define LUA_INT_TYPE	LUA_INT_DEFAULT

#elif LUA_INT_TYPE == LUA_INT_LONGLONG	/* }{ long long */
/* use presence of macro LLONG_MAX as proxy for C99 compliance */
#if defined(LLONG_MAX)		/* { */
/* use ISO C99 stuff */
#define LUA_INTEGER		long long       // 这里

可以通过改变 LUA_INT_TYPE 的宏定义,达到 lua_Integer 真正类型不同

  • LUA_INT_INTlua_Integer 被定义为 int
  • LUA_INT_LONGlua_Integer 被定义为 long
  • LUA_INT_LONGLONGlua_Integer 被定义为 long long

这些配置是在 Lua 源文件的 luaconf.h 文件中

三、检查栈空间

对于大多数情况下,栈的空间是够用的,至少有 20 个空闲位置,由 LUA_MINSTACK 进行定义,但是有时如果需要较多的空间时则需要进行检查是否够用,此时 Lua 并不会进行保证安全。

cpp 复制代码
// 在 lua.h 文件中可以看到
/* minimum Lua stack available to a C function */
#define LUA_MINSTACK	20

1、lua_checkstack

可以通过 lua_checkstack 进行检查是否够用

cpp 复制代码
int   (lua_checkstack) (lua_State *L, int n);

参数:

  • L: Lua 的状态
  • n: 需要的空间数量

返回值:

如果空间够的就返回 1 ,否则返回 0

举个例子:

cpp 复制代码
int checkSize = lua_checkstack(L, 100000000); --> 0
checkSize = lua_checkstack(L, 10);  --> 1

2、luaL_checkstack

可以使用辅助库的 luaL_checkstack 函数检查是否够用

cpp 复制代码
void (luaL_checkstack) (lua_State *L, int sz, const char *msg);

可以通过具体实现得知,其实调用的也是 lua_checkstack ,只是会在不够空间时,抛出异常同时终止执行

cpp 复制代码
LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) {
  if (l_unlikely(!lua_checkstack(L, space))) {
    if (msg)
      luaL_error(L, "stack overflow (%s)", msg);
    else
      luaL_error(L, "stack overflow");
  }
}

举个例子:

cpp 复制代码
// 会抛出异常 PANIC: unprotected error in call to Lua API (stack overflow (Not enough space.))
luaL_checkstack(L, 100000000, "Not enough space.");


// 正常运行
luaL_checkstack(L, 10, "Not enough space.");

四、查询元素

1、栈索引

Lua 栈的索引有两种方式:

  • 第一种是从栈底往上开始计数,起始下标是 1 ,往上一个元素则加 1
  • 第二种是从栈顶往下开始技术,起始下标是 -1 ,往下一个元素则减 1

举个例子:

如果栈中已经有四个元素,则栈的元素下标如下图所示

1、判断栈中元素类型

C API 描述
int (lua_isnumber) (lua_State *L, int idx); 判断是否为数值
int (lua_isstring) (lua_State *L, int idx); 判断是否为字符串(数值也会被认为是字符串
int (lua_iscfunction) (lua_State *L, int idx); 判断是否为 C 函数
int (lua_isinteger) (lua_State *L, int idx); 判断是否为整型数
int (lua_isuserdata) (lua_State *L, int idx); 判断是否为用户数据
int (lua_type) (lua_State *L, int idx); 获取元素类型
const char *(lua_typename) (lua_State *L, int tp); 获取类型名称

2、lua_type 函数

lua_type 函数返回栈中元素类型,每一种类型都有一个对应的常量:

cpp 复制代码
#define LUA_TNIL		0
#define LUA_TBOOLEAN		1
#define LUA_TLIGHTUSERDATA	2
#define LUA_TNUMBER		3
#define LUA_TSTRING		4
#define LUA_TTABLE		5
#define LUA_TFUNCTION		6
#define LUA_TUSERDATA		7
#define LUA_TTHREAD		8

3、从栈中获取值

C API 描述
lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); 获取指定索引的值,转为浮点数
lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); 获取指定索引的值,转为整数型
int (lua_toboolean) (lua_State *L, int idx); 获取指定索引的值,转为布尔值
const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); 获取指定索引的值,转为字符串,len 指针会返回该字符串的长度
lua_Unsigned (lua_rawlen) (lua_State *L, int idx); 获取指定索引的值原始长度,不进行元方法的调用。 1. 对于字符串类型(string),将返回字符串的字节数。 2. 对于表类型(table),将返回表的元素个数,即键值对的数量。 3. 对于用户数据类型(userdata),将回用户数据占用的字节数。 4. 对于其他类型的值,将返回 0。
lua_CFunction (lua_tocfunction) (lua_State *L, int idx); 获取指定索引的值,转为 C 函数指针
void *(lua_touserdata) (lua_State *L, int idx); 获取指定索引的值,转为用户数据
lua_State *(lua_tothread) (lua_State *L, int idx); 获取指定索引的值,转为 lua_State 的指针
const void *(lua_topointer) (lua_State *L, int idx); 获取指定索引的值,转为一个指针

3-1、错误处理

从 "栈中获取值的函数" 即使指定的元素类型不正确,也不会抛出异常。

C API 错误表现
lua_tonumberx 索引的内容非数值,则返回 0 ,isnum 为 0
lua_tointegerx 索引的内容非整数,则返回 0 ,isnum 为 0
lua_toboolean 索引的内容为 nil 和 false 则转为 0 ,所有其他 Lua 值都转为 1 (值得注意的是数值 0 也会转为 true
lua_tolstring 索引的内容非字符串,则返回 NULL
lua_rawlen 从上一个表可以知道,如果类型不符合则返回 0
lua_tocfunction 索引的内容非 C 函数,则返回 NULL
lua_touserdata 索引的内容非用户数据,则返回 NULL
lua_tothread 索引的内容非 lua_State 指针,则返回 NULL
lua_topointer 索引的内容非指针,则返回 NULL

举些例子

继续延用第二节中的栈内容,下面的代码都是用到索引为 1 的栈元素。此时索引为 1 的栈元素是 nil 。

cpp 复制代码
int isNum = false;
printf("lua_tonumberx(L, -1,&isNum) --> %f\n", lua_tonumberx(L, 1, &isNum));        // lua_tonumberx(L, -1,&isNum) --> 0.000000
printf("lua_tointegerx(L, -1,&isNum) --> %llu\n", lua_tointegerx(L, 1, &isNum));    // lua_tointegerx(L, -1,&isNum) --> 0
printf("lua_toboolean(L, -1) --> %d\n", lua_toboolean(L, 1));                       // lua_toboolean(L, -1) --> 0

size_t length = 0;
printf("lua_tolstring(L, -1, &length) == nullptr --> %d\n", lua_tolstring(L, 1, &length) == nullptr);   // lua_tolstring(L, -1, &length) == nullptr --> 1
printf("lua_rawlen(L, -1) --> %llu\n", lua_rawlen(L, 1));                           // lua_rawlen(L, -1) --> 0

printf("lua_tocfunction(L, -1)) == nullptr --> %d\n", lua_tocfunction(L, 1) == nullptr);        // lua_tocfunction(L, -1)) == nullptr --> 1
printf("lua_touserdata(L, -1) == nullptr --> %d\n", lua_touserdata(L, 1) == nullptr);           // lua_touserdata(L, -1) == nullptr --> 1
printf("lua_tothread(L, -1) == nullptr --> %d\n", lua_tothread(L, 1) == nullptr);               // lua_tothread(L, -1) == nullptr --> 1
printf("lua_topointer(L, -1) == nullptr --> %d\n", lua_topointer(L, 1) == nullptr);             // lua_topointer(L, -1) == nullptr --> 1

3-2、lua_len 和 lua_rawlen

cpp 复制代码
void  (lua_len)    (lua_State *L, int idx);  

lua_len 会获取指定索引的值长度,会调用到元方法( lua_rawlen 则不会调用元方法 )。会根据给定索引处的值类型执行不同的操作:

  • 如果值是一个字符串 string 或表 table ,它会返回字符串的长度或表中元素的个数。
  • 如果值是一个用户数据 userdata ,它会尝试调用元方法 __len 来获取长度。

值得注意:

  • 调用 lua_len 会在栈上产生一个新的整数值,表示获取到的长度或大小。
  • 如果值没有定义长度或大小,或者索引无效,函数将抛出一个错误。

举两个例子:

继续延用第二节中的栈内容,索引为 1 的栈元素是 nil ,索引为 -2 的栈元素是字符串 string 。

cpp 复制代码
// 如果获取错误会抛出异常,PANIC: unprotected error in call to Lua API (attempt to get length of a nil value)
lua_len(L, 1);

// 会将 -2 的内容长度压入栈
lua_len(L, -2);
// 获取栈顶数据进行打印
printf("lua_len(L, -2) --> %lld\n", lua_tointeger(L, -1));      // lua_len(L, 1) --> 46
// 将长度弹出
lua_pop(L, 1);

4、5.2 之前的数值类型转换 C API

在 5.2 之前,使用 lua_tonumber 进行获取数值无法提示错误,只会简单的返回 0 。

c++ 复制代码
#define lua_tonumber(L,i)	lua_tonumberx(L,(i),NULL)
#define lua_tointeger(L,i)	lua_tointegerx(L,(i),NULL)

5.2 之后(包括),增加了 lua_tonumberxlua_tointegerx 两个函数,进行相应的浮点数和整数转换,可以通过最后一个参数 isnum 得知是否能转换成功。

举些例子:

继续延用第二节中的栈内容,索引为 -1 的栈元素是 thread ,索引为 2 的栈元素是 0.1 。

cpp 复制代码
printf("lua_tonumber(L, -1) %f\n", lua_tonumber(L, -1));        // lua_tonumber(L, -1) 0.000000
printf("lua_tointeger(L, -1) %llu\n", lua_tointeger(L, -1));    // lua_tointeger(L, -1) 0

int isNumber = false;
printf("lua_tointegerx(L, 2, &isNumber) %lld\n", lua_tointegerx(L, 2, &isNumber));              // lua_tointegerx(L, 2, &isNumber) 0
printf("%s 转 integer 是否成功 %s\n", lua_tostring(L, 2), (isNumber == 0 ? "false" : "true"));  // 0.1 转 integer 是否成功 false

printf("lua_tonumberx(L, 2, &isNumber) %f\n", lua_tonumberx(L, 2, &isNumber));                  // lua_tonumberx(L, 2, &isNumber) 0.100000
printf("%s 转 number 是否成功 %s\n", lua_tostring(L, 2), (isNumber == 0 ? "false" : "true"));   // 0.1 转 number 是否成功 true

5、lua_tolstring

cpp 复制代码
const char     *(lua_tolstring) (lua_State *L, int idx, size_t *len);

返回的是指向该字符串内部副本的指针,并将字符串的长度存入到 len 参数中,返回的副本是不能修改的(因为是 const )

Lua 保证只要对应的字符串还在栈中,则这个指针就是有效的。当 Lua 调用的一个 C 函数返回时,Lua 就会清空栈。因此永远不要把指向 Lua 字符串的指针存放到获取该指针的函数之外。

lua_tolstring 返回的字符串会在末尾追加一个 \0 。因为字符串内容可能也含有 \0 ,所以真正的内容长度通过 len 进行才是可靠的。

如果不需要知道长度,可以传递 NULL 给 len , 当然也可以使用 lua_tostring 函数,他是一个 lua_tolstring 定义的宏。

c++ 复制代码
#define lua_tostring(L,i)	lua_tolstring(L, (i), NULL)

举个例子:

下面两个写法是等同的

cpp 复制代码
printf("lua_tolstring(L, -2, nullptr) %s\n", lua_tolstring(L, -2, nullptr));    // lua_tolstring(L, -2, nullptr) --> lua_pushfstring name: jiang peng yong, age: 29
printf("lua_tostring(L, -2) %s\n", lua_tostring(L, -2));    // lua_tostring(L, -2) --> lua_pushfstring name: jiang peng yong, age: 29

因为 lua_tostring 真正实现是 lua_tolstring ,所以异常的处理是一致的,不会抛出异常,只会返回 NULL

cpp 复制代码
printf("lua_tostring(L, -1) 获取非字符串内容 %s\n", lua_tostring(L, -1));   // lua_tostring(L, -1) 获取非字符串内容 --> (null)

五、栈操作

C API 描述
int (lua_absindex) (lua_State *L, int idx); 用于将栈索引转换为绝对值栈索引 1. 如果给定的索引 >= 0,则返回相同的值。 2. 如果给定的索引 < 0,则会将索引转换为从栈底计算的正索引值。
int (lua_gettop) (lua_State *L); 获取栈中元素的个数
void (lua_settop) (lua_State *L, int idx); 设置栈中元素的个数,如果之前的栈顶比新设置的值更高,则会将高出的元素丢弃;反之,该函数会向栈压入 nil 进行补足大小
void (lua_pushvalue) (lua_State *L, int idx); 将指定索引上的元素的副本压入栈
void (lua_rotate) (lua_State *L, int idx, int n); 将指定索引上的元素转动 n 个位置,正数表示向栈顶方向转动,负数表示向栈底方向转动
void (lua_copy) (lua_State *L, int fromidx, int toidx); 将索引上的值复制到另一个索引上,并且原值不受影响
lua_insert(L,idx) 将栈顶元素移动到指定位置,并上移指定位置之上的所有元素以开辟一个元素空间
lua_remove(L,idx) 删除指定索引的元素,并将该元素之上的所有元素下移以填充空缺
lua_replace(L,idx) 将栈顶元素设置到指定索引上的值,并将栈顶元素弹出
lua_pop(L,n) 从栈中弹出 n 个元素

使用以下代码填充栈内容

cpp 复制代码
lua_pushnumber(L, 10.8);
lua_pushstring(L, "jiang peng yong");
lua_pushnil(L);
lua_pushinteger(L, 29);

填充后栈内容如下,下面的例子就基于这一个栈进行演示

cpp 复制代码
栈顶
^ typename: number, value(integer): 29    
^ typename: nil, value: nil    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(number): 10.8    
栈底

1、lua_absindex

用于将栈索引转换为绝对值栈索引

  1. 如果给定的索引 >= 0,则返回相同的值。
  2. 如果给定的索引 < 0,则会将索引转换为从栈底计算的正索引值。
cpp 复制代码
printf("lua_absindex(L, -1) --> %d\n", lua_absindex(L, -1));        // lua_absindex(L, -1) --> 4
printf("lua_absindex(L, 1) --> %d\n", lua_absindex(L, 1));          // lua_absindex(L, 1) --> 1

2、lua_pushvalue

将指定索引上的元素的副本压入栈

cpp 复制代码
lua_pushvalue(L, -3);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(integer): 29    
^ typename: nil, value: nil    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(number): 10.8    
栈底

3、lua_rotate

将指定索引上的元素转动 n 个位置,正数表示向栈顶方向转动,负数表示向栈底方向转动

n 为正数

cpp 复制代码
lua_rotate(L, 3, 1);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: number, value(integer): 29    
^ typename: nil, value: nil    
^ typename: string, value: 'jiang peng yong'    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(number): 10.8    
栈底

n 为负数

cpp 复制代码
lua_rotate(L, 3, -2);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: nil, value: nil    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(integer): 29    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(number): 10.8    
栈底

一图胜千言

4、lua_copy

将索引上的值复制到另一个索引上,并且原值不受影响

cpp 复制代码
lua_copy(L, 1, -1);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: number, value(number): 10.8    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(integer): 29    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(number): 10.8    
栈底

5、lua_insert(L,idx)

将栈顶元素移动到指定位置,并上移指定位置之上的所有元素以开辟一个元素空间

lua_insert 是一个宏定义,真正实现是通过 lua_rotate

cpp 复制代码
// lua_rotate(L, (idx), 1) 将索引为 idx 的元素向栈顶移动一个位置,从而达到栈顶元素移动到指定位置
#define lua_insert(L,idx)	lua_rotate(L, (idx), 1)
cpp 复制代码
lua_insert(L, 2);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(integer): 29    
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(number): 10.8    
^ typename: number, value(number): 10.8    
栈底  

6、lua_remove(L,idx)

删除指定索引的元素,并将该元素之上的所有元素下移以填充空缺

lua_remove 也是一个宏定义,真正实现是通过 lua_rotatelua_pop

cpp 复制代码
// 主要通过两步进行实现
// 1、lua_rotate(L, (idx), -1) 将 idx 向栈底移动一个位置(就是移动到栈顶)
// 2、lua_pop(L, 1) 将栈顶弹出
#define lua_remove(L,idx)	(lua_rotate(L, (idx), -1), lua_pop(L, 1))
cpp 复制代码
lua_remove(L, -3);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: string, value: 'jiang peng yong'    
^ typename: number, value(integer): 29    
^ typename: number, value(number): 10.8    
^ typename: number, value(number): 10.8    
栈底   

7、lua_replace(L,idx)

将栈顶元素设置到指定索引上的值,并将栈顶元素弹出

其实这是一个宏定义,真正实现的是通过 lua_copylua_pop

cpp 复制代码
// 分为两步:
// 1、lua_copy(L, -1, (idx)) 将栈顶元素设置到索引为 idx 的位置
// 2、lua_pop(L, 1) 从栈中弹出 1 个元素,就是将栈顶元素弹出
#define lua_replace(L,idx)	(lua_copy(L, -1, (idx)), lua_pop(L, 1))
cpp 复制代码
lua_replace(L, 1);

操作后栈变为:

cpp 复制代码
// 栈顶
栈顶
^ typename: number, value(integer): 29    
^ typename: number, value(number): 10.8    
^ typename: string, value: 'jiang peng yong'    
栈底 
// 栈底

8、lua_pop

从栈中弹出 n 个元素

lua_pop 也是一个宏定义,真正实现是通过 lua_settop

cpp 复制代码
// lua_settop(L, -(n)-1) 通过 lua_settop 设置栈个数,利用负数达到从栈顶往下计算索引
#define lua_pop(L,n)		lua_settop(L, -(n)-1)
cpp 复制代码
lua_pop(L, 1);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: number, value(number): 10.8    
^ typename: string, value: 'jiang peng yong'    
栈底

9、lua_gettop

获取栈的元素个数

cpp 复制代码
printf("lua_gettop(L) --> %d\n", lua_gettop(L));        --> lua_gettop(L) --> 2

10、lua_settop

设置栈的个数,如果之前栈顶比新设置的值高,则会将高出的元素丢弃;反之,该函数会向栈压入 nil 进行补足

当设置的比原先的栈个数大,则会使用 nil 在栈顶进行补充

cpp 复制代码
lua_settop(L, 5);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: nil, value: nil    
^ typename: nil, value: nil    
^ typename: number, value(integer): 29    
^ typename: number, value(number): 10.8    
^ typename: string, value: 'jiang peng yong'    
栈底

当设置的比原先的栈个数小,则会舍弃栈顶的值

cpp 复制代码
lua_settop(L, 3);

操作后栈变为:

cpp 复制代码
栈顶
^ typename: number, value(integer): 29    
^ typename: number, value(number): 10.8    
^ typename: string, value: 'jiang peng yong'    
栈底

可以使用 lua_settop(L, 0); 清空栈

六、写在最后

Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)

如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀

公众号搜索 "江澎涌",更多优质文章会第一时间分享与你。

相关推荐
奶香臭豆腐12 分钟前
C++ —— 模板类具体化
开发语言·c++·学习
不想当程序猿_18 分钟前
【蓝桥杯每日一题】分糖果——DFS
c++·算法·蓝桥杯·深度优先
graceyun20 分钟前
C语言初阶习题【9】数9的个数
c语言·开发语言
cdut_suye29 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
波音彬要多做1 小时前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
捕鲸叉1 小时前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
Schwertlilien2 小时前
图像处理-Ch5-图像复原与重建
c语言·开发语言·机器学习
只做开心事2 小时前
C++之红黑树模拟实现
开发语言·c++
程序员buddha3 小时前
C语言从入门到放弃教程
c语言·开发语言
程序员老冯头3 小时前
第十五章 C++ 数组
开发语言·c++·算法