目录
- 🙋♂️ 作者:海码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 会调用这个函数。
__index
作为表
-- 创建一个表local myTable ={name ="Alice"}-- 创建一个元表local myMetatable ={
__index ={age =30}}-- 设置元表setmetatable(myTable, myMetatable)-- 访问表中的值print(myTable.name)-- 输出 "Alice"print(myTable.age)-- 输出 30 (从元表中获取)
__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 表和元表的区别
- 表(Table):- 表是 Lua 中的基本数据结构,用于存储键值对。- 表可以用来表示数组、字典、集合、对象等。- 表是动态的,可以根据需要添加或删除键值对。
- 元表(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. 具体区别总结
- 定义方式:-
function tableName.functionName(args)
:定义普通函数。-function tableName:functionName(args)
:定义方法,隐式传递self
。 - 调用方式:-
tableName.functionName(args)
:调用普通函数,显式传递所有参数。-tableName:functionName(args)
:调用方法,隐式传递self
作为第一个参数。
3.1.4 示例对比
- 使用
.
定义和调用普通函数
local myTable ={}function myTable.sayHello(name)print("Hello, ".. name)end
myTable.sayHello("Alice")-- 输出:Hello, Alice
- 使用
:
定义和调用方法
local myTable ={}function myTable:sayHello(name)print("Hello, ".. name)print("Called by", self)end
myTable:sayHello("Alice")-- 输出:Hello, Alice-- Called by table: 0x...
- 适用场景
- 使用
.
定义和调用普通函数时,适用于不需要引用调用者的场景。 - 使用
:
定义和调用方法时,适用于需要引用调用者(即表本身)的场景,例如在面向对象编程中定义类的方法。 - 通过理解这两种方式的区别,可以更灵活地在 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 的基本步骤:
- 安装 UnLua:- 下载并安装 UnLua 插件。- 将插件添加到你的 UE 项目中。
- 配置 UnLua:- 在项目设置中启用 UnLua 插件。- 配置 Lua 脚本路径等参数。
- 编写 Lua 脚本:- 创建 Lua 脚本文件,例如
MyScript.lua
。- 编写游戏逻辑,例如:print("Hello from UnLua!")functionOnBeginPlay()print("Game started")end
- 在 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 脚本。以下是使用该插件的一些基本步骤:
- 安装插件:- 下载并安装 Unreal Engine Lua Plugin。- 将插件添加到你的 UE 项目中。
- 配置插件:- 在项目设置中启用 Lua 插件。- 配置 Lua 脚本路径等参数。
- 编写 Lua 脚本:- 创建 Lua 脚本文件,例如
MyScript.lua
。- 编写游戏逻辑,例如:print("Hello from Lua!")functionOnBeginPlay()print("Game started")end
- 在 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)交互的底层实现原理主要涉及以下几个方面:
- 嵌入 Lua 解释器:- 在 UE 中嵌入 Lua 解释器,使得 Lua 脚本可以在 UE 的运行时环境中执行。- 这通常通过在 C++ 代码中包含 Lua 解释器库(如
lua.hpp
)并初始化 Lua 解释器来实现。 - 绑定 C++ 和 Lua:- 通过绑定机制,将 UE 的 C++ 类和函数通过反射机制暴露给 Lua,使得 Lua 脚本可以调用这些 C++ 函数。- 绑定机制可以手动实现,也可以使用自动化工具或库(如 LuaBridge、Sol2、UnLua 等)来简化绑定过程。
- 脚本加载和执行:- 提供加载和执行 Lua 脚本的功能,使得 Lua 脚本可以在特定的事件或条件下执行。- 这通常通过在 C++ 代码中调用 Lua 解释器的 API 来实现,例如
luaL_dofile
用于加载和执行 Lua 脚本。 - 事件和回调机制:- 实现事件和回调机制,使得 Lua 脚本可以响应 UE 中的事件(如游戏开始、对象碰撞等)。- 这通常通过在 C++ 代码中注册 Lua 函数作为回调函数,并在特定事件发生时调用这些回调函数来实现。
以下是一些具体的实现细节,展示了如何在 C++ 代码中嵌入 Lua 解释器并实现与 Lua 的交互。
- 嵌入 Lua 解释器
首先,需要在 C++ 代码中包含 Lua 解释器库并初始化 Lua 解释器:
#include"lua.hpp"
lua_State* L =luaL_newstate();// 创建一个新的 Lua 状态luaL_openlibs(L);// 打开 Lua 标准库
- 绑定 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++ 函数
- 脚本加载和执行
可以在 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));}
- 事件和回调机制
可以在 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的不同赋值操作
- 基本类型赋值
Lua对于基本类型(如数字、字符串、布尔值、nil),赋值操作是值拷贝。这意味着赋值操作会创建一个新副本,两个变量之间没有任何关联。这里的值拷贝并不涉及对象的引用,因此不适用浅拷贝和深拷贝的概念。
local a =10local b = a -- 值拷贝print(a)-- 输出: 10print(b)-- 输出: 10
b =20print(a)-- 输出: 10print(b)-- 输出: 20
在这个例子中,a 和 b 是独立的变量,修改 b 不会影响 a。
- 引用类型赋值
对于引用类型(如
表、函数、用户数据
),赋值操作是引用拷贝(即
浅拷贝
)。这意味着赋值操作不会创建新副本,而是让两个变量引用同一个对象。
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 就是这样一种语言。以下是动态类型语言的一些特点:
- 类型检查在运行时进行:变量的类型是在程序运行时确定的,而不是在编译时确定的。
- 灵活性高:由于类型是在运行时确定的,变量可以在不同的时间点持有不同类型的值。
- 代码简洁:不需要显式声明变量的类型,代码通常更简洁。
- 易于使用:对于快速原型开发和脚本编写非常方便。
代码示例
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++ 就是这样一种语言。以下是静态类型语言的一些特点:
- 类型检查在编译时进行:变量的类型是在编译时确定的,编译器会在编译时进行类型检查。
- 类型安全:由于类型在编译时确定,许多类型错误可以在编译时被发现,从而提高了程序的 安全性 。
- 性能高:由于类型在编译时确定,编译器可以进行更多的优化,从而提高程序的运行效率。
- 代码冗长:需要显式声明变量的类型,代码通常更冗长。
代码示例
#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
- 创建表并赋值给
t1
local t1 ={1}
这行代码创建了一个包含一个元素1
的表,并将其引用赋值给变量t1
。 - **将
t1
的引用赋值给t2
**:local t2 = t1
这行代码将t1
的引用赋值给t2
,此时t1
和t2
都指向同一个表。 - **将
t1
置为nil
**:t1 =nil
这行代码将t1
置为nil
,此时t1
不再引用那个表,但t2
仍然引用着那个表。 - **检查
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 支持两种类型的弱引用表:
- 弱键表:表的键是弱引用。
- 弱值表:表的值是弱引用。
你可以通过设置元表的
__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
详细解释
- 创建弱值表:
local weakTable =setmetatable({},{ __mode ="v"})
这行代码创建了一个弱值表,表中的值是弱引用。 - **创建表并赋值给
t1
**:local t1 ={1}
这行代码创建了一个包含一个元素1
的表,并将其引用赋值给变量t1
。 - **将
t1
存储的表弱引用赋值给weakTable
**:weakTable["key"]= t1
这行代码将t1
存储的表的引用赋值给weakTable
,但由于weakTable
是弱值表,所以这个引用是弱引用。 - **将
t1
置为nil
**: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
由于weakTable
中的值是弱引用,当t1
被置为nil
后,那个表不再有强引用,因此会被垃圾回收器回收。
运行这段代码会输出:
The table has been garbage collected.
这表明当
t1
被置为
nil
后,
weakTable
中的值也被垃圾回收器回收了,因为它是一个弱引用。
版权归原作者 海码007 所有, 如有侵权,请联系我们删除。