首页 > 其他分享 >Lua学习笔记之迭代器、table、模块和包、元表和协程

Lua学习笔记之迭代器、table、模块和包、元表和协程

时间:2024-02-25 23:44:56浏览次数:26  
标签:__ 元表 协程 迭代 -- end Lua print table

迭代器

迭代器是一种对象,它能够来遍历标准库模板容器中的部分或全部元素,每个迭代器对象代表容器中确定的地址,在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将内部状态(数据)返回到主程序中。

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

标签:__,元表,协程,迭代,--,end,Lua,print,table
From: https://www.cnblogs.com/TonyCode/p/18033355

相关文章

  • UVA12422 (Kengdie) Mua (II) - Expression Evaluator 题解
    题目传送门闲话蒟蒻的第一篇黑题题解!连着花了\(12\)个小时才做出来,打代码\(6\)小时,调试\(6\)小时。一开始怎么编也编不过,直到看到了tiger大神的题解才豁然开朗。思路本题主要是输出函数或运算式子的结果,最重要的就是判断优先级。tiger大神提出了表达式树法和递归......
  • golang中协程&管道&锁
    进程和线程进程(Process)就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间。一个进程至少有5种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态,通......
  • lua小小实战的资料
    Lua实现JSON解析器http://www.manongjc.com/detail/25-ozepzazdsivhrxe.htmlhttps://blog.51cto.com/u_15072927/3936779游戏中的排行榜Lua设计(简单实现,线段树,跳表)https://codeleading.com/article/15992061562/LuaJSON解析与序列化https://blog.csdn.net/wx771720/art......
  • Unity xLua开发环境搭建与基础进阶
    Unity是一款非常流行的游戏开发引擎,而xLua是一个为Unity开发者提供的Lua框架,可以让开发者使用Lua语言来进行游戏开发。在本文中,我们将介绍如何搭建UnityxLua开发环境,并进行基础进阶的学习。 对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正......
  • 打造纯Lua组件化开发模式:Unity xLua框架详解
    在传统的Unity开发中,通常会使用C#来编写游戏逻辑和组件。但是,随着Lua在游戏开发中的应用越来越广泛,我们可以将游戏逻辑和组件完全用Lua来实现,实现纯Lua的组件化开发模式。这样做的好处是可以更加灵活地修改游戏逻辑,而不需要重新编译C#代码。 3.实现步骤对啦!这里有个游戏开......
  • Lua学习笔记
    Lua学习笔记lua的基本语法和数据类型在Lua中,最重要的线程是协同程序(coroutine)它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其它协同程序共享全局变量和其它大部分东西。线程和协程的区别:线程可以同时运行多个,而协程任意时刻只能运行一个,并且处于运行......
  • Go - argument evaluation with defer
        ......
  • 【Go-Lua】Golang嵌入Lua代码——gopher-lua
    嵌入式8篇文章0订阅订阅专栏Lua代码嵌入GolangGo版本:1.19首先是Go语言直接调用Lua程序,并打印,把环境跑通packagemainimportlua"github.com/yuin/gopher-lua"funcmain(){ L:=lua.NewState() deferL.Close() //go err:=L.DoString(`print("gogogo!")`) iferr!=n......
  • hello_lua
    https://github.com/norman/hello-luahello_lua2libnativefunc.c#include<lua.h>#include<lauxlib.h>staticintl_mult50(lua_State*L){doublenumber=luaL_checknumber(L,1);lua_pushnumber(L,number*50);return1;}intluaopen......
  • OpenResty 介绍与实战讲解(nginx&lua)
    目录一、概述二、OpenResty安装三、OpenResty的工作原理四、OpenResty核心模块1)ngx_lua模块2)ngx_stream_lua模块3)ngx_http_lua_module模块4)ngx_http_headers_more模块5)ngx_http_echo模块6)ngx_http_lua_upstream模块7)ngx_http_redis模块8)ngx_http_proxy_connect_module......