《Lua程序设计》-- 学习9

迭代器和泛型for

迭代器和闭包

迭代器(iterator)是一种可以让我们遍历一个集合中所有元素的代码结构。在Lua语言中,通常使用函数表示迭代器:每一次调用函数时,函数会返回集合中的“下一个”元素。

一个闭包就是一个可以访问其自身的环境中一个或多个局部变量的函数。这些变量将连续调用过程中的值并将其保存在闭包中,从而使得闭包能够记住迭代所处的位置

一个简单的迭代器:

在这个例子中,values就是工厂。每当调用这个工厂时,它就会创建一个新的闭包(即迭代器本身)。这个闭包将它的状态保存在其外部的变量t和i中,这两个变量也是由values创建的。每次调用这个迭代器时,它就从列表t中返回下一个值。在遍历完最后一个元素后,迭代器返回nil,表示迭代结束。

泛型for的语法

泛型for在循环过程中在其内部保存了迭代函数。实际上,泛型for保存了三个值:一个迭代函数、一个不可变状态(invariant state)和一个控制变量(control variable)

无状态迭代器

无状态迭代器(stateless iterator)就是一种自身不保存任何状态的迭代器。因此,可以在多个循环中使用同一个无状态迭代器,从而避免创建新闭包的开销。

迭代的状态由正在被遍历的表(一个不可变状态,它不会在循环中改变)及当前的索引值(控制变量)组成。

还有一种创建迭代器的方式可以让迭代器进行实际的迭代操作。当使用这种迭代器时,就不再需要编写循环了。相反,只需要调用这个迭代器,并传入一个描述了在每次迭代时迭代器需要做什么的参数即可。更确切地说,迭代器接收一个函数作为参数,这个函数在循环的内部被调用,这种迭代器就被称为真正的迭代器(true iterator)。举一个更具体的例子,让我们使用这种风格再次重写allwords迭代器:

马尔可夫链算法

print("please Input N (size for the sequence of previous words):")
local N = tonumber(io.read())
while math.type(N) ~= "integer" doprint("Invalid type, ReInput N:")N = tonumber(io.read())
endfunction allwords ()local line = io.read() -- current linelocal pos = 1 -- current position in the linereturn function()-- iterator functionwhile line and line ~= "" do-- repeat while there are lineslocal w, e = string.match(line, "(%w+[,;.:]?)()", pos)if w then-- found a word?pos = e -- update next positionreturn w -- return the wordelseline = io.read() -- word not found; try next linepos = 1 -- restart from first positionendendreturn nil -- no more lines: end of traversalend
endfunction prefix (words)return table.concat(words," ")
endlocal statetab = {}function insert (prefix, value)local list = statetab[prefix]if list == nil thenstatetab[prefix] = { value }elselist[#list + 1] = valueend
endlocal MAXGEN = 200
local NOWORD = "\n"-- build table
local wordTable = {}
for i = 1,N dowordTable[N] = NOWORD
end
print("\nPlease Input the words:")
for nextword in allwords() doinsert(prefix(wordTable), nextword)for i = 1,#wordTable dowordTable[i] = wordTable[i + 1]endwordTable[N] = nextword
end
insert(prefix(wordTable), NOWORD)-- generate text
for i = 1,N dowordTable[N] = NOWORD
end
for i = 1, MAXGEN dolocal list = statetab[prefix(wordTable)]-- choose a random item from listlocal r = math.random(#list)local nextword = list[r]if nextword == NOWORD thenreturnendio.write(nextword, " ")for i = 1,#wordTable dowordTable[i] = wordTable[i + 1]endwordTable[N] = nextword
end

pil4/chapter19/chapter19.lua at master · 0kk470/pil4 (github.com)

元表和元方法

元表可以修改一个值在面对一个未知操作时的行为

例如,假设a和b都是表,那么可以通过元表定义Lua语言如何计算表达式a+b。当Lua语言试图将两个表相加时,它会先检查两者之一是否有元表(metatable)且该元表中是否有__add字段。如果Lua语言找到了该字段,就调用该字段对应的值,即所谓的元方法(metamethod)(是一个函数)

可以认为,元表是面向对象领域中的受限制类。像类一样,元表定义的是实例的行为。不过,由于元表只能给出预先定义的操作集合的行为,所以元表比类更受限;同时,元表也不支持继承

Lua语言中的每一个值都可以有元表。每一个表和用户数据类型都具有各自独立的元表,而其他类型的值则共享对应类型所属的同一个元表。Lua语言在创建新表时不带元表:

可以使用函数setmetatable来设置或修改任意表的元表:

在Lua语言中,我们只能为表设置元表;如果要为其他类型的值设置元表,则必须通过C代码或调试库完成

字符串标准库为所有的字符串都设罝了同一个元表,而其他类型在默认情况中都没有元表

一个表可以成为任意值的元表;一组相关的表也可以共享一个描述了它们共同行为的通用元表;一个表还可以成为它自己的元表,用于描述其自身特有的行为。总之,任何配置都是合法的。

算术运算相关的元方法

现在,假设想使用加法操作符来计算两个集合的并集,那么可以让所有表示集合的表共享一个元表。这个元表中定义了这些表应该如何执行加法操作。首先,我们创建一个普通的表,这个表被用作集合的元表:

然后,修改用于创建集合的函数Set.new。在新版本中只多了一行,即将mt设置为函数Set.new所创建的表的元表:

在此之后,所有由Set.new创建的集合都具有了一个相同的元表:

最后,向元表中加入元方法(metamethod)__add,也就是用于描述如何完成加法的字段:

此后,只要Lua语言试图将两个集合相加,它就会调用函数Set.union,并将两个操作数作为参数传入。

 关系运算相关的元方法

元表还允许我们指定关系运算符的含义,其中的元方法包括等于(__eq)、小于(__lt)和小于等于(__le)。其他三个关系运算符没有单独的元方法,Lua语言会将a~=b转换为not(a==b),a>b转换为b<a,a>=b转换为b<=a。

部分有序是指,并非所有类型的元素都能够被正确地排序。例如,由于Not a Number(NaN)的存在,大多数计算机中的浮点数就不是完全可以排序的。

标准规定任何涉及NaN的比较都应返回假,这就意味着NaN<=x永远为假,x<NaN也为假。因此,在这种情况下,a<=b到not(b<a)的转化也就不合法了。

在集合的示例中,我们也面临类似的问题。<=显而易见且有用的含义是集合包含:a<=b通常意味着a是b的一个子集。然而,根据部分有序的定义,a<=b和b<a可能同时为假。因此,我们就必须实现__le(小于等于,子集关系)和__lt(小于,真子集关系):

表相关的元方法

算术运算符、位运算符和关系运算符的元方法都定义了各种错误情况的行为,但它们都没有改变语言的正常行为。Lua语言还提供了一种改变表在两种正常情况下的行为的方式,即访问和修改表中不存在的字段。

__index元方法

对表的访问会引发解释器查找一个名为__index的元方法。如果没有这个元方法,那么像一般情况下元素不存在的情况一样,结果就是nil;否则,则由这个元方法来提供最终结果。

---[[
--创建具有默认值的原型
prototype = {x = 0,y = 0,width = 100,height = 100}local mt = {} --创建一个元表
--声明构造函数
function new(o)setmetatable(o,mt)return o
endmt.__index = function (_,key )return prototype[key]
endw = new{x = 10,y = 20}
print(w.width)
--]]

输出100

Lua语言会发现w中没有对应的字段"width",但却有一个带有__index元方法的元表。因此,Lua语言会以w(表)和"width"(不存在的键)为参数来调用这个元方法。元方法随后会用这个键来检索原型并返回结果。

在Lua语言中,使用元方法__index来实现继承是很普遍的方法。虽然被叫作方法,但元方法__index不一定必须是一个函数,它还可以是一个表当元方法是一个函数时,Lua语言会以表和不存在的键为参数调用该函数,正如我们刚刚所看到的。当元方法是一个表时,Lua语言就访问这个表。因此,在我们此前的示例中,可以把__index简单地声明为如下样式:

mt.__index  =  prototype

如果我们希望在访问一个表时不调用__index元方法,那么可以使用函数rawget。调用rawget(t,i)会对表t进行原始(raw)的访问,即在不考虑元表的情况下对表进行简单的访问

__newindex元方法

元方法__newindex与__index类似,不同之处在于前者用于表的更新而后者用于表的查询。当对一个表中不存在的索引赋值时,解释器就会查找__newindex元方法:如果这个元方法存在,那么解释器就调用它而不执行赋值。像元方法__index一样,如果这个元方法是一个表,解释器就在此表中执行赋值,而不是在原始的表中进行赋值。此外,还有一个原始函数允许我们绕过元方法:调用rawset(t,k,v)来等价于t[k]=v,但不涉及任何元方法

具有默认值的表

一个普通表中所有字段的默认值都是nil。通过元表,可以很容易地修改这个默认值:

 跟踪对表的访问

假设我们要跟踪对某个表的所有访问。由于__index和__newindex元方法都是在表中的索引不存在时才有用,因此,捕获对一个表所有访问的唯一方式是保持表是空的。如果要监控对一个表的所有访问,那么需要为真正的表创建一个代理(proxy)。这个代理是一个空的表,具有用于跟踪所有访问并将访问重定向到原来的表的合理元方法

---[[
function track(t)local proxy = {} --'t'的代理类--为代理创建元表local mt = {__index == function(_,k)print("*access to element" .. tostring(k))return t[k]  --访问原来的表 end,__newindex = function ( _,k,v )print("*update of element" .. tostring(k) .. " to " .. tostring(v))t[k] = v --更新原来的表end,__pairs = function( )return function(_,k)  --迭代函数local nextkey,nextvalue = next(t,k)if nextkey ~= nil then --避免最后一个值print("*traversing element" .. tostring(nextkey))endreturn nextkey,nextvalueendend,__len = function() return #t end}setmetable(proxy,mt)return proxy
endt = {}
t = track(t)
t[2] = "hello"
print(t[2])--]]

只读的表

面向对象(Object-Oriented)编程

从很多意义上讲,Lua语言中的一张表就是一个对象。首先,表与对象一样,可以拥有状态。其次,表与对象一样,拥有一个与其值无关的的标识(self -- 类似于 this 指针);特别地,两个具有相同值的对象(表)是两个不同的对象,而一个对象可以具有多个不同的值;最后,表与对象一样,具有与创建者和被创建位置无关的生命周期。

对象有其自己的操作。表也可以有自己的操作,例如:

上面的代码创建了一个新函数,并将该函数存入Account对象的withdraw字段。

不过,在函数中使用全局名称Account是一个非常糟糕的编程习惯。首先,这个函数只能针对特定对象工作。其次,即使针对特定的对象,这个函数也只有在对象保存在特定的全局变量中时才能工作。如果我们改变了对象的名称,withdraw就不能工作了:

这种行为违反对象拥有独立生命周期的原则。

另一种更加有原则的方法是对操作的接受者(receiver)进行操作。因此,我们的方法需要一个额外的参数来表示该接受者,这个参数通常被称为self或this

此时,当我们调用该方法时,必须指定要操作的对象:

通过使用参数self,可以对多个对象调用相同的方法:

Lua语言可以使用冒号操作符(colon operator)隐藏self参数。使用冒号操作符,我们可以将上例重写为a2:withdraw(260.00):

我们可以使用点分语法来定义一个函数,然后用冒号语法调用它,反之亦然,只要能够正确地处理好额外的参数即可:

类(Class)

我们可以参考基于原型的语言(prototype-based language)中的一些做法来在Lua语言中模拟类,例如Self语言(JavaScript采用的也是这种方式)。在这些语言中,对象不属于类。相反,每个对象可以有一个原型(prototype)。原型也是一种普通的对象,当对象(类的实例)遇到一个未知操作时会首先在原型中查找。要在这种语言中表示一个类,我们只需要创建一个专门被用作其他对象(类的实例)的原型对象即可。类和原型都是一种组织多个对象间共享行为的方式。

如果有两个对象A和B,要让B成为A的一个原型,只需要:

即,Lua语言调用了原来的deposit函数,传入了a作为self参数。因此,新账户a从Account继承了函数deposit。同样,它还从Account继承了所有的字段。

继承(Inheritance)

多重继承(Multiple Inheritance)

这种实现的关键在于把一个函数用作__index元方法。请注意,当一个表的元表中的__index字段为一个函数时,当Lua不能在原来的表中找到一个键时就会调用这个函数。基于这一点,就可以让__index元方法在其他期望的任意数量的父类中查找缺失的键。

多重继承意味着一个类可以具有多个超类。因此,我们不应该使用一个(超)类中的方法来创建子类,而是应该定义一个独立的函数createClass来创建子类。函数createClass的参数为新类的所有超类

--在表‘plist’的列表中查找‘k’
local function search( k,plist )for i = 1,#plist dolocal v = plist[i][k]  --尝试第‘i’个超类if v then return v end end
endfunction ccreateClass( ... )local c = {} --新类local parents = {...}  --父类列表--在父类列表中查找类缺失的方法setmetatable(c,{__index = function(t,k)return search(k,parents)end })--将‘c’作为其实例的元素c.__index = c--为新类定义一个新的构造函数function c:new(o)o = o or {}setmetatable(o,c)return oendreturn c  -- 返回新类
end

私有性(Privacy)

一个表用来保存对象的状态,另一个表用于保存对象的操作(或接口)。我们通过第二个表来访问对象本身,即通过组成其接口的操作来访问。为了避免未授权的访问,表示对象状态的表不保存在其他表的字段中,而只保存在方法的闭包中。例如,如果要用这种设计来表示银行账户,那么可以通过下面的工厂函数创建新的对象:

首先,这个函数创建了一个用于保存对象内部状态的表,并将其存储在局部变量self中。然后,这个函数创建了对象的方法。最后,这个函数会创建并返回一个外部对象,该对象将方法名与真正的方法实现映射起来。这里的关键在于,这些方法不需要额外的self参数,而是直接访问self变量。由于没有了额外的参数,我们也就无须使用冒号语法来操作这些对象,而是可以像普通函数那样来调用这些方法:

这种设计给予了存储在表self中所有内容完全的私有性。当newAccount返回后,就无法直接访问这个表了,我们只能通过在newAccount中创建的函数来访问它

单方法对象(Single-method Object)

上述面向对象编程实现的一个特例是对象只有一个方法的情况。在这种情况下,可以不用创建接口表,只要将这个单独的方法以对象的表示形式返回即可。诸如io.lines或string.gmatch这样的内部保存了状态的迭代器就是一个单方法对象。

虽然使用这种方式不能实现继承,但我们却可以拥有完全的私有性:访问单方法对象中某个成员只能通过该对象所具有的唯一方法进行。

对偶表示(Dual Representation)

实现私有性的另一种有趣方式是使用对偶表示(dual representation)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/658958.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SD-WAN有哪些组网方式?

随着企业网络需求的不断增长和变化&#xff0c;SD-WAN作为一种先进的网络架构技术&#xff0c;提供了多种灵活的组网方式&#xff0c;以适应不同企业的需求。本文将介绍SD-WAN常见的几种组网方式&#xff0c;帮助企业更好地理解如何利用SD-WAN构建高效的网络。 1、点对点&#…

离线使用Element UI和Vue

需要依赖如下&#xff1a; 1.vue.js; 2.index.js(Element UI) 3.index.css(Element UI) 4.element-icons.ttf(Element UI字体) 5.element-icons.woff(Element UI图标) 下载链接如下&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1nGOi0Vm_xExRGmVp6oVLoA 提取…

【笔记】React-Native跟Android交互--简单示例

/** * 使用命令 npx react-nativelatest init DemoRN创建项目 * * "react": "18.2.0", * "react-native": "0.73.2" * * 官网有详细教程&#xff1a;https://reactnative.dev/docs/native-modules-android */ 一、RN invoke androi…

【读点论文】SPTS Single-Point Text Spotting

SPTS Single-Point Text Spotting ABSTRACT 现有的场景文本识别(即&#xff0c;端到端文本检测和识别)方法依赖于昂贵的边界框注释(例如&#xff0c;文本行&#xff0c;词级或字符级边界框)。我们首次证明&#xff0c;训练场景文本识别模型可以通过对每个实例的单点进行极低成…

线性代数----------学习记录

线性代数发展历程 &#xff08;1&#xff09;线性方程组&#xff1a;例如二元一次方程组&#xff1b; &#xff08;2&#xff09;行列式&#xff1a;determinant,克莱默&#xff0c;莱布尼兹&#xff1b; &#xff08;3&#xff09;矩阵&#xff1a;方程个数与未知数的个数可…

黑马程序员前端web入门:新浪新闻

黑马程序员前端web入门&#xff1a;新浪新闻 几点学习到的&#xff1a; 设置li无圆点: list-style: none;设置a无下划线&#xff1a;text-decoration: none;a属于行内元素&#xff0c;高度hegiht不起作用&#xff0c;可以设置 display: block; 把它变成块元素。此时&#xff0c…

万户 ezOFFICE wpsservlet SQL注入漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

操作系统-调度算法-1(先来先服务算法 短作业优先 高响应比优先 )

文章目录 总览先来先服务算法例题 短作业优先例题&#xff08;非抢占式的短作业优先调度算法&#xff09;例题&#xff08;抢占式的短作业优先调度算法&#xff09;注意 FCFS和SJF两种算法的思考高响应比优先例题 小结 总览 先来先服务算法 不会导致饥饿&#xff1a;只要该进程…

拥抱变局,坚韧向新|复旦大学-华盛顿大学EMBA项目C20毕业典礼

12月初&#xff0c;复旦大学-华盛顿大学EMBA项目20班的学员们前往美国&#xff0c;完成了项目最后一次移动课堂&#xff0c;并在奥林商学院举办了毕业典礼。      20班的学员们在项目20周年之际入学&#xff0c;也是疫情以来第一个正式恢复线下授课的班级。虽然经历了一些波…

C语言基础13

今天是学习嵌入式相关内容的第十四天&#xff0c;以下是今日所学内容 1.结构体: 1.结构体类型定义 2.结构体变量的定义 3.结构体元素的访问 4.结构体的存储 内存对齐 结构体整体的大小必须为最大基本类型长度的整数倍 5.结构体作为函数参数 值传递 练习:定…

【pdf密码】怎么打印加密的PDF文件?

PDF文件是可以打开查看的&#xff0c;但是现在不能编辑、不能打印&#xff0c;功能栏中的功能都是灰色的&#xff0c;这种设置了加密的PDF文件该如何加密&#xff1f; 如果PDF中的大多数功能按钮以及打印按钮都是灰色的状态&#xff0c;那就证明是文件的问题导致不能打印的。 …

sqlmap的使用

2024.1.31 sqlmap支持五种不同的注入模式&#xff1a; 1、布尔盲注2、时间盲注3、报错注入4、联合注入5、堆叠注入 检测注入 GET请求的基本格式 ​python sqlmap.py -u <测试网址> Ps:不知道为什么我的sqlmap使用时前面要加python&#xff0c;而大部分其他教程没提到…

React、React Router、JSX 简单入门快速上手

React、React Router、JSX 简单入门快速上手 介绍特点 JSX使用js表达式渲染列表样式控制注意事项 入门脚手架创建react项目安装目录介绍入口文件解析 组件解析介绍函数式组件类组件 事件绑定注意点定义使用事件对象事件处理函数接收额外参数 组件状态状态的定义使用 组件通信父…

Mac 终端可以使用yarn,但是vscode里面报错segmentation fault

Mac 终端可以使用yarn 但是vscode里面报错segmentation fault 查阅官网https://www.yarnpkg.cn/getting-started/install 在vscode运行corepack enable即可解决该问题

嵌入式-stm32-江科大-OLED调试工具

文章目录 一&#xff1a;OLED调试工具1.1 OLED显示屏介绍1.2 实验&#xff1a;在OLED显示屏的使用1.3 自己新增功能测试道友&#xff1a;今天没有开始的事&#xff0c;明天绝不会完成。 一&#xff1a;OLED调试工具 1.1 OLED显示屏介绍 学习任何一门语言就需要进行调试&#…

Spark SQL的高级用法

一. 快速生成多行的序列 需求:请生成一列数据, 内容为 1 , 2 , 3 , 4 ,5 -- 快速生成多行的序列 -- 方式一 select explode(split("1,2,3,4,5",",")); --方式二 /*序列函数sequence(start,stop,step):生成指定返回的列表数据[start,stop]必须传入,step步…

付费使用GitHub Copilot半年,我残了吗?

有些程序员担心使用AI编程助手来写程序,自己会残掉 代码都不写了,你还是程序员吗? 手生脚慢,你怕是只会写hello word了吧? 接下来我用自己的亲身经验,告诉你答案。 ---- 购买记录 ---- 2023年7月17号,我付费100美金购买了一年Copilot,下一次续费的日期是今年的7月1…

防火墙详解

一、基本定义 所谓“防火墙”是指一种将内部网和公众访问网&#xff08;如Internet&#xff09;分开的方法&#xff0c;它实际上是一种建立在现代通信网络技术和信息安全技术基础上的应用性安全技术&#xff0c;隔离技术。越来越多地应用于专用网络与公用网络的互联环境之中&a…

PYTHON蓝桥杯——每日一练(简单题)

题目 利用字母可以组成一些美丽的图形&#xff0c;下面给出了一个例子&#xff1a; ABCDEFG BABCDEF CBABCDE DCBABCD EDCBABC 这是一个5行7列的图形&#xff0c;请找出这个图形的规律&#xff0c;并输出一个n行m列的图形。 输入格式 输入一行&#xff0c;包含两个整数…

AttributeError: module ‘cv2.gapi.wip.draw‘ has no attribute ‘Text‘

解决方法&#xff1a; pip install opencv-python4.6.0.66问题原因&#xff0c;安装的版本不对&#xff08;或是安装了cv2&#xff09;