Lua学习笔记

这篇博客深入介绍了Lua编程语言的基础知识,包括命名规范、数据类型、运算符、流程控制、函数、元表、协程以及字符串和表的操作。此外,还探讨了Lua的垃圾回收机制和标准数学函数库。文章强调了Lua的模块与包加载机制,以及如何处理全局变量和局部变量。通过对Lua特性的解析,展示了其在不同场景下的应用,如闭包和协程,为读者提供了全面了解Lua语言的视角。
摘要由CSDN通过智能技术生成

lua学习

命名规范

image-20210504160714399

数据类型

image-20210504210435485

Str1="Hello";
Str2="World";
NumArray={1,2,3,4,5};
Bool=true
Number=0.1
Nil=nil
function Fun1()
    return 10
end

print("类型")
print(type(Str1))
print(type(NumArray))
print(type(Bool))
print(type(Number))
print(type(Nil))
print(type(Fun1))

运算符

image-20210504210900444

image-20210504211501227

多重赋值

image-20210504212532043

全局变量与局部变量

image-20210504212720485

local localX="在这里定义局部变量不就相当于全部吗?"
if (true) then
    local localY="这里才算是全局变量"
end
--print(localY);

lua 没有local修饰的都是全局变量

流程控制语句

image-20210504214155847

if

image-20210504214702344

if true then

else

end

if true then
elseif true then
end

while

while true do

  

end

repeat(相当于do while)

repeat
    
until true

for

image-20210504215829723

步长不为0,如果初始值+步长无法得到结束值则不输出

for i = 1, 10, 2 do
    print(i)
end

ipair

image-20210504220235476

注意ipair遇到nil就停止,pair会遍历全部

pair可以遍历map,也可以遍历list

image-20210504220347603

for key, value in pairs(KV) do
    print(key.."-"..value)
end

for key, value in pairs(NumArray) do
    print(value)
end

break

迭代器

image-20210504221818476

函数

image-20210504223110553

函数中的全局变量与局部变量

image-20210504223805382

函数的多返回值

image-20210504223931156

变量赋值函数

image-20210504223435838

函数作为参数

image-20210504224156306

可变参数

function MulArg(a,...)
    print("这是"..a)
    local b={...}
    for key,value in pairs(b) do
        print(value)
    end
end

MulArg("dddd","a","b","c");

匿名函数

image-20210504224336238

定义匿名函数的时候一定要赋值给变量,优点,应该就是变量改变指向的时候,函数从内存中清除吧。

后面闭包可能有用。

尾调用

image-20210505165431635

image-20210505165416182

作用是优化储存空间,不会出现栈溢出 ,因为尾调用不需要保留本方法

function RecurFunc(num)
    if(num>0) then
        return RecurFunc(num-1)
   			--return RecurFunc(num-1)+1
    else
        return 0
    end
        
    
end
RecurFunc10000000)

注意

image-20210505170744836

image-20210505170721316

闭包

image-20210505174417539

image-20210505174740143

image-20210505175807545

--闭包
function CloseFun()
    local i=0        --这里只会执行一次   注意要使用local  不然多个实例的时候其实就共享一个全局变量
    return function()
        i=i+1
        return i
    end
end
print("无参闭包")
CloseFunArg=CloseFun()  --奇怪,这里不是一个数字吗。不是,看下面
print(CloseFunArg)      --function: 000001C33F000660
--原理,这货返回的根本就不是一个数字,他返回的是一个方法,而且这个方法保留了i的值
print(CloseFunArg())   --1
print(CloseFunArg())   --2
print(CloseFunArg())	 --3
print(CloseFunArg())   --4

内嵌函数含有参数

image-20210505175323184

应用场景

共享一个upValues

image-20210505175052509

我的迭代器

function Itrs(Array)
    local i=0
    return function ()
        i=i+1
        return Array[i]
    end
end
MyItrs=Itrs(NumArray)
print("我的迭代器")
for arg in MyItrs do
    print(arg)
end

字符串

字符串基础

image-20210504204220172

print(StrMultiLine)
print("字符串连接".."不用+")
print("3"+"4");
print("3"+10);
--print("字符+数字会报错,如果字符隐式转换失败的话"+10);
print(#"#号得字符串字节数");

print("下面是显示转换");
print(tonumber("2222")+tostring(2222))
print(tonumber("2222")..tostring(2222))
print(tonumber("2222")..tostring(NumArray))
print(tonumber("2222")..tostring(Nil))

字符串的定义

image-20210504225022177

转义字符串

image-20210504225041612

字符串API

image-20210504225720507

string.find*(“查找字符位置”,“字符”) 夭寿了,下标从1开始的,字节为单位,汉字是两个字节

string.find*(“查找字符位置”,“字符”,5) 从5的位置开始搜索

string.find*(“截取字符”,3,5) 从3的位置开始截取到5 注意是以字节为单位

string.gsub*(“查找字符位置”,“字”,“子”) 替换

string.format(“a=%d”) 和c的print差不多

table表

image-20210504230908013

输出

image-20210504231039445

for迭代输出

数组类型

print("数组的迭代输出")
for i = 1, #NumArray do
    print(NumArray[i])
end

table.getn已经不用了,需要手写

键值对

image-20210505092108783

pair迭代输出

这样输出是无序的

image-20210505092424039

Table 操作

以下列出了 Table 操作常用的方法:

序号方法 & 用途
1**table.concat (table [, sep [, start [, end]]])😗*concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。
2**table.insert (table, [pos,] value)😗*在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾.
3**table.maxn (table)**指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。(Lua5.2之后该方法已经不存在了,本文使用了自定义函数实现)
4**table.remove (table [, pos])**返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。
5**table.sort (table [, comp])**对给定的table进行升序排序。 按ASCII编码排序

image-20210505094425736

table 资源释放

image-20210505095654415

模块 与包

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
 
-- 定义一个常量
module.constant = "这是一个常量"
 
-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end
 
local function func2()
    print("这是一个私有函数!")
end
 
function module.func3()
    func2()
end
 
return module
require("module")
print(module.constant)
module.func3()

local m =require("module")
print(m.constant)
m.func3()
--如果在ww文件夹下就需要这样
local m =require("ww.module")
print(m.constant)
m.func3()
print(_G.package.path)


加载机制(不太懂)

函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块

require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。

当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 “~/lua/” 路径加入 LUA_PATH 环境变量里:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

我这里输出的搜索到的是,这个.?.lua;应该是搜索当前目录的。如果当前目录不存在,就需要用.代替斜杠。

;.\?.lua;D:\Lua5.1\lua\?.lua;D:\Lua5.1\lua\?\init.lua;D:\Lua5.1\?.lua;D:\Lua5.1\?\init.lua;D:\Lua5.1\lua\?.luac

如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。lua是loadfile

搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。

搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。c会用loadlib

C 包(吃灰去吧)

Lua和C是很容易结合的,使用 C 为 Lua 写包。

与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。

Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:

local path = “/usr/local/lua/lib/libluasocket.so”
local f = loadlib(path, “luaopen_socket”)

loadlib 函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在Lua中调用他。

如果加载动态库或者查找初始化函数时出错,loadlib 将返回 nil 和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:

local path = “/usr/local/lua/lib/libluasocket.so”
– 或者 path = “C:\windows\luasocket.dll”,这是 Window 平台下
local f = assert(loadlib(path, “luaopen_socket”))
f() – 真正打开库

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。

将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。

元表

Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。

个人感觉吧,元表类是接口,定义了各种操作符方法,但是具体需要你去实现,如果你没有实现也不会强制你实现,就是会报错。

方法对应操作符号我的编程习惯
__add对应的运算符 ‘+’.我喜欢用来对应值相加
__sub对应的运算符 ‘-’.
__mul对应的运算符 ‘*’.
__div对应的运算符 ‘/’.
__mod对应的运算符 ‘%’.
__unm对应的运算符 ‘-’. 这是负号不是减号)
__concat对应的运算符 ‘…’.两个表合成一个表
__eq对应的运算符 ‘==’.
__lt对应的运算符 ‘<’.
__le对应的运算符 ‘<=’.

说直白一点,两个表可以通过操作符+相加,但是怎么加,加的规则是什么,你自己实现。

  • setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
  • getmetatable(table): 返回对象的元表(metatable)

__concat()

Metatable={}
Metatable.__concat=function (a,b)
    for key, value in pairs(b) do
        table.insert(a,value)
    end
    return a
end

A={1,nil,3,4,5}
B={7,8,nil,10,11}
setmetatable(A,Metatable)
setmetatable(B,Metatable)
C=A..B
for key, value in pairs(C) do
    print(key,value)
end

--[[ 
1       1
3       3
4       4
5       5
6       7
7       8
8       10
9       11
结论是:table.insert不会insert nil
]]

__index

--相当于重写table.字段名或者table[字段名]   table[字段名]==table.字段名  当key不存在时,会触发__index
--不要试图在__mytable中递归调用,会栈溢出的,后面需要用value代替,避免递归调用
Metatable.__index = function(mytable, key)
    print(key)
    return "nothing"
  end

print(D["key10"]==nil)
print(D.key1,D.key10)
--[[ 
key10
false
key10
k1      nothing
]]

__newindex

Metatable.__index = function(mytable, key) 
  if(not mytable.values[key]~=nil) then
    return mytable.values[key]
  end
  return nil
end

E = {}
E.values={}
E.Metatable=Metatable
--为了避免递归调用,所以使用value代替其值,E.key相当于E.values[key]
setmetatable(E, E.Metatable)
-- 相当于重写table.字段名或者table[字段名]   table[字段名]==table.字段名  当key不存在时,会触发__index
-- 不要试图在__mytable中递归调用,会栈溢出的
E.key11 = "123"
print(E.key11)

__call

Metatable.__call = function(mytable, key) 
  error("MyError:not function")
  
end

E()

协程

这货。。。。感觉吧,压根都不是多线程,区别在于这货是可以暂停的再继续执行。

概念

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。

线程和协同程序区别

线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。

在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。

协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

注意:由于Lua中的协程无法在外部将其停止(也就是说只能内部yield),而且有可能导致程序阻塞

函数介绍

img

corotine.create(arg)

用来首次启动或再次启动一个协程,使其由挂起状态变成运行状态。也可以这么说,resume函数相当于在执行协程中的方法。参数Val1…是执行协程co时传递给协程的参数。
(1) 首次调用resume执行协程co时,参数Val1…会赋值给协程co的函数,作为函数参数

(2) 以后再调用resume执行协程co时,参数Val1…会赋值给协程co中上一次yield的返回值

resume函数的返回有3种情况:

(1) 如果协程co的函数执行完毕,协程正常终止,resume 返回 true和函数的返回值。

(2) 如果协程co的函数执行过程中,协程让出了(调用了yield()方法),那么resume返回true和协程中调用yield传入的参数。

(3) 如果协程co的函数执行过程中发生错误,resume返回false与错误消息。

可以看到resume无论如何都不会导致程序崩溃。它是在保护模式下执行的

**注意:**如果一个协程发生错误结束或正常运行结束。那么就处于dead状态,这时候如果调用resume()的话会直接返回false,且报错"cannot resume dead coroutine"

coroutine.yield()函数

使正在执行的协程挂起,注意是执行该函数中会使协程挂起,该函数并未执行结束,下次resume()时才会执行完毕

(1) yeild的参数会作为resume的第二个返回值

(2) 该协程再次执行resume,resume函数传入的参数将会作为yield的返回值

(3)再次执行的时候,会从yield()中开始执行

示例

co = coroutine.create(
    function(j)
        for i = 1, j, 1 do
            print(i);
            if(i==5)then
                print("yield");
                local yield_arg = coroutine.yield("yield return arg")
                print("resume again"..yield_arg)
            end
        end
        
    end
)
 
CoExecutionResult,arg=coroutine.resume(co, 10)
print(CoExecutionResult,arg)
print("co Stop And Begin")
coroutine.resume(co, " ARG")
CoExecutionResult,arg=coroutine.resume(co, 10)
print(CoExecutionResult,arg)
print(coroutine.status(co))  -- dead

[[
1
2
3
4
5
yield
true    yield return arg
co Stop And Begin
resume again ARG
6
7
8
9
10
false   cannot resume dead coroutine
dead
]]

协程状态

suspended:挂起状态,协程刚创建完成时或者yield之后

running :运行状态,如果在协程的函数中调用status,传入协程自身的句柄,那么执行到这里的时候才会返回running状态

normal :如果协程A resume() 协程B时,则协程A处于的状态为normal。在协程B的执行过程中,协程A就一直处于normal状态。因为它这时候既不是挂起状态、也不是运行状态

dead :结束状态,如果一个协程发生错误结束或正常运行结束。那么就处于dead状态,这时候如果调用resume()的话会直接返回false,且报错"cannot resume dead coroutine"

生产者和消费者(等我搞定IO先)

IO

r以只读方式打开文件,该文件必须存在。
w打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
a以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
r+以可读写方式打开文件,该文件必须存在。
w+打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a+与a类似,但此文件可读可写
b二进制模式,如果文件是二进制文件,可以加上b
+号表示对文件既可以读也可以写
--操作语句
file = io.open (filename [, mode])
--open函数返回一个文件的句柄。如果发生错误,则返回nil,以及一个错误信息和错误代码

io.read()

“*all”从当前位置读取整个文件,若为文件尾,则返回空字串
“*line”[默认]读取下一行的内容,若为文件尾,则返回nil
“*number”读取指定字节数的字符,如果number为0则返回空字串,若为文件尾,则返回nil;
读取num个字符到串

IO的API

image-20210507225125886

简单模式

普通操作

-- 以只读方式打开文件
File=io.open("text.txt","r")

-- 设置默认输入文件为 test.lua
io.input(File)

-- 输出文件第一行
print(io.read())

-- 关闭打开的文件
io.close(File)


-- 以附加的方式打开只写文件
File = io.open("text.txt", "a")

-- 设置默认输出文件为 test.lua
io.output(File)

-- 在文件最后一行添加 Lua 注释
io.write("-- this is an annotation")

-- 关闭打开的文件
io.close(File)


-- 以w的方式打开文件,会删除原文件
File = io.open("text.txt", "w")

-- 设置默认输出文件为 test.lua
io.output(File)

-- 在文件最后一行添加 Lua 注释
io.write("-- IO w Mode")

-- 关闭打开的文件   
io.close(File)

+操作

-- 以r+方式打开文件
File=io.open("text.txt","r+")

io.input(File)

-- 输出文件第一行
print(io.read())
io.write("1111")     --这里我write会写道控制台当中,如果没有指定io.output会在控制台输出
-- 关闭打开的文件
io.close(File)

-- 以w+方式打开文件
File=io.open("text.txt","w+")

io.output(File)
-- 输出文件第一行
io.write(io.read())  --如果没有指定io.input会在控制台输入,控制台输入东西可以写入文件


-- 关闭打开的文件
io.close(File)

-- 以a+方式打开文件
File=io.open("text.txt","a+")

io.output(File)
-- 输出文件第一行
io.write(io.read())  --如果没有指定io.input会在控制台输入,控制台输入东西可以写入文件


-- 关闭打开的文件
io.close(File)

--指定后input和output,才能达到在同一文件上读取和写入
--感觉r+应该和a+一模一样,r+也是从头部进行添加的,前提是你没有动指针
InFile=io.open("TextIn.txt","r+")
io.input(InFile)
io.output(InFile)
local a=io.read()
print(a)
io.write(a)

io.close(InFile)

b操作 不明,不知道如何体现出来

-- 以r+方式打开文件
File=io.open("text.txt","r+b")

io.input(File)

-- 输出文件第一行
print(io.read())
io.write("1111") 
-- 关闭打开的文件
io.close(File)

完全模式

通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name 来代替 io.function_name 方法。以下实例演示了如何同时处理同一个文件:

-- 以只读方式打开文件
file = io.open("test.lua", "r")

-- 输出文件第一行
print(file:read())

-- 关闭打开的文件
file:close()

-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")

-- 在文件最后一行添加 Lua 注释
file:write("--test")

-- 关闭打开的文件
file:close()

image-20210508104414489

File=io.open("TextIn.txt","r+")

print(File:read())

for line in File:lines() do 
    print(line)
end

Index,ErrorResult= File:seek("cur",0)
print(Index,ErrorResult)
File:write("\nBBBBBBBBBBBBBBBBBB")


File:close()

--TextIn.txt
[[
31233123312331233123312331233123aaaaa
BBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBB
]]
--控制台输出
[[
31233123312331233123312331233123aaaaa
BBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBB
97      nil
]]

其他

image-20210508112954256

image-20210508114203591

image-20210508114212692

会输出一些当前文件夹中的东西,可是如果我调用一下flush那就不会。

有人分析如下,但是感觉还是不太到位。

image-20210508114300824

错误处理(我喜欢叫异常处理,lua好奇葩啊)

Assert

assert(bool arg1, string arg2)

如果arg1为false,那么就抛出异常

error

语法格式:

error (message [, level])

功能:终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回)

通常情况下,error会附加一些错误位置的信息到message头部。

Level参数指示获得错误的位置:

  • Level=1[默认]:为调用error位置(文件+行号)
  • Level=2:指出哪个调用error的函数的函数 (我看不出来)
  • Level=0:不添加错误位置信息
function ErrorFun0()
    error("level0",0)
end
function ErrorFun1()
    error("level1")
end
function ErrorFun2()
    error("level2",2)
end

image-20210508134430581

image-20210508134446742

image-20210508134502317

异常捕获

pcall(lua为什么你总是这么特殊,唉)

传入参数,以 保护模式 调用函数 f 。 这意味着 f 中的任何错误不会抛出; 取而代之的是,pcall 会将错误捕获到,并返回一个状态码。 第一个返回值是状态码(一个布尔量), 当没有错误时,其为真。 此时,pcall 同样会在状态码后返回所有调用的结果。 在有错误时,pcall 返回 false 加错误消息。

HasError,ErrorMessger =pcall(ErrorFun1)

print(HasError)

print(ErrorMessger)

xpcall

function HandleFun(ErrorMsg)
    print("Error:"..ErrorMsg)
end
HasError =xpcall(ErrorFun1,HandleFun)
print(HasError)

debug

function HandleFun2()
    print("-----------"..debug.traceback())
end
xpcall(ErrorFun1,HandleFun2)

image-20210508153604716

模拟面向对象

image-20210505100240391

image-20210505100851878

. 与 : 的区别在于使用 : 定义的函数隐含 self 参数,使用 : 调用函数会自动传入 table 至 self 参数

简单实例

Preson = {}

--构建函数
function Preson:New()
    Object={}
    setmetatable(Object, self)
    self.__index = self --以后Object调用.实际上是获得元表self的值
    self.Name = "JAY"
    self.Weight = 55
    return Object
end

function Preson:Spreak() print("我的名字是", self.Name) end
function Preson:Info()
    print("自我介绍")
    print(self.Name)
    print(self.Weight)
    self:Spreak()
end

A = Preson:New()
B = Preson:New()
A.Name = "V"
A:Info()
B:Info()
-- 元类
Shape = {area = 0}

-- 基础类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end

-- 基础类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)

myshape:printArea()

继承

-- Meta class
Shape = {area = 0}
-- 父类构造类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end
-- 父类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

--子类继承
Square = Shape:new() 				--这继承。。。。。牛皮
-- Derived class method new
function Square:new (o,side)     
  o = o==Shape or Shape:new(o,side)   
  setmetatable(o, self)
  self.__index = self
  return o
end

多继承(好无语)

lua的多继承原理即在一个表中查找不到,则到多个表中进行查询

垃圾回收

Mark-and-Sweep垃圾回收算法

Mark and Sweep Algorithm(标记清除算法)

一般而言,垃圾回收算法都会包含两个基本操作。操作1,检测所有不可达对象;步骤2,回收不可达对象所占用的堆内存。

Mark and Sweep Algorithm(标记清除算法)在下面两个阶段执行这两个操作:

1)Mark phase(标记阶段)

2)Sweep phase(清除阶段)

Mark phase(标记阶段)

在对象创建时,设置它的标记位为0(false)。在Mark phase(标记阶段),我们设置所有可达对象的标记位为1。这一设置过程可以通过图遍历算法来完成,比如深度优先遍历算法。我们可以将每个对象看作图的一个结点,从每一个可达的结点出发,访问和它相连的没有被访问过的结点,并将这些结点标记为可达,然后递归访问与这些结点相连的没有被访问过的结点。

伪代码如下:

  • root变量引用了一个可以被局部变量直接访问的对象。这里我们假设只有一个root变量。
  • markedBit(obj)用来访问一个对象的标记位。
Mark(root)
    If markedBit(root) = false then
        markedBit(root) = true
        For each v referenced by root
             Mark(v)

提示:如果有多个root变量,对每个root变量调用Mark()即可。

Sweep phase(清除阶段)

这一阶段将回收所有不可达对象所占用的堆内存(注意这里是直接遍历堆内存)。所有标记位为false的对象,会被清除,其余标记位为true的对象,会将其标记为设置为false,为下一次GC做准备。

伪代码如下:

Sweep()
	For each object p in heap
		If markedBit(p) = true then
			markedBit(p) = false
		else
			heap.release(p)

Mark and Sweep Algorithm(标记清除算法)的优点

  • 它可以处理循环引用的情况。(因为他未标记的时候才会遍历其引用)

    image-20210508155748489

  • 算法执行时不存在额外开销。

Mark and Sweep Algorithm(标记清除算法)的缺点

  • 执行算法时需要挂起整个程序的运行。(啥意思)
  • 执行算法多次后,堆内存可能被划分为多个不连续的区域,如下图所示:

img

Lua的垃圾回收函数

垃圾回收器函数

Lua 提供了以下函数**collectgarbage ([opt [, arg]])**用来控制自动内存管理:

  • collectgarbage(“collect”): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:
  • collectgarbage(“count”): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。
  • collectgarbage(“restart”): 重启垃圾收集器的自动运行。
  • collectgarbage(“setpause”): 将 arg 设为收集器的 间歇率。 返回 间歇率 的前一个值。
  • collectgarbage(“setstepmul”): 返回 步进倍率 的前一个值。
  • collectgarbage(“step”): 单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
  • collectgarbage(“stop”): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。

collectgarbage(“setpause”, 200) : 内存增大 2 倍(200/100)时自动释放一次内存 (200 是默认值)。

collectgarbage(“setstepmul”, 200) :收集器单步收集的速度相对于内存分配速度的倍率,设置 200 的倍率等于 2 倍(200/100)。(200 是默认值)

标准函数库

image-20210505162148244

数学函数库

image-20210505162409630

TODO

userdata是什么

thread是什么

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值