0


【UE Lua】 快速入门(基础语法、与UE引擎的交互原理、与C++的不同)

目录

请添加图片描述

  • 🙋‍♂️ 作者:海码007
  • 📜 专栏:UE虚幻引擎专栏
  • 💥 标题:【UE Lua】 快速入门(基础语法、与UE引擎的交互原理、与C++的不同)
  • ❣️ 寄语:书到用时方恨少,事非经过不知难!
  • 🎈 最后:文章作者技术和水平有限,如果文中出现错误,希望大家能指正,同时有问题的话,欢迎大家留言讨论。

0 引言

游戏开发中经常需要使用

Lua

语言作为胶水语言,来辅助游戏的快速开发。所以就总结一下学习

Lua

的一些心得。

相关参考文章:
游戏开发为什么要使用Lua
Lua是如何与C++进行交互的

1 基础语法

Lua 是一种轻量级的脚本语言,语法简洁且易于学习。以下是 Lua 脚本的基础语法,包括变量、数据类型、控制结构、函数、表、元表等。

1.1 变量和数据类型

Lua 是动态类型语言,变量不需要声明类型。

-- 变量local a =10-- 整数local b =3.14-- 浮点数local c ="Hello"-- 字符串local d =true-- 布尔值local e =nil-- 空值

1.2 注释

Lua 支持单行注释和多行注释。

-- 单行注释--[[
多行注释
可以跨越多行
]]

1.3 控制结构

条件语句

local x =10if x >0thenprint("x is positive")elseif x <0thenprint("x is negative")elseprint("x is zero")end

循环语句

-- while 循环local i =1while i <=5doprint(i)
    i = i +1end-- for 循环for i =1,5doprint(i)-- 输出1,2,3,4,5end-- 泛型 for 循环local t ={10,20,30}for index, value inipairs(t)doprint(index, value)-- 输出 1,10  2,20  3,30end

1.4 函数

-- 定义函数functionadd(a, b)return a + b
end-- 调用函数local result =add(3,4)print(result)-- 输出 7-- 匿名函数local multiply =function(a, b)return a * b
endprint(multiply(3,4))-- 输出 12

1.5 表(Table)

表是 Lua 中唯一的数据结构,可以用来表示数组、字典、集合、对象等。同时也可以存放函数

-- 创建一个空表local t ={}-- 数组local array ={1,2,3,4,5}print(array[1])-- 输出 1-- 字典(又称散列表、哈希表)local dict ={name ="Alice", age =30}print(dict["name"])-- 输出 "Alice"print(dict.age)-- 输出 30-- 嵌套表local nested ={a ={b ={c =10}}}print(nested.a.b.c)-- 输出 10------------------------------------------------------------------------------------ 定义一个表local myTable ={}-- 将函数存储在表中
myTable.sayHello =function()print("Hello, World!")end-- 调用表中的函数
myTable.sayHello()-- 输出:Hello, World!----------------------------------------------------------------------------------

在 Lua 中,往表(table)中插入键值对有多种方式。以下是几种常见的方法:

-- 1. 使用方括号 `[]` 语法-- 这是最常见和直接的方法。local myTable ={}
myTable["key1"]="value1"
myTable["key2"]="value2"-- 2. 使用点 `.` 语法-- 这种方法只能用于键是有效的标识符(即由字母、数字和下划线组成,且不能以数字开头)。local myTable ={}
myTable.key1 ="value1"
myTable.key2 ="value2"-- 3. 在表构造时直接初始化-- 在创建表时,可以直接在构造器中插入键值对。local myTable ={
    key1 ="value1",
    key2 ="value2"}-- 4. 使用 `table.insert` 函数-- `table.insert` 通常用于插入数组(即具有连续整数键的表)中的元素,但也可以用于插入键值对。local myTable ={}
table.insert(myTable,"value1")-- 插入到数组中,键为1
table.insert(myTable,"value2")-- 插入到数组中,键为2-- 5. 使用 `rawset` 函数-- `rawset` 函数可以直接设置表的键值对,绕过元表(metatable)的 `__newindex` 元方法。local myTable ={}rawset(myTable,"key1","value1")rawset(myTable,"key2","value2")-- 6. 使用 `setmetatable` 和 `__newindex` 元方法-- 通过元表和 `__newindex` 元方法,可以自定义插入键值对的行为。local myTable ={}local mt ={
    __newindex =function(table, key, value)rawset(table, key, value)print("Inserted key: ".. key ..", value: ".. value)end}setmetatable(myTable, mt)

myTable.key1 ="value1"-- 会触发 __newindex 元方法
myTable["key2"]="value2"-- 也会触发 __newindex 元方法-- 7. 使用 `for` 循环批量插入-- 如果有多个键值对需要插入,可以使用 `for` 循环。local myTable ={}local keys ={"key1","key2","key3"}local values ={"value1","value2","value3"}for i =1,#keys do
    myTable[keys[i]]= values[i]end

1.6 元表(Metatable)

元表用于改变表的行为,可以定义一些特殊的操作,如算术运算、比较运算、表访问等。

-- 创建一个表local myTable ={name ="Alice"}-- 创建一个元表local myMetatable ={
    __index ={age =30}}-- 设置元表setmetatable(myTable, myMetatable)-- 访问表中的值print(myTable.name)-- 输出 "Alice"print(myTable.age)-- 输出 30  (从元表中获取)

1.7 字符串操作

Lua 提供了一些常用的字符串操作函数。

local str ="Hello, World!"-- 获取字符串长度print(#str)-- 输出 13-- 字符串连接local str2 = str .." Lua"print(str2)-- 输出 "Hello, World! Lua"-- 字符串查找local start, finish = string.find(str,"World")print(start, finish)-- 输出 8 12-- 字符串替换local newStr = string.gsub(str,"World","Lua")print(newStr)-- 输出 "Hello, Lua!"

1.8 模块和包

Lua 支持模块和包,可以通过

require

函数加载模块。

-- mymodule.lualocal mymodule ={}function mymodule.greet(name)print("Hello, ".. name)endreturn mymodule

-- main.lualocal mymodule =require("mymodule")
mymodule.greet("World")-- 输出 "Hello, World"

2 数据结构 - 表

在 Lua 中,表(table)是最重要的数据结构,而元表(metatable)则是用于改变表行为的机制。以下是对表和元表的详细解释,以及

__index

元方法的作用。

2.1 表(Table)

表是 Lua 中唯一的数据结构,可以用来表示数组、字典、集合、对象等。表是动态的,可以根据需要添加或删除键值对。

-- 创建一个空表local myTable ={}-- 添加键值对
myTable["name"]="Alice"
myTable["age"]=30-- 访问表中的值print(myTable["name"])-- 输出 "Alice"print(myTable["age"])-- 输出 30

2.2 元表(Metatable)

元表是一个特殊的表,可以用来改变另一个表的行为。通过设置元表,可以定义一些特殊的操作,如算术运算、比较运算、表访问等。

-- 创建一个表local myTable ={}-- 创建一个元表local myMetatable ={}-- 设置元表setmetatable(myTable, myMetatable)

2.3

__index

元方法

__index

是元表中的一个特殊字段,用于处理对表中不存在的键的访问。当访问一个表中不存在的键时,Lua 会查找该表的元表中的

__index

元方法。如果

__index

是一个表,Lua 会在这个表中查找键;如果

__index

是一个函数,Lua 会调用这个函数。

  1. __index 作为表
-- 创建一个表local myTable ={name ="Alice"}-- 创建一个元表local myMetatable ={
    __index ={age =30}}-- 设置元表setmetatable(myTable, myMetatable)-- 访问表中的值print(myTable.name)-- 输出 "Alice"print(myTable.age)-- 输出 30  (从元表中获取)
  1. __index 作为函数
-- 创建一个表local myTable ={name ="Alice"}-- 创建一个元表local myMetatable ={
    __index =function(table, key)if key =="age"thenreturn30elsereturnnilendend}-- 设置元表setmetatable(myTable, myMetatable)-- 访问表中的值print(myTable.name)-- 输出 "Alice"print(myTable.age)-- 输出 30  (通过函数获取)

2.4 表和元表的区别

  1. 表(Table):- 表是 Lua 中的基本数据结构,用于存储键值对。- 表可以用来表示数组、字典、集合、对象等。- 表是动态的,可以根据需要添加或删除键值对。
  2. 元表(Metatable):- 元表是一个特殊的表,用于改变另一个表的行为。- 元表可以包含一些特殊的字段(如 __index__newindex__add 等),用于定义表的特殊操作。- 元表通过 setmetatable 函数设置,getmetatable 函数获取。
  • 表(Table) 是 Lua 中的基本数据结构,用于存储键值对。
  • 元表(Metatable) 是一个特殊的表,用于改变另一个表的行为。
  • __index 元方法用于处理对表中不存在的键的访问,可以是一个表或一个函数。

通过使用元表和

__index

元方法,可以实现更灵活和强大的表操作,满足各种编程需求。

3 Lua 面向对象范式

3.1 使用 . 定义函数和使用 : 定义函数的区别

在 Lua 中,使用

.

:

定义和调用函数有着重要的区别,主要体现在函数的调用方式和隐式传递的参数上。具体来说,

:

是用于定义和调用方法(method),而

.

是用于定义和调用普通函数(function)。理解这一点,才能更好的理解 Lua 是怎么通过表来实现面向对象编程的。

**1. 使用

.

定义和调用函数**

使用

.

定义的函数是普通函数,调用时需要显式传递所有参数。

-- 定义一个表local myTable ={}-- 使用 . 定义一个普通函数function myTable.sayHello(name)print("Hello, ".. name)end-- 调用普通函数
myTable.sayHello("Alice")-- 输出:Hello, Alice

**2. 使用

:

定义和调用方法**

使用

:

定义的函数是方法,调用时会隐式传递调用者(即表本身)作为第一个参数

self

-- 定义一个表local myTable ={}-- 使用 : 定义一个方法function myTable:sayHello(name)print("Hello, ".. name)print("Called by", self)end-- 调用方法
myTable:sayHello("Alice")-- 输出:Hello, Alice--       Called by table: 0x...

在上面的例子中,

myTable:sayHello("Alice")

实际上等价于

myTable.sayHello(myTable, "Alice")

。也就是说,调用者

myTable

被隐式地作为第一个参数传递给方法

sayHello

,并在方法内部作为

self

使用。

3. 具体区别总结

  1. 定义方式:- function tableName.functionName(args):定义普通函数。- function tableName:functionName(args):定义方法,隐式传递 self
  2. 调用方式:- tableName.functionName(args):调用普通函数,显式传递所有参数。- tableName:functionName(args):调用方法,隐式传递 self 作为第一个参数。

3.1.4 示例对比

  1. 使用 . 定义和调用普通函数
local myTable ={}function myTable.sayHello(name)print("Hello, ".. name)end

myTable.sayHello("Alice")-- 输出:Hello, Alice
  1. 使用 : 定义和调用方法
local myTable ={}function myTable:sayHello(name)print("Hello, ".. name)print("Called by", self)end

myTable:sayHello("Alice")-- 输出:Hello, Alice--       Called by table: 0x...
  1. 适用场景
  • 使用 . 定义和调用普通函数时,适用于不需要引用调用者的场景。
  • 使用 : 定义和调用方法时,适用于需要引用调用者(即表本身)的场景,例如在面向对象编程中定义类的方法。
  • 通过理解这两种方式的区别,可以更灵活地在 Lua 中定义和调用函数,编写出更清晰和结构化的代码。

3.2 实现面向对象编程

虽然 Lua 本身没有内置的面向对象编程支持,但可以通过元表(metatables)和表(tables)来实现面向对象编程。

-- 定义一个类
Person ={}
Person.__index = Person

-- 构造函数function Person:new(name, age)local self =setmetatable({}, Person)
    self.name = name
    self.age = age
    return self
end-- 方法function Person:greet()print("Hello, my name is ".. self.name .." and I am ".. self.age .." years old.")end-- 创建对象local person = Person:new("Alice",30)
person:greet()

4 Lua与UE引擎的交互

Lua 与 Unreal Engine(UE)交互通常通过第三方插件或绑定库来实现。这些插件和库提供了在 UE 中嵌入 Lua 脚本的能力,使得开发者可以使用 Lua 编写游戏逻辑、控制游戏对象等。以下是一些常见的方法和工具:

4.1 使用第三方插件 UnLua

UnLua 是一个专门为 Unreal Engine 设计的 Lua 插件,提供了深度集成和高性能。以下是使用 UnLua 的基本步骤:

  1. 安装 UnLua:- 下载并安装 UnLua 插件。- 将插件添加到你的 UE 项目中。
  2. 配置 UnLua:- 在项目设置中启用 UnLua 插件。- 配置 Lua 脚本路径等参数。
  3. 编写 Lua 脚本:- 创建 Lua 脚本文件,例如 MyScript.lua。- 编写游戏逻辑,例如:print("Hello from UnLua!")functionOnBeginPlay()print("Game started")end
  4. 在 UE 中调用 Lua 脚本:- 在 UE 蓝图或 C++ 代码中加载并执行 Lua 脚本。// 在 C++ 代码中加载 Lua 脚本UUnLuaManager* UnLuaManager =UUnLuaManager::Get();UnLuaManager->RunFile("MyScript.lua");// 调用 Lua 函数UnLuaManager->CallFunction("OnBeginPlay");

4.2 使用 Unreal Engine Lua Plugin

Unreal Engine Lua Plugin 是一个流行的插件,允许在 UE 中嵌入 Lua 脚本。以下是使用该插件的一些基本步骤:

  1. 安装插件:- 下载并安装 Unreal Engine Lua Plugin。- 将插件添加到你的 UE 项目中。
  2. 配置插件:- 在项目设置中启用 Lua 插件。- 配置 Lua 脚本路径等参数。
  3. 编写 Lua 脚本:- 创建 Lua 脚本文件,例如 MyScript.lua。- 编写游戏逻辑,例如:print("Hello from Lua!")functionOnBeginPlay()print("Game started")end
  4. 在 UE 中调用 Lua 脚本:- 在 UE 蓝图或 C++ 代码中加载并执行 Lua 脚本。// 在 C++ 代码中加载 Lua 脚本ULuaState* LuaState =NewObject<ULuaState>();LuaState->DoFile("MyScript.lua");// 调用 Lua 函数LuaState->GetFunction("OnBeginPlay");LuaState->Call(0,0);

4.3 Lua和UE交互的实现原理

Lua 与 Unreal Engine(UE)交互的底层实现原理主要涉及以下几个方面:

  1. 嵌入 Lua 解释器:- 在 UE 中嵌入 Lua 解释器,使得 Lua 脚本可以在 UE 的运行时环境中执行。- 这通常通过在 C++ 代码中包含 Lua 解释器库(如 lua.hpp)并初始化 Lua 解释器来实现。
  2. 绑定 C++ 和 Lua:- 通过绑定机制,将 UE 的 C++ 类和函数通过反射机制暴露给 Lua,使得 Lua 脚本可以调用这些 C++ 函数。- 绑定机制可以手动实现,也可以使用自动化工具或库(如 LuaBridge、Sol2、UnLua 等)来简化绑定过程。
  3. 脚本加载和执行:- 提供加载和执行 Lua 脚本的功能,使得 Lua 脚本可以在特定的事件或条件下执行。- 这通常通过在 C++ 代码中调用 Lua 解释器的 API 来实现,例如 luaL_dofile 用于加载和执行 Lua 脚本。
  4. 事件和回调机制:- 实现事件和回调机制,使得 Lua 脚本可以响应 UE 中的事件(如游戏开始、对象碰撞等)。- 这通常通过在 C++ 代码中注册 Lua 函数作为回调函数,并在特定事件发生时调用这些回调函数来实现。

以下是一些具体的实现细节,展示了如何在 C++ 代码中嵌入 Lua 解释器并实现与 Lua 的交互。


  1. 嵌入 Lua 解释器

首先,需要在 C++ 代码中包含 Lua 解释器库并初始化 Lua 解释器:

#include"lua.hpp"

lua_State* L =luaL_newstate();// 创建一个新的 Lua 状态luaL_openlibs(L);// 打开 Lua 标准库
  1. 绑定 C++ 和 Lua

可以使用 LuaBridge 或其他绑定库来简化绑定过程。以下是使用 LuaBridge 的示例:

#include"LuaBridge/LuaBridge.h"voidHelloWorld(){UE_LOG(LogTemp, Log,TEXT("Hello from C++"));}voidBindFunctions(lua_State* L){
    luabridge::getGlobalNamespace(L).addFunction("HelloWorld", HelloWorld);}

在 Lua 脚本中,可以调用绑定的 C++ 函数:

HelloWorld()-- 调用 C++ 函数
  1. 脚本加载和执行

可以在 C++ 代码中加载和执行 Lua 脚本:

if(luaL_dofile(L,"MyScript.lua")!= LUA_OK){constchar* error =lua_tostring(L,-1);UE_LOG(LogTemp, Error,TEXT("Error: %s"),UTF8_TO_TCHAR(error));}
  1. 事件和回调机制

可以在 C++ 代码中注册 Lua 函数作为回调函数,并在特定事件发生时调用这些回调函数:

// 注册 Lua 回调函数lua_getglobal(L,"OnBeginPlay");if(lua_isfunction(L,-1)){lua_pcall(L,0,0,0);}

在 Lua 脚本中定义回调函数:

functionOnBeginPlay()print("Game started")end

5. 总结

Lua 与 Unreal Engine 交互的底层实现原理主要涉及嵌入 Lua 解释器、绑定 C++ 和 Lua、加载和执行 Lua 脚本以及实现事件和回调机制。通过这些机制,可以在 UE 中嵌入 Lua 脚本,实现灵活的游戏逻辑编写和控制。使用第三方插件和库(如 UnLua、LuaBridge 等)可以简化这些过程,使得开发者更容易实现 Lua 与 UE 的交互。

5 Lua和C++的不同之处

5.1 变量的赋值规则

5.1.1 C++的基本变量赋值和对象赋值

基本变量赋值就是值拷贝的过程,对于对象赋值才有浅拷贝和深拷贝的区分。

  • 浅拷贝是指复制对象时,只复制对象的基本数据成员,而不复制指向的资源(如动态分配的内存)。这意味着两个对象将共享同一块内存资源。
  • 深拷贝是指复制对象时,不仅复制对象的基本数据成员,还复制指向的资源。这意味着每个对象都有自己独立的内存资源。

浅拷贝发生的场景

  • 对于内置类型(如int、double、char等)和简单的结构体,赋值操作是值拷贝,因为这些类型的赋值操作只是复制其值。(,“浅拷贝” 和 “深拷贝” 通常用于描述对象(如数组、结构体、类实例等)的复制行为,而不是基本数据类型的复制行为。)
  • 对于类对象,默认的赋值操作符(operator=)是浅拷贝。默认的赋值操作符会逐个成员地进行赋值,这对于简单类型的成员是浅拷贝,但对于指针成员则只是复制指针地址。

如果需要深拷贝,可以自定义赋值操作符和拷贝构造函数。

5.1.2 Lua的不同赋值操作

  1. 基本类型赋值

Lua对于基本类型(如数字、字符串、布尔值、nil),赋值操作是值拷贝。这意味着赋值操作会创建一个新副本,两个变量之间没有任何关联。这里的值拷贝并不涉及对象的引用,因此不适用浅拷贝和深拷贝的概念。

local a =10local b = a  -- 值拷贝print(a)-- 输出: 10print(b)-- 输出: 10

b =20print(a)-- 输出: 10print(b)-- 输出: 20

在这个例子中,a 和 b 是独立的变量,修改 b 不会影响 a。

  1. 引用类型赋值

对于引用类型(如

表、函数、用户数据

),赋值操作是引用拷贝(即

浅拷贝

)。这意味着赋值操作不会创建新副本,而是让两个变量引用同一个对象。

local t1 ={1,2,3}local t2 = t1  -- 引用拷贝print(t1[1])-- 输出: 1print(t2[1])-- 输出: 1

t2[1]=10print(t1[1])-- 输出: 10print(t2[1])-- 输出: 10

在这个例子中,t1 和 t2 引用同一个表,修改 t2 会影响 t1。如果需要进行深拷贝,需要自己实现一个深拷贝函数。

3. Lua引用和C++指针的区别

Lua 的引用类型和 C++ 的指针有一些相似之处,但也有显著的不同:

  • 相似之处: - 都可以让多个变量引用同一个对象。- 修改一个变量的内容会影响所有引用该对象的变量。
  • 不同之处: - 语法:Lua 没有显式的指针语法,引用类型的赋值和使用与基本类型没有区别。而在 C++ 中,指针有特定的语法(如 * 和 & 操作符)。- 内存管理:Lua 使用垃圾回收机制自动管理内存,而 C++ 通常需要手动管理内存(除非使用智能指针)。- 类型安全:Lua 是动态类型语言,引用类型的变量可以在运行时改变其引用的对象类型。而 C++ 是静态类型语言,指针类型在编译时确定。

5.2 动态类型和静态类型

Lua是动态类型语言,C++是静态类型语言,但是提供一些动态类型检查的方法,例如 DynamicCast 。这两种类型系统有着显著的区别,影响了它们的编程风格和使用场景。

5.2.1 动态类型语言(Lua)

在动态类型语言中,变量的类型是在运行时确定的,而不是在编译时确定的。Lua 就是这样一种语言。以下是动态类型语言的一些特点:

  1. 类型检查在运行时进行:变量的类型是在程序运行时确定的,而不是在编译时确定的。
  2. 灵活性高:由于类型是在运行时确定的,变量可以在不同的时间点持有不同类型的值
  3. 代码简洁:不需要显式声明变量的类型,代码通常更简洁。
  4. 易于使用:对于快速原型开发和脚本编写非常方便。

代码示例

local x =10-- x 是一个数字print(x)-- 输出:10

x ="Hello"-- x 现在是一个字符串print(x)-- 输出:Hello

x ={1,2,3}-- x 现在是一个表print(x[1])-- 输出:1

在这个示例中,变量

x

可以在不同的时间点持有不同类型的值。

同时Lua 提供了一个内置的

type

函数,可以用来检查变量的类型。

type

函数返回一个字符串,表示变量的类型。或者可以使用

assert

函数来进行类型检查,并在类型不匹配时抛出错误。

assert

函数接受一个条件和一个可选的错误消息,如果条件为假,则抛出错误。

5.2.2 静态类型语言(C++)

在静态类型语言中,变量的类型是在编译时确定的。C++ 就是这样一种语言。以下是静态类型语言的一些特点:

  1. 类型检查在编译时进行:变量的类型是在编译时确定的,编译器会在编译时进行类型检查。
  2. 类型安全:由于类型在编译时确定,许多类型错误可以在编译时被发现,从而提高了程序的 安全性
  3. 性能高:由于类型在编译时确定,编译器可以进行更多的优化,从而提高程序的运行效率。
  4. 代码冗长:需要显式声明变量的类型,代码通常更冗长。

代码示例

#include<iostream>#include<string>intmain(){int x =10;// x 是一个整数
    std::cout << x << std::endl;// 输出:10

    std::string y ="Hello";// y 是一个字符串
    std::cout << y << std::endl;// 输出:Hello// x = "Hello";  // 错误:不能将字符串赋值给整数return0;}

在这个示例中,变量

x

y

的类型在编译时就已经确定,并且不能改变。

虽然C++是静态类型语言,但是还是有很多手段进行动态类型检查的。包括

dynamic_cast

typeid

std::type_info

自定义类型检查

以及结合智能指针和类型擦除技术。(挖个坑,以后填)

5.2.3 总结

  • Lua(动态类型语言):- 类型在运行时确定。- 变量可以在不同时间点持有不同类型的值。- 代码更简洁,适合快速原型开发和脚本编写。
  • C++(静态类型语言):- 类型在编译时确定。- 变量的类型一旦确定就不能改变。- 代码更冗长,但类型安全性更高,性能更好。

这两种类型系统各有优缺点,适用于不同的编程场景。动态类型语言提供了更高的灵活性和更快的开发速度,而静态类型语言提供了更高的类型安全性和性能。

5.3 垃圾回收

Lua具有垃圾回收功能,C++没有。Lua的垃圾回收主要作用于动态分配的内存对象,例如,表、函数、用户数据、线程、字符串。

为了更好的理解Lua垃圾回收的过程。引入一个场景,在 Lua 中,当你将

t1

置为

nil

时,再去访问

t2

t2

仍然会保持对原始表的引用。

local t1 ={1}local t2 = t1
t1 =nilif t2 ==nilthen-- 这个块不会被执行end
  1. 创建表并赋值给 t1local t1 ={1}这行代码创建了一个包含一个元素 1 的表,并将其引用赋值给变量 t1
  2. **将 t1 的引用赋值给 t2**:local t2 = t1这行代码将 t1 的引用赋值给 t2,此时 t1t2 都指向同一个表。
  3. **将 t1 置为 nil**:t1 =nil这行代码将 t1 置为 nil,此时 t1 不再引用那个表,但 t2 仍然引用着那个表。
  4. **检查 t2 是否为 nil**:if t2 ==nilthen-- 这个块不会被执行end由于 t2 仍然引用着那个表,所以这个表不会被垃圾回收,所以 t2 不为 nil,因此这个条件判断为 false,代码块不会被执行。

需要注意的是,Lua 的垃圾回收机制会在没有任何变量引用某个对象时回收其内存。在这个例子中,虽然

t1

被置为

nil

,但

t2

仍然引用着那个表,所以垃圾回收器不会回收这个表的内存。只有当

t2

也被置为

nil

或引用其他对象时,垃圾回收器才会回收这个表的内存。

t2 =nilcollectgarbage()-- 手动触发垃圾回收

在这之后,原始的表将不再被引用,垃圾回收器会在适当的时候回收其内存。

5.4 弱引用

就刚才的代码示例而言,有些时候希望t2只是弱引用t1的表。在t1将表取消引用的时候,就将这块内存区域给回收。

在 Lua 中,可以使用弱引用来实现某些高级内存管理策略。弱引用允许你创建一种特殊的表,这种表中的键或值不会阻止垃圾回收器回收它们所引用的对象。Lua 提供了

setmetatable

__mode

元方法来实现弱引用。

Lua 支持两种类型的弱引用表:

  1. 弱键表:表的键是弱引用。
  2. 弱值表:表的值是弱引用。

你可以通过设置元表的

__mode

字段来指定弱引用的类型:

  • "k" 表示弱键。
  • "v" 表示弱值。
  • "kv" 表示弱键和弱值。

以下是一个示例,展示如何创建一个弱值表,使得

t2

弱引用

t1

存储的表:

-- 创建一个弱值表local weakTable =setmetatable({},{ __mode ="v"})-- 创建一个表并赋值给 t1local t1 ={1}-- 将 t1 存储的表弱引用赋值给 weakTable
weakTable["key"]= t1

-- 将 t1 置为 nil
t1 =nil-- 手动触发垃圾回收collectgarbage()-- 检查 weakTable 中的值是否被回收if weakTable["key"]==nilthenprint("The table has been garbage collected.")elseprint("The table is still accessible.")print(weakTable["key"][1])-- 输出: 1end

详细解释

  1. 创建弱值表local weakTable =setmetatable({},{ __mode ="v"})这行代码创建了一个弱值表,表中的值是弱引用。
  2. **创建表并赋值给 t1**:local t1 ={1}这行代码创建了一个包含一个元素 1 的表,并将其引用赋值给变量 t1
  3. **将 t1 存储的表弱引用赋值给 weakTable**:weakTable["key"]= t1这行代码将 t1 存储的表的引用赋值给 weakTable,但由于 weakTable 是弱值表,所以这个引用是弱引用。
  4. **将 t1 置为 nil**:t1 =nil这行代码将 t1 置为 nil,此时没有强引用指向那个表。
  5. 手动触发垃圾回收collectgarbage()这行代码手动触发垃圾回收,垃圾回收器会回收所有不再被强引用的对象。
  6. 检查 weakTable 中的值是否被回收if weakTable["key"]==nilthenprint("The table has been garbage collected.")elseprint("The table is still accessible.")print(weakTable["key"][1])-- 输出: 1end由于 weakTable 中的值是弱引用,当 t1 被置为 nil 后,那个表不再有强引用,因此会被垃圾回收器回收。

运行这段代码会输出:

The table has been garbage collected.

这表明当

t1

被置为

nil

后,

weakTable

中的值也被垃圾回收器回收了,因为它是一个弱引用。

标签: lua 交互 junit

本文转载自: https://blog.csdn.net/hhw_hhw/article/details/140268503
版权归原作者 海码007 所有, 如有侵权,请联系我们删除。

“【UE Lua】 快速入门(基础语法、与UE引擎的交互原理、与C++的不同)”的评论:

还没有评论