您当前的位置: 首页 >  游戏
  • 1浏览

    0关注

    322博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【游戏客户端与服务器面试题】-- 2022年最新游戏客户端与服务器面试(lua篇持续更新)

森明帮大于黑虎帮 发布时间:2022-05-19 16:13:59 ,浏览量:1

【游戏客户端与服务器面试题干货】-- 2022年度最新游戏客户端面试干货(lua篇)

文章目录
  • 一、Lua的8种数据类型
      • (1) nil 类型
      • (2) boolean类型
      • (3) number类型
        • 1.加+,减-,乘*:
        • 2.除/:
        • 3.双除法 // :
        • 4.取整:
        • 5.强制类型转换
        • 6.取随机数:
        • 7.表示范围
      • (4) function类型
        • 1.多返回值
        • 2.可变长参数
      • (5) string类型
        • 1.字符串常量
        • 2.长字符串/多行字符串
        • 3.类型强制转换
        • 4.字符串常用操作
        • 5.字符串标准库
      • (6) table类型
        • 1.lua中深拷贝与浅拷贝
        • 2.lua中深拷贝与浅拷贝源码
        • 3.表索引
        • 4.表构造器
        • 5.数组,列表和序列
        • 6.遍历表
        • 7.表标准库
      • (7) userdata类型
        • 1.实例lua调用capi
          • 1.看两个小栗子
            • 1.创建lua_Stack
            • 2.第一个栗子:c++调用lua的函数
            • 3.第二个栗子:lua调用c++的函数
          • 1.分析这两个栗子
      • (8) thread类型
        • 1.C/C++与lua的交互方式
        • 2.lua_Stack究竟由什么组成
          • 1.CommonHeader -- GC的通用头
          • 2. nci -- 记录调用栈item个数的变量
          • 3.l_G -- 全局状态机,维护全局字符串表、内存管理函数、gc等信息
            • 1.什么是全局状态机global_State
            • 2.全局状态机global_State的组成
            • 3.全局状态机global_State初始化过程
          • 4.StkId -- 数据栈:
          • 5. CallInfo -- 调用栈
          • 6. HOOK 相关-- 服务于debug模块
            • 1.首先什么是debug库
            • 2.自省(introspective)函数
            • 3.introspective自省debug.getinfo函数
            • 3.1.栈级别(stack level)
            • 3.2.提高调用getInfo的效率
            • 4.introspective自省debug.getlocal函数
            • 5.Hooks钩子
          • 7.GC 垃圾回收
  • 二、pairs和ipairs的区别
  • 三、lua表常用方式(插入,删除,移动,排序)
  • 四、如何实现继承关系(__index)
      • 1.面向对象编程--语法糖
      • 2.面向对象编程--类的实现与继承
        • 1.定义一个类:
        • 2.继承
      • 3.__index元方法
  • 五、__newindex元方法
      • 1.__nexindex指向一个函数
      • 2.__nexindex指向一个表
  • 六、实现一个常量表
  • 七、__call元方法
  • 八、__tostring元方法
  • 九、lua元方法
      • 1.__index元方法
      • 2.__newindex元方法
        • 1.__nexindex指向一个函数
        • 2.__nexindex指向一个表
      • 3.__call元方法
      • 4.__tostring元方法
      • 5.__le,__eq, __lt,__add, __pow,__mod
  • 十 、lua闭包
      • 1.闭包的概念
      • 2.闭包的应用
      • 3.闭包的实现原理
  • 十一、类使用:和.的区别
  • 十二、require,loadfile和dofile的区别
  • 十三、Lua的热更新原理
      • 1.什么是热更新
      • 2.热更新机制应运而生,两种实现方式
        • 1.简单版但是有缺陷
        • 2.复杂版但是很有用
      • 2.hotfix 实现了Lua热更新,仅对函数作替换,保留旧数据
      • 3.热更新进一步介绍
        • 1.原理
        • 2.要点分析
        • 3.Lua热更新的实现
        • 4._ENV 环境变量
        • 5.上值 upvalue
        • 6.热更新函数Lua的require函数
        • 7.热更新函数Lua的setenv函数
        • 8.热更新函数Lua的debug库函数
        • 9.深度递归替换所有的upvalue
        • 10.实例分析
      • 4.Lua脚本热更新方案
      • 5.lua热更新
        • 1.什么是热更新
        • 2.热更新原理第一种:
        • 3.热更新原理第二种:
  • 十四、Lua协同程序
      • 1.lua协同程序初阶
      • 2.lua协同程序进阶
        • 1.什么是协程
        • 2.协程&线程的作用
        • 3.lua中协程的调用
          • 1.coroutine.create()创建协程
          • 2.coroutine.status()协程状态
          • 3.resume和yeild
        • 4.通过协程实现异步I/O
  • 十五、Lua垃圾回收机制
      • 1.mark阶段
      • 2.cleaning阶段
      • 3.sweep阶段
      • 4.finalization析构
      • 5.缺陷
  • 十六、Lua和C相互调用
      • 1.实例lua调用capi
        • 1.看两个小栗子
          • 1.创建lua_Stack
          • 2.第一个栗子:c++调用lua的函数
          • 3.第二个栗子:lua调用c++的函数
        • 1.分析这两个栗子
  • 十七、Lua的一些实例测试
      • (1) 字符串把“abc”,换成“bcd”
      • (2) if的使用方法
      • (3) 如何对表中元素排序后输出
      • (4) 写一个返回多值的函数
      • (5) 写一个可变参数得函数
  • 十八、lua的命名规范以及注释方法
      • 1.lua命名规范
      • 2.lua注释方式
  • 十九、lua条件与循环
      • 1.条件选择语句if
      • 2. while循环语句
      • 3. repeat..until循环语句
      • 4. for循环语句
      • 5.提前结束循环
  • 二十、lua代码优化,别再依赖if..else了
  • 二十一、lua数值型for和泛型for
      • 1.数值型for
      • 2.泛型for
  • 二十二、lua模式匹配
      • 1.string.find()
      • 2.string.match()
      • 3.string.gmatch()
      • 4.string.gsub()
  • 二十三、lua模式匹配练习
  • 二十四、lua之数据结构(多维数组,链表,队列)
      • 1.数组
        • 1.一维数组
        • 2.二维数组
      • 2.链表
      • 3.栈和队列
  • 二十五、rawset & rawget方法
      • 1.什么是rawset & rawget
  • 二十六、lua环境ENV
      • 1.全局环境_G
      • 2.非全局环境_ENV

一、Lua的8种数据类型

在Lua中有8种基本类型,分别是:nil–空,boolean–布尔,number–数值,string–字符串,userdata–用户数据,function–函数,thread–线程(注意这里的线程和操作系统的线程完全不同,lua和c/c++进行交互的lua_Stack就是一种llua的线程类型),和table–表。

我们可以通过调用print(type(变量))来查看变量的数据类型。 在这里插入图片描述

(1) nil 类型

nil是一种只有一个nil值的类型,它的主要作用是与其他所有值进行区分。Lua语言使用nil值来表示没有有用的值的情况。全局变量第一次被赋值前的默认值就是nil,将nil赋值给全局变量相当于将其删除。

(2) boolean类型

boolean类型具有两个值,true和false,他们分别代表了传统的布尔值。敲黑板:

不过在Lua中,任何值都能表示条件:Lua定义除了false和nil的值为假之外,所有的值都为真,包括0和空字符串。

提到布尔值就不得不提一下逻辑运算符:and,or,not 他们都遵循着短路求值。

举个栗子:

首先,对于and来说,如果第一个值为假,则返回第一个值,否则返回第二个值: 在这里插入图片描述 对于or来说,如果第一个值为真,则返回第一个值,否则返回第二个值: 在这里插入图片描述 对于not来说,返回值永远为Boolean: 在这里插入图片描述 通过上述对逻辑运算符的理解,我们用这种写法来代替简单的if else,让代码变得更优雅

if a + b > 0 then
    c = 1
else
    c = 10
end
 
-------------- 等同于 ---------------
 
c = a + b > 0 and 1 or 10
(3) number类型

在Lua5.2之前所有的数值都是双精度的浮点型,在Lua5.3之后引入了整形integer。整形的引入是Lua5.3的一个重要标志。

整形与浮点型的区别: 整形:类似1,2,3,4,5…在其身后不带小数和指数。 浮点型:类似1.2,3.5555,4.57e-3…在其身后带小数点或指数符号(e)的数字。 我们使用type(3) 和type(3.5)返回的都是num。 但是如果我们调用math库里面的math.type(3)返回的是integer, math.type(3.5)返回的是float。

对于游戏开发,对num类型的使用无非是以下的状况, Lua语言还提供了除了加减乘除之外,向下取整除法(floor除法),取模和指数运算。

1.加+,减-,乘*:

int对int型进行运算,则得到的结果都是int型,但只要两个数字中有一个是float型,则得出来的结果都是float型。 在这里插入图片描述

2.除/:

无论是什么数字,得到的结果永远都是float类型。 在这里插入图片描述 那如果我硬是想要直接一步到位,除出来的结果也要是整形怎么办?

3.双除法 // :

得到的是一个整值,若结果存在小数,则向负无穷截断。 在这里插入图片描述 除了加减乘除之外,使用得最多的就是取整和取随机数了。

4.取整:
  • floor()–向负无穷取整
  • ceil() – 向正无穷取整
  • modf()–向0取整
  • floor(x+0.5)–四舍五入

number类型的取整,返回结果为整形值:

  • (1)floor()–向负无穷取整:floor(1.5)=1
  • (2)ceil() – 向正无穷取整:ceil(1.5)=2
  • (3)modf()–向0取整:modf(1.5)=1.modf(-1.5)=-1
  • (4)floor(x+0.5)–四舍五入
5.强制类型转换

number类型的取整以及强制转换。

  • 整数类型转化成浮点型:+0.0
  • 浮点类型转化成整形:math.tointeger()
6.取随机数:

产生随机数(math.random()):

  • Lua中产生随机数的三种方式:
  • math.random()-- 产生(0~1)的随机值
  • math.random(n)-- 产生(1~n)的随机值
  • math.random(m,n)-- 产生(m~n)的随机值
7.表示范围

最大值math.maxinteger和最小值math.mininteger。

lua中int型和float型都是使用8个字节来存储的,所以他们有最大值和最小值存在。

当对int最大值加整数1时,会发生回滚,如:

math.maxinteger+1=math.mininteger

math.mininteger-1=math.maxinteger

但是如果当他们加的是浮点型数字时,就不会发生回滚,而是取近似值。

math.maxinteger+1.0=math.maxinteger

math.mininteger-1.0=math.mininteger

(4) function类型

在Lua语言中,函数(Function)是对语句和表达式进行抽象的一种方式。函数调用时都需要使用一对圆括号把参数列表括起来。几时被调用的函数不需要参数,也需要一堆空括号()。唯一的例外是,当函数只有一个参数且该参数是字符串常量或表构造器{}时,括号是可选的。

print "Hello World"  --  相等于print(“Hello World”)
type {} -- 相等于type({})

正如我们已经在其他示例中看到的一样,Lua语言中的函数定义的常见语法格式如下,举个例子:

function add(a)               -- 声明add这个函数
    local sum = 0             -- 实现序列a的求和
    for i=1, #a do            -- 循环体
        sum = sum + a[i]
    end
    return sum                -- 返回值
end

在这种语法中,一个函数定义具有一个函数名(name,本例中的add),一个参数组成的列表和由一组语句组成的函数体。参数的行为与局部变量的行为完全一致,相当于一个用函数调用时进行初始化的局部变量。

调用函数时,使用的参数个数与定义函数时使用的参数不一致。Lua语言会通过抛弃多余的参数以及将不足的参数设为nil的方式来调整参数的个数。 在这里插入图片描述

这是我们类C的写法,function 函数名 小括号 参数, 但其实我们还有另外一种写法,把函数当成一个对象去定义:

两种方式都可以声明一个函数,至于使用哪一种方式,就根据贵公司项目而定了。

lua的函数类型除了可以把它当成对象这样定义之外,还有两个特性:可变长参数,以及多返回值。

1.多返回值
  • Lua语言中一种与众不同但又非常有用的特性是允许一个函数返回多个结果,只需要在return关键字后列出所有要返回的值即可。
  • 例如一个用于查找序列中最大元素的函数可以同时返回最大值以及该元素的位置: 在这里插入图片描述 当函数作为 一条单独语句使用时,其所有值均会被抛弃。当函数被作为 表达式(例如加法操作数)调用时,将 只保留第一个返回值。
function foo () 
  return "a","b"
end
 
x,y = foo()  -- x="a",y="b"
x = foo()    -- x="a"
x,y,z=foo()  -- x="a",y="b",z=nil
2.可变长参数
  • Lua语言中的函数可以是可变长参数函数(variadic),即可以支持数量可变的参数, 只需要在函数声明的时候参数项用…代替即可。
  • 下面是一个简单的示例,该函数返回所有参数的总和: 在这里插入图片描述 参数列表中的三个点表示该函数的参数是可变长的。当这个函数被调用时,Lua内容会把它的所有参数收集起来,三个点是作为一个表达式来使用的。在上例中,表达式{…}的结果是一个由所有可变长参数组成的列表,该函数会遍历该列表来累加。
-- 我们可以通过以下这几种方式进行对变化参数的调用
 
local Array{...} -- 把它变成一个表
 
#{...} -- 取得当前变化参数的个数
 
select{i,...} -- 通过select方法取得第i个变化参数的值 
(5) string类型

Lua中的字符串是不可变值(immutable value)。我们不可以像在C语言中那样直接改变某个字符串中的某个字符。但是我们可以创建另外一个新字符串的方式来达到修改的目的。 在这里插入图片描述 可以使用来获取字符串的长度。 在这里插入图片描述 我们也可以用连接操作符(…)来拼接两个字符串)。但是由于Lua的字符串是不可变的,所以得到的是一个新的字符串。 在这里插入图片描述

1.字符串常量

我们可以使用双引号或者单引号来声明字符串常量。

a = "a line"
b = ‘another line’

那么如果在字符串内容中出现双引号或者单引号怎么办呢?老司机们可能就会脱口而出:用转义字符’'啊。

没错使用转义字符确实能够解决问题,但是如果是在双引号定义的字符串中出现单引号,或者单引号字符串中出现双引号则不需要使用转义字符。

  • 使用双引号声明的字符串中出现单引号时,不需要转义。
  • 同理,使用单引号声明的字符串出现双引号时,不需要转义。 在这里插入图片描述
2.长字符串/多行字符串

为了方便缩进排版,所以Lua定义了用户可以使用一对方括号 [[]] 来声明长字符串。被方括号扩起来的内容可以由很多行,并且内容中的转义序列不会被转义。 在这里插入图片描述 同时,为了避免出现像这种情况:

array[b[10]]   -- 出现了两个]]

我们还可以在声明长字符串时在两个中括号之间加入等量的=号,如:

array[==[ 
      123456    -- 这样lua也会自动识别它是一个长的字符串
     ]==]
3.类型强制转换

当Lua语言发现在需要字符串的地方出现数字时,它会自动把数值转换为字符串。 在这里插入图片描述 但是假如我们需要 1 … 2 想输出“12”的化话,那么数字和…连接符之间需要带空格,避免系统把它当成小数点。

当在算数运算中发现字符串时,它会转化为浮点型数值再进行计算,要注意在比较操作中不会默认转化。比如下图中的a和b是字符串,但是相加的时候则转化成数字: 在这里插入图片描述

当然我们也可以显式的把字符串和数值相互转换:tostring()-- 返回字符串/ tonumber () --返回整形或浮点型数值。 在这里插入图片描述

4.字符串常用操作
  • (1) 字符串拼接: …(两个点)
a = “hello”

b = "world"

c = a..b    -- 此时c等于hello world
  • (2) 取字符串长度
c = “hello world”

print (#c)    -- 此时输出11
5.字符串标准库

Lua本身对字符串的处理十分有限,仅能够创建,拼接,取长度和比较字符串。

所以Lua处理字符串的完整能力来自字符串的标准库。 在这里插入图片描述 诶!怎么没有得到想要的结果呢?原来是忘记了Lua中字符串是不可变的这定义。所以我们要看到改变后的后果,可以用一个新的字符串接住它。 在这里插入图片描述

string.gsub(stringName,“字符串一”,“字符串二”)–把字符串一改成字符串二 string.sub(stringName,起始位置,终止位置) – 返回从起始位置到终止位置的字符串 string.char(num) – 把数字通过ascall译码转化为字符 string.byte(stringName) – 把字符通过ascall译码转化为数字 string.reverse(stringName) – 把字符串翻转 string.rep(stringName, 重复的次数) – 把字符串重复N遍 string.upper(stringName) – 字符串大写 string.lower(stringName) – 字符串小写

示例图: 在这里插入图片描述 最后要给大家介绍介绍string.format(),它适用于进行字符串格式化和将数值输出为字符串的强大工具。

有点类似C中的printf()。 在这里插入图片描述

(6) table类型

表是Lua语言中最强大也是唯一的数据结构。使用表,Lua语言可以以一种简单,统一且高效的方式表示数组,集合,记录和其他很多的数据结构。

Lua语言中的表本质是一种辅助数组,这种数组不仅可以通过数字下标作为索引,也可以通过使用字符串或其他任意类型的值来映射相对应的值(键值对)。

在我看来,当lua是使用连续的数字下标作为索引的时候,它就是c++中的数组,当是使用键值对方式映射,用字符串作为索引的时候,因为其无序且键值唯一,它就很像c++中的unorder_map。

我们使用构造器表达式创建表,其最简单的形式是{}

构造:

a = {} -- 创建了一个空表
 
a[“x”] = 10 -- 这句话的键是“x”,值是10,此时我们可以通过a.x和a["x"]访问到10
 
a[10] = "Hello Table" --这句话的意思是,索引是10,值是字符串“Hello Table”
a = {} -- 创建了一个空表
 
k = “x”
 
a[k] = 10 -- 这句话的意思是a["x"]=10,键是“x”,值是10,此时我们可以通过a.x和a["x"]访问到10
 
a[10] = "Hello Table" --这句话的意思是,索引是10,值是字符串“Hello Table”

表永远是匿名的,表本身和保存表的变量之间没有固定的关系。当没有变量指向表的时候,Lua会对其进行自动回收。

a = {}                       -- a指向一个空表
a["x"] = 10                  -- a的"x"键赋值为10
b = a                        -- b指向a这个表
print(b["x"])                -- 此时答案为10
b["x"] = 20                    
print(a["x"])                -- 此时答案为20
                             -- 说明a和b指向的是同一张表,并没有进行深拷贝
a=nil                        -- 只剩下b指向这张表
b=nil                        -- Lua自动回收

解释一下上面的b = a,此时a和b其实是同一张表,b只不过是a表的一个别名,这有点像c++中的引用&,大家是同一个内存地址,所以修改b的时候,a也会被修改。这是浅拷贝,若想完全复制一个互相不影响的表,我们需要使用clone()函数,比如b = a:clone()。

1.lua中深拷贝与浅拷贝

lua中我们使用 = 来进行浅拷贝,使用函数clone() 来进行深拷贝。

如果拷贝的对象里的元素只有值(string、number、boolean等基础类型 ),那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象。

如果是一个表 的话,则浅拷贝拷贝出来的对象和拷贝前的实际上是同一个对象,占同一个内存,而深拷贝才创建出一个新的内存,一个新的对象。

2.lua中深拷贝与浅拷贝源码
function clone(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then 
            return object 
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for key, value in pairs(object) do
            new_table[_copy(key)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object) -- 返回clone出来的object表指针/地址
end

lua中clone的源代码十分简短,但是如果第一次看的话还是比较容易看懵。

我们如果传进去的对象不是表类型的话,那么我们就会直接把这个值return出去,然后再利用=号进行一次浅拷贝,上文提过如果是数值类型的话,浅拷贝也会生成一个对象。那么如果如果传的object是一个表类型的话,则递归去把object中的key, value复制到一个新创建的表中,最后再把object的元表设置成新表的元表。这样就完成了整个深克隆的过程了。

3.表索引

同一个表中存储的值可以有不同的类型索引:既不同类型的键。未经初始化的表元素为nil。

当把表当做结构体使用时,可以把索引当做成员名称使用。 在这里插入图片描述 对于Lua语言而言,这两种形式是等价的。但是对于阅读程序的人而言,这两种形式分别代表了两种意图:当你用a.name来赋值时,清晰地说明了把表当做结构体使用,此时的标识由预先定义的键组成的集合。而使用a【“name”】来赋值,则说明了表可以使用任意字符串当做键。

4.表构造器

除了使用空构造器{}构造表之外我们还可以这样做:

注意:Lua中默认值是从1开始。

days = {“Monday”,“Tuesday”,“Wednesday”,“Thursday”,“Friday”,“Saturday”,“Sunday”}
       --[[ 此时days[1]到days[7]被默认定义为“Monday”~“Sunday” ]]

Lua语言还提供了一种初始化记录式表的特殊语法:

a = {x = 10 , y = 20}
-- 上面的写法等价于 a["x"]=10,a["y"]=20

在同一个构造器中,可以混用记录式和列表式写法。

polyLine = {
 
           color = "blue",                 
           thickness = 2,
           npoints = 4,
           {x=0,y=0},                   --[[ 类似二维数组,此时polyLine[1]={x=0,y=0}
           {x=-10,y=1},                                    polyLine[2]={x=-10,y=1}
           {x=0,y=1}                                       polyLine[3]={x=0,y=1] ]]
 
            }
5.数组,列表和序列

如果想表示常见的数组或者列表,那么只需要使用整形作为索引的表即可。当该表不存在空洞,既表中的所有数据都不为nil时,则成这个表为序列(sequence)。

Lua语言提供了获取序列长度的操作符#。正如我们之前所看到,对于字符串而言,该操作符会统计字符串的字节数。对于表而言,则会返回序列的大小。 在这里插入图片描述 因而,当我们想在序列后增加元素时则可以使用语句 a[#a+1]=new 在这里插入图片描述

6.遍历表

我们可以使用pairs迭代器遍历表中的键值对。遍历过程中元素出现的顺序可能是随机的,相同的程序在每次运行时也可能产生不同的顺序。唯一可以确定的是,在遍历的过程中每个元素会且只会出现一次。 在这里插入图片描述 对于序列而言我们可以使用ipairs迭代器:此时Lua确保是按顺序进行的。

在这里插入图片描述

7.表标准库

表标准库提供了操作列表和序列的一些常用函数。

今天简单介绍增加(table.insert),删除(table.remove),移动(table.move)以及排序(table.sort)。

  • table.insert ()
  • insert()有两种格式,一种是两个参数,insert(tableName,元素),这种情况下就会默认插到末尾。
  • 另一种是三个参数(tableName,位置,元素),则可以按照自己的想法插入元素。 在这里插入图片描述
  • table.remove ()
  • 删除指定位置的元素,并把后面的元素往前移动填充删除所造成的空缺。。 在这里插入图片描述
  • table.move(tableA,起始索引,终止索引,tableB)
  • 它的作用时把表A中从起始索引到终止索引的值移动到表B中。
  • table.sort()
  • 这个就是单纯的排序方法。 在这里插入图片描述
(7) userdata类型

userdata是用户自定义的数据类型,lua只提供了一块原始的内存区域,用于存储任何东西, 在Lua中userdata没有任何预定义操作。 因为lua只是一个两三万行代码的一个脚本语言,有很多功能都是依靠c给它提供,所以userdata在实际中它代指了那些使用c/c++语言给lua提供的函数模块。 在这里插入图片描述

1.实例lua调用capi

今天是要和大家分享关于luaDebug库的一些内容,但是我在研究luaDebug库的时候,发现它调用了许多的luaAPI,对于没有研究过lua与c/c++交互的我可以说是看到满头大汉,一脸懵逼。所以我就决定从最原始入手,研究lua和c/c++是如何相互调用。今天分享的流程主要是通过举两个c++和lua相互调用的栗子,然后研究底层的实现,紧接着对我们的lua_debug库进行介绍,最后再尝试打印一些堆栈信息。 在这里插入图片描述 大家都知道,lua和c/c++之间是通过一个lua_Stack进行交互的,关于lua_Stack,网上对它的叫法有很多,有的说它是一个lua的堆栈,有的说它是lua状态机,也有的将它叫做lua的线程(注意这里的thread是lua的一种数据类型,与操作系统的线程需要区分开),我们可以简单的把lua_Stack当作一个翻译官,负责在c/c++与lua之间翻译,把正确的信息保存并传达给对方。

1.看两个小栗子

要让lua文件与c/c++文件进行交互有两种方式:

  • 其一是把我们的CAPI给打包成一个动态链接库dll,然后在运行的时候再加载这些函数。
  • 其二是把CAPI给编译到exe文件中。为了方便,以下是测试例子使用的是编译成一个exe文件的方式,准备步骤分三步:
  1. 新建一个c++控制台项目。
  2. 下载lua源码,把src目录下的所有文件拷贝到新建的c++目录下。
  3. include需要用到的lua库函数,生成解决方案即可。
extern "C" {
  #include "lua.h"
  #include "lualib.h"
  #include "lauxlib.h"
}

需要注意的是,因为我们创建的是c++的程序(cocos,u3d,ue4的底层都是c++代码),但是lua的库函数中使用的是纯c的接口,所以我们要extern "C"让编译器帮我们修改一下函数的编译和连接规约。

extern关键字:

my.cpp文件

//
#include "my.h"

CMyWinApp theApp; // 声明和定义了一个全局变量

//------------------------------------------------------------------
// main
//------------------------------------------------------------------
int main()
{

    CWinApp* pApp = AfxGetApp();

    return 0;

}
//------------------------------------------------------------------

MFC.cpp

#include "my.h"  // it should be mfc.h, but for CMyWinApp definition, so...

extern CMyWinApp theApp; // 提示编译器此变量定义在其他文件中,遇到这个变量时到其他模块中去寻找

CWinApp* AfxGetApp()
{
  return theApp.m_pCurrentWinApp;
}

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

extern C用法:

典型的,一个C++程序包含其它语言编写的部分代码。类似的,C++编写的代码片段可能被使用在其它语言编写的代码中。不同语言编写的代码互相调用是困难的,甚至是同一种编写的代码但不同的编译器编译的代码。例如,不同语言和同种语言的不同实现可能会在注册变量保持参数和参数在栈上的布局,这个方面不一样。 为了使它们遵守统一规则,可以使用extern指定一个编译和连接规约。例如,声明C和C++标准库函数strcyp(),并指定它应该根据C的编译和连接规约来链接:

extern "C" char* strcpy(char*,const char*);

extern "C"指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言,如Fortran、assembler等。 还有要说明的是,extern "C"指令仅指定编译和连接规约,但不影响语义。例如在函数声明中,指定了extern “C”,仍然要遵守C++的类型检测、参数转换规则。 如果你有很多语言要加上extern “C”,你可以将它们放到extern “C”{ }中。

extern "C"{
    typedef int (*CFT) (const void*,const void*);//style of C
    void qsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
}

extern "C"的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern “C”,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。(注:我在这里所说的类C,代表的是跟C语言的编译和连接方式一致的所有语言)

1.创建lua_Stack

前文提及lua_Stack是c/c++与lua的翻译官,所以在它们交互之前我们首先需要生成一个lua_Stack:

lua_State *L = luaL_newstate();

然后我们需要打开lua给我们提供的标准库:

luaL_openlibs(L); 

在这里插入图片描述 其实lua早已经在我们不经意间调用了c的api。

2.第一个栗子:c++调用lua的函数

我们首先需要新建一个lua文件,名称随意我这里使用的是luafile.lua。然后我们在lua文件中定义一个function,举一个最简单的减法吧。 在这里插入图片描述 然后就是使用luaL_dofile方法让我们的lua_Stack编译并执行这个文件,我们在打lua引用其他文件的时候知道loadfile是只编译,dofile是编译且每次执行,require是在package.loaded中查找此模块是否存在,不存在才执行,否则返回该模块。luaL_dofile和luaL_loadfile和上述原理相似,luaL_loadfile是仅编译,luaL_dofile是编译且执行。

然后通过lua_getglobal方法可以通过lua的全局表拿到lua的全局函数,并将它压入栈底(我们可以把lua_Stack的存储结构理解为下图的样子,实际上肯定没有那么简单,我们往下看)。

lua数据栈的抽象图:

我们可以通过两种索引来获取lua_Stack的调用栈所指向的数据:

static TValue *index2addr (lua_State *L, int idx) {
  CallInfo *ci = L->ci;
  if (idx > 0) {
    TValue *o = ci->func + idx;
    api_check(L, idx top - (ci->func + 1), "unacceptable index");
    if (o >= L->top) return NONVALIDVALUE;
    else return o;
  }
  else if (!ispseudo(idx)) {  /* negative index */
    api_check(L, idx != 0 && -idx top - (ci->func + 1), "invalid index");
    return L->top + idx;
  }
  else if (idx == LUA_REGISTRYINDEX)
    return &G(L)->l_registry;
  else {  /* upvalues */
    idx = LUA_REGISTRYINDEX - idx;
    api_check(L, idx func))  /* light C function? */
      return NONVALIDVALUE;  /* it has no upvalues */
    else {
      CClosure *func = clCvalue(ci->func);
      return (idx nupvalues) ? &func->upvalue[idx-1] : NONVALIDVALUE;
    }
  }
}

在这里插入图片描述 然后把两个参数按顺序压入栈中(不同类型压栈的函数接口大家可以查阅文档),然后调用pcall函数执行即可:

/* c++调用lua函数 */
luaL_dofile(L, "luafile.lua");
lua_getglobal(L, "l_sub");
lua_pushnumber(L, 1);
lua_pushnumber(L, 2);
lua_pcall(L, 2, 1, 0);
cout             
关注
打赏
1664288938
查看更多评论
0.0523s