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

news2024/11/16 13:24:47

一、栈

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 ,进行相应数据类型的压栈

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 的结合使用

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
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");
}

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

栈顶
^ 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 的源码默认的宏定义可以证明

#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 位整型)

#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 并不会进行保证安全。

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

1、lua_checkstack

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

int   (lua_checkstack) (lua_State *L, int n);

参数:

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

返回值:

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

举个例子:

int checkSize = lua_checkstack(L, 100000000); --> 0
checkSize = lua_checkstack(L, 10);  --> 1

2、luaL_checkstack

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

void (luaL_checkstack) (lua_State *L, int sz, const char *msg);

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

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");
  }
}

举个例子:

// 会抛出异常 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 函数返回栈中元素类型,每一种类型都有一个对应的常量:

#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 。

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
void  (lua_len)    (lua_State *L, int idx);  

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

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

值得注意:

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

举两个例子:

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

// 如果获取错误会抛出异常,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 。

#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 。

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

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 定义的宏。

#define lua_tostring(L,i)	lua_tolstring(L, (i), NULL)

举个例子:

下面两个写法是等同的

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

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 个元素

使用以下代码填充栈内容

lua_pushnumber(L, 10.8);
lua_pushstring(L, "jiang peng yong");
lua_pushnil(L);
lua_pushinteger(L, 29);

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

栈顶
^ 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,则会将索引转换为从栈底计算的正索引值。
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

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

lua_pushvalue(L, -3);

操作后栈变为:

栈顶
^ 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 为正数

lua_rotate(L, 3, 1);

操作后栈变为:

栈顶
^ 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 为负数

lua_rotate(L, 3, -2);

操作后栈变为:

栈顶
^ 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

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

lua_copy(L, 1, -1);

操作后栈变为:

栈顶
^ 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

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

操作后栈变为:

栈顶
^ 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

// 主要通过两步进行实现
// 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))
lua_remove(L, -3);

操作后栈变为:

栈顶
^ 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

// 分为两步:
// 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))
lua_replace(L, 1);

操作后栈变为:

// 栈顶
栈顶
^ 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

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

操作后栈变为:

栈顶
^ typename: number, value(number): 10.8    
^ typename: string, value: 'jiang peng yong'    
栈底

9、lua_gettop

获取栈的元素个数

printf("lua_gettop(L) --> %d\n", lua_gettop(L));        --> lua_gettop(L) --> 2

10、lua_settop

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

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

lua_settop(L, 5);

操作后栈变为:

栈顶
^ 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'    
栈底

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

lua_settop(L, 3);

操作后栈变为:

栈顶
^ typename: number, value(integer): 29    
^ typename: number, value(number): 10.8    
^ typename: string, value: 'jiang peng yong'    
栈底

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

六、写在最后

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1106719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【周末闲谈】VR新视界,“眼”见未来

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 系列目录 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 系列目录前言虚拟现实(VR)技术虚拟现实技术的原理虚拟现实技术发…

v-model表单数据双向绑定-表单提交示例

示例如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>v-model表单数据双向绑定<…

【Note】CNN与现代卷积神经网络part3(附PyTorch代码)

文章目录 2 现代卷积神经网络2.1 批量规范化2.1.1 训练深层网络2.1.2 批量规范化层2.1.2.1 全连接层2.1.2.2 卷积层2.1.2.3 预测过程中的批量规范化 2.1.3 从零实现2.1.4 使用批量规范化层的 LeNet2.1.5 简明实现2.1.6 controversies&#xff08;争议&#xff09;2.1.7 Summary…

YOLOv7改进:新机制,扩展DCNv3,基于DCNv2优化 | CVPR2023 InternImage

💡💡💡本文属于原创独家改进:DCNv3优势:1) 共享投射权重;2) 引入多组机制;3)采样点调制标量归一化; DCNv3 | 亲测在多个数据集实现暴力涨点; 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 🚀🚀🚀YOL…

Flink学习之旅:(一)Flink部署安装

1.本地搭建 1.1.下载Flink 进入Flink官网&#xff0c;点击Downloads 往下滑动就可以看到 Flink 的所有版本了&#xff0c;看自己需要什么版本点击下载即可。 1.2.上传解压 上传至服务器&#xff0c;进行解压 tar -zxvf flink-1.17.1-bin-scala_2.12.tgz -C ../module/ 1.3.启…

随机专享记录第一话 -- RustDesk的自我搭建和使用

1.介绍 RustDesk是继TeamView、向日葵等远程桌面软件后的新起之秀,最主要的是开源的可自己搭建中继服务。相比于公共服务器,连接一次等待的时间要多久,用过TeamView的都知道,而且还是免费的,不像某些远程搞各种个人证书,各种登录设备限制! 先看看软件图,这是待连接界…

14.11 Socket 基于时间加密通信

在之前的代码中我们并没有对套接字进行加密&#xff0c;在未加密状态下我们所有的通信内容都是明文传输的&#xff0c;这种方式在学习时可以使用但在真正的开发环境中必须要对数据包进行加密&#xff0c;此处笔者将演示一种基于时间的加密方法&#xff0c;该加密方法的优势是数…

微信小程序获取手机号(2023年10月 python版)[无需订阅]

技术栈&#xff1a; 1. 微信开发者工具中的调试基础库版本&#xff1a;3.1.2。 2. 后台&#xff1a;django。 步骤&#xff1a; 1. 首先在后台django项目的定时任务中增加一个下载access_token函数&#xff0c;并把得到的access_token保存在数据库中&#xff08;其实随便哪里…

SpringCloud学习笔记-gateway网关自定义全局过滤器

需求&#xff1a;定义全局过滤器&#xff0c;拦截请求&#xff0c;判断请求的参数是否满足下面条件&#xff1a; 参数中是否有authorization&#xff0c; authorization参数值是否为admin 如果同时满足则放行&#xff0c;否则拦截 实现&#xff1a; 在gateway中定义一个过…

C语言实现通讯录(超详细)

1.实现怎样一个通讯录 实现一个通讯录联系人信息&#xff1a;1.可以保存100个人的信息名字2.添加联系人年龄3.删除指定联系人性别4.查找指定联系人电话5.修改指定联系人住址6.排序联系人7.显示所有联系人信息 2.通讯录的实现 2.1创建两个源文件和一个头文件 首先我们创建con…

C++DAY47

头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QLabel> #include <QLineEdit> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public…

数据结构----算法--五大基本算法(这里没有写分支限界法)和银行家算法

数据结构----算法–五大基本算法&#xff08;这里没有写分支限界法&#xff09;和银行家算法 一.贪心算法 1.什么是贪心算法 在有多个选择的时候不考虑长远的情况&#xff0c;只考虑眼前的这一步&#xff0c;在眼前这一步选择当前的最好的方案 二.分治法 1.分治的概念 分…

6-8 舞伴问题 分数 15

void DancePartner(DataType dancer[], int num) {LinkQueue maleQueue SetNullQueue_Link();LinkQueue femaleQueue SetNullQueue_Link();// 将男士和女士的信息分别加入对应的队列for (int i 0; i < num; i) {if (dancer[i].sex M){EnQueue_link(maleQueue, dancer[i]…

Vue跨域配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 配置详情 请在项目的vue.config.js中通过proxy的配置&#xff0c;解决Vue跨域&#xff1b;代码如下&#xff1a; const { defineConfig } require(vue/cli-service) modu…

用JavaScript输出0-9的两种方法、以及setTimeout的三个参数的意义

方法一&#xff1a; for (let i 0; i < 10; i) {setTimeout(() > {console.log(i);}, 1000) } 方法二&#xff1a;利用 setTimeout 函数的第三个参数&#xff0c;会作为回调函数的第一个参数传入 for (var i 0; i < 10; i) {setTimeout(i > {console.log(i);…

canvas画布中beginPath和closePath的作用要点

1.画笔画完一个图案后&#xff0c;立即又回到了原点&#xff0c;所以下次如果没有beginPath&#xff0c;就会从原点重新开始 2.beginPath相当于让画笔从原点抬起&#xff0c;重新开始一个新路径&#xff0c;不重复走之前的路径&#xff0c;所以不会覆盖之前的路径 3.closePat…

通过IP地址查询避免电子招投标串标的方法

随着电子招投标的广泛应用&#xff0c;诚实和公平的竞争变得至关重要。然而&#xff0c;一些不道德的投标者可能试图串通以获取不正当的竞争优势。为了解决这个问题&#xff0c;可以利用IP地址查控技术&#xff0c;确保电子招投标的公平性和透明性。本文将介绍如何通过IP地址查…

XCode15与iOS17/17.1 真机测试问题处理

XCode15与iOS17/17.1 真机测试问题处理&#xff0c;网上相关博客很多&#xff0c;摘录了如下实践后能起作用的地址如下&#xff1a;Xcode 15 报错处理 - 简书iOS17版本适配-CSDN博客 Xcode15适配-六虎 主要介绍下&#xff1a;Assertion failure in void _UIGraphicsBeginImag…

【AI视野·今日Sound 声学论文速览 第二十六期】Mon, 16 Oct 2023

AI视野今日CS.Sound 声学论文速览 Mon, 16 Oct 2023 Totally 7 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers Low-latency Speech Enhancement via Speech Token Generation Authors Huaying Xue, Xiulian Peng, Yan Lu现有的基于深度学习的语音增强…

VirtualBox安装时提示失败(未解决)

似乎是升级6.1.48的时候&#xff0c;提示签名有问题。重启后就这样了。 卸载1&#xff1a; 卸载2 修复式安装