Lua学习笔记之迭代器、table、模块和包、元表和协程
  1wMBnbixARwC 2024年02月26日 48 0

迭代器

迭代器是一种对象,它能够来遍历标准库模板容器中的部分或全部元素,每个迭代器对象代表容器中确定的地址,在Lua中迭代器是一种支持指针类型的结构,他可以遍历集合的每一个元素。

泛型for迭代器

泛型for自己内部保存迭代函数,实际上保存三个值:迭代函数、状态常量、控制变量。

泛型for迭代器提供了集合的key/value对,

array = {"Hello","Tony","Chang"}

--for迭代器遍历
for key,value in pairs(array)
do
    print(key,value)
end

结果:

image-20240224110807305

事实上使用Lua默认提供的迭代函数 ipairs,我们常常使用函数来描述迭代器,下面从函数角度分析pairs迭代器运行。

(1). 初始化,获取“in”后面表达式的三个值----迭代函数、状态常量、控制变量(如果表示式返回家过不足三个则使用nil来补齐,如果返回数值多,则自动忽略其余三个的数值)

(2) 将状态常量和控制变量作为参数调用迭代函数。(在for结构来说,状态常量仅仅在初始化时候获取它的数值并传递给迭代函数)

(3)将迭代函数返回的数值赋给变量列表(每次调用函数就会返回集合的下一个元素

(4) 如果返回的第一个数值为nil循环结束,否则执行循环体

(5)回到第二步骤再次调用迭代函数

Lua迭代器的分类:

  1. 无状态的迭代器
  2. 多状态的迭代器

无状态的迭代器

无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。

而每一次的迭代,迭代函数利用两个变量(状态常量和控制变量)的数值作为参数进行调用。

其代表迭代器式ipairs迭代器

--下面实现自定义迭代器来对元素内容进行平方

--自定义迭代函数
function square(iteratorMaxCount,currentNumber)
    if currentNumber<iteratorMaxCount
    then
        currentNumber=currentNumber+1 --控制变量加一
    return currentNumber,currentNumber*currentNumber
    end
end

--作为迭代器调用
for i,n in square,3,0
do
    print(i,n)
end

image-20240224113255926

function iter(array,i)
    i=i+1
    local v=array[i]
    if v then
        return i,v
    end
end

function ipiars(array)
    return iter,array,0  --三个变量 函数、状态常量、控制变量
end

根据这个原理,当Lua调用ipairs(array)时候,获取三个值:迭代函数iter、状态常量array、控制变量的初始值0;然后调用iter(array,0)返回 1,array[1];

第二次调用iter(array,1) 返回2,array[2];直至返回的 n,nil 出现nil元素

多状态的迭代器

很多情况下迭代器需要保存多个状态信息而并不是简单的状态常量和控制变量,最简单的方法是使用闭包。另一种办法式将所有的信息状态封装到table中,将table作为迭代器的状态常量。

array={"Hello","Tony","Chang"}
function elementIterator(collection)
    local index=0
    local count=#collection
    --闭包函数
    return function()
        index=index+1
        if index<=count
        then
            return collection[index]
        end
    end
end

for element in elementIterator(array)
do
    print(element)
end
    	

image-20240224120321753

table(表格)

  1. table索引可以是任意类型,但这个数值不能是nil

  2. table大小不固定,根据自己需要进行扩容

  3. 使用table来解决模块(module)、包(package)、和对象(Object)的。

--table的使用
--声明 
table1={}
table[1]="TonyChang"
--移除引用
table1=nil
--lua的垃圾回收会释放内存

验证table属于一个存在于堆上的对象,(两个索引指向同一个table,最后更改一个索引为空则对象还是存在,被另一个索引所指引着)

test = {}
print("test的类型是",type(test))

test[1]="Tony"
test["FirstName"]="Chang"
print("test[FirstNmae]",test["FirstName"])

testCopy=test
testCopy["FirstName"]="Zhang"
print("通过testCopy进行修改之后-------------")
print("test[FirstNmae]",test["FirstName"])

--释放副本指引指引
testCopy=nil
--仍旧可以通过原指引访问table
print("test[1]")
--进行遍历打印
for key,value in pairs(test)
do
	print(key,value)
end

Table中的操作

--table中的元素的连接
table2={"Hello","everyone","my","name","is","Tony"}
--直接连接
print(table.concat(table2))
--指定连接字符
print(table.concat(table2,", "))
--指定元素索引来连接
print(table.concat(table2,", ",1,2))
print(table.concat(table2,", ",1,3))

image-20240224213050741

--table中的插入和移除
--在末尾插入
table.insert(table2,"Chang")
print(table.concat(table2,", "))
--指定索引处插入
table.insert(table2,2,"everybody")
print(table.concat(table2,", "))
--移除操作(默认删除最后一个元素)
table.remove(table2)
print(table.concat(table2,", "))
--移除指定索引的元素
table.remove(table2,3)
print(table.concat(table2,", "))

image-20240224214130749

--table的排序
table3={"Hello","everyone","my","name","is","Tony"}
print("排序前")
for key,val in pairs(table3)
do
    print(key,val)
end
--排序
table.sort(table3)
print("排序后")
for key,val in pairs(table3)
do
    print(key,val)
end

image-20240224214658230

--新建数组
array={}
--赋值
for i=1,5
do
    array[i]=i*2
end
--遍历查看
for key,val in pairs(array)
do
    print(key,val)
end
--返回所有正key值中的最大值(Lua 5.2之后消失)
--注意是所有索引中最大值 如果没有则返回0
print(table.maxn(array))

image-20240224215652685

模块和包

Lua中的模块类似一个封装库,从Lua5.1开始,Lua加入标砖的模块管理机制,可以把一些公用的代码放在一个文中中,以API接口形式在其它地方进行调用,有利于降低代码耦合度。

--模块
--文件名称为 module.lua
module={}
module.constant="这是一个常量"
function module.func1()
    io.write("这是一个共有函数\n")
end
--私域函数不可以直接访问
local function func2()
    print("这是一个私有函数!")
end
--通过func3函数来访问func2私域函数
function module.func3()
    func2()
end
return module

--通过require函数来加载模块
require("<module>")
require "<module>"

--以别名导入模块
local nick=require("module")

nick.func3()

元表(Metatable)

在table中我们可以通过访问对应的key值来得到value值,但是无法从表的层面直接来操作(例如table1+table2)这样的操作。而如果我们有了元表,就可以根据元表中定义的add加的函数来执行两个table之间的相加操作,因此给普通表table设置metatable元表可以完成table层面的操作!

--元表设置
mytable={}     --普通表
mymetatable={} --元表
setmetatable(mytable,mymetatable) --将mymetatable设置为mytable的原表

--也可以直接设置
mytablb=setmetatable({},{})
--返回对象
getmetatable(mytable) --这里返回mymetatable

以下为元表常用的字段:

  • 算术类元方法: 字段:__add(+), __mul(*), __ sub(-), __div(/), __unm, __mod(%), __pow, (__concat)

  • 关系类元方法: 字段:__eq, __lt(<), __le(<=),其他Lua自动转换 a~=b --> not(a == b) a > b --> b < a a >= b --> b <= a (注意NaN的情况)

  • table访问的元方法: 字段: __index, __newindex

  • __index: 查询:访问表中不存的字段&
    rawget(t, i)

  • __newindex: 更新:向表中不存在索引赋值

    rawset(t, k, v)

-index用来对表访问

如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。

--
table0=setmetatable({key1="value1"},{
        __index = function(table,key)
            if key=="key2" then
                return "metatablevalue"
            else
                return nil
            end
         end
    })
print(mytable.key1,mytable.key2)

image-20240225185225214

-newindex元方法对表进行更新

metatable1={}
table1=setmetatable({key1="value1"},{__newindex=metatable1})

print(table1.key1) -- 打印出value1

table1.key2="val2"
print(table1.key2) --此时未发现有key2 会打印nil
--然后会检查元表中是否有__newindex方法,进行调用新加
--最后打印出val2
print(metatable1.key2)
--再次设置新键
table1.key3="val3"
print(table1.key3)
print(metatable1.key3)
--上三行代码重复验证

image-20240225180317293

image-20240225180908389

--如果不通过table访问不存在的键,则__newindex函数只会调用但不会进行赋值 所以访问table元组
--的新索引值是nil
print(metatable1.key4)
--打印nil

image-20240225181140095

由此可知,当要访问的是缺失的索引值,解释器就会查找__newindex元方法,如果存在则调用这个函数(不进行赋值操作)只有外部访问时候赋值,其才会赋值。

在__newindex中通过调用rawset函数来更新table内容

table6=setmetatable({key1="value1"},{__newindex=function(table,key,value)
        rawset(table,key,"\""..value.."\"")
        end
        })
table6.key1="new value"
table6.key2=6
print(table6.key1)
print(table6.key2)--打印出6 而table的索引对应数值写入进去

image-20240225183444948

此时遍历访问table中内容:

image-20240225184215652

-两table相加

--两个table相加
--在元组中设置__add函数
table1=setmetatable({1,2,3},{__add=function(table1,table2)--双下划线
        for i=1,table.maxn(table2) 
            do
            table.insert(table1,table.maxn(table1)+1,table2[i])
            end
            return table1
        end
    })
secondtable={4,5,6}
table3=table1+secondtable

for k,v in pairs(table3)
do
	print(k,v)
end

image-20240225172340918

模式 描述(对应符号)
__add +
__sub -
__mul *
__div /
__mod %
__unm -
_concat ..
__eq ==
__It <
__Ie <=

__call元方法

call方法在调用一个值时候调用

--定义元方法来和
table4=setmetatable({10},{
        __call=function(table1,table2)
            sum=0
            for i=1,table.maxn(table2) do
                sum=sum+table2[i]
                end
            for i=1,table.maxn(table1) do
                sum=sum+table1[i]
                end
            return sum
        end
    })
table5={12,13,14}
print(table4(table5))--直接传入要操作的对象

image-20240225174552714

协同程序

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

协程和线程的区别?

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

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

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

--协程
--创建协程
coroutineTest=coroutine.create(
	function(i)
        print(i)
    end
)
--启动/重启协程
coroutine.resume(coroutineTest,1)
print(coroutine.status(coroutineTest))--查看协程的状态(挂起、运行、死亡)
print('--------------------------')

--协程创造
--包裹一个函数的协程
coroutineTest2=coroutine.wrap(
	function(i)
        print(i)
    end
)
coroutineTest2(1)--直接调用协程 执行

print("--------------------")

--一个协程中的函数
--打印1,10 
coroutineTest3=coroutine.create(
		function()
        	for i=1,10 do
            	print(i)
            	if i==3 then
                	print(coroutine.status(coroutineTest3))
                	print(coroutine.running())   -- 返回运行的协程
    		    end
            	coroutine.yield() --协程挂起
            end
        end
)

--每次重新执行则检查上次函数执行的中断点
--继续执行循环逻辑
coroutine.resume(coroutineTest3)--打印1
coroutine.resume(coroutineTest3)--打印2
coroutine.resume(coroutineTest3)--打印3 并且打印线程状态 和运行协程的线程编号

print(coroutine.status(coroutineTest3)) --打印状态 suspended挂起状态
print(coroutine.running()) --nil 现在没有协程正在运行

print("-------------------------------")

image-20240225221844677

image-20240225222230873

分析可知:coroutine.running可以得知,协程的底层还是由线程实现的。

当create一个coroutine的时候就是在新线程中注册一个事件,当使用resume出发事件时候,create的

coroutine的函数就被执行,当遇到yield的时候就表示挂起当前线程,当resume触发事件时候,create的coroutine函数继续被执行,当遇到yield的时候就代表挂起当前线程,等候再次被resume唤醒。

案例:

--协程中调用的函数
function sayNum(a)
    print("sayNum:",a)
    return coroutine.yield(2*a)
end

coroutineTest5=coroutine.create(function(a,b)
    print("第一次协同程执行输出:",a,b)
    local p=sayNum(a+1)
    print("第二次协同程序执行输出:",p)
    local p,q=coroutine.yield(a+b,a-b)
    
    print("第三次协同程序执行输出:",p,q)
    return b,"结束协程"
    end)
print("main",coroutine.resume(coroutineTest5,1,10))
print("--------------------------------------------")
print("main",coroutine.resume(coroutineTest5,"r"))
print("--------------------------------------------")
print("main",coroutine.resume(coroutineTest5,"x","y"))
print("--------------------------------------------")

image-20240225230452064

分析:

  1. 调用resum 协程唤醒(第一次执行) resume唤醒成功返回true,(若唤醒失败则返回false)(yield函数返回参数内容)
  2. 协程运行
  3. 运行到yield语句挂起(在sayName()函数中)
  4. 被挂起之后,第一次resume唤醒(第一个参数是要唤醒的协程,剩余的参数是接下来执行语句参数)
  5. 遇到yield语句挂起,返回yield参数的内容(a+b,a-b)
  6. 再次resume唤醒,继续执行打印语句(resume第二个函数是打印语句中的参数)
  7. 遇到return 协程中函数逻辑执行完毕!协程死亡

resume和yield的配合强大之处在于--resume处于主城中,它将外部状态(数据)传入到程序(协程)内部;

yield将内部状态(数据)返回到主程序中。

使用协程来实现生产者--消费者问题

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2024年02月26日 0

暂无评论

推荐阅读
  oXKBKZoQY2lx   2024年03月19日   35   0   0 游戏开发
  oXKBKZoQY2lx   2024年03月23日   43   0   0 游戏开发
  1wMBnbixARwC   2024年03月01日   41   0   0 游戏开发
  1wMBnbixARwC   2024年03月09日   59   0   0 游戏开发
  1wMBnbixARwC   2024年03月06日   25   0   0 游戏开发
  398vzLiyyaJn   2024年03月13日   24   0   0 游戏开发
1wMBnbixARwC