魔兽世界编程宝典读书笔记(4-2)
时间:2020-08-13 分类:Lua 作者:编程之家
表也可以用于另外一种“面向对象编程”的编程方式,这种方式是基于对象的概念进行的一种编程方式。对象既
包括 了数据,也
包括 了对这些数据的操作(专业术语把操作叫做“
方法 ”)。对应于Lua,数据就是指各种变量,而
方法 就是指特定的
函数 。而通过前面的讲述,大家已经看到,表既可以赋值为变量,也可以赋值为
函数 。
@H_
404 _7@
4.4.1
创建非面向对象计数器
为了
显示 面向对象的威力,我们先来看一段非面向对象的
代码 。我们写
一个 计数器:
> do
>>@H_404 _7@ -- 私有变量counter
>>
>>@H_404 _7@ -- 公有的
函数 ,完成获得counter的值
>>@H_404 _7@ counter_get=function()
>>@H_404 _7@ return counter
>>
>>@H_404 _7@ counter_inc=function()
>>@H_404 _7@ counter=counter+1
>> end
上面这个计数器是可以正常工作的:
> counter_inc()
> print(counter_get())
1
> counter_inc()
> counter_inc()
> counter_inc()
> print(counter_get())
4
这段
代码 创建了
一个 简单的单向计数器,它不能后退,但可以通过
函数 counter_get()和counter_inc()分别
获取 值和递增值。但是,这里只有
一个 计数器,而很多情况下,我们需要大量的计数器,于是,我们可能需要重复这些
代码 许多许多遍。声明很多很多的变量,这些变量名不能相同,由于变量名的不同,所有的
获取 值和递增值的
函数 也要全部重复很多遍。所以大家看出这样写法在
功能 上是很有局限的,而且
代码 上也做了不必要的重复。
下面是计数器的另一种实现方式,我们使用
一个 表来定义变量和
函数 (
@H_
404 _7@
默 然说话:对的,表既可以装变量,也可以装函数 ,所以,我们可以在一个 表里既装变量,也装函数 )
> counter={
>> }
> counter.get=function(self)
>> return self.count
>> end
> counter.inc=function(self)
>> self.count=self.count+1
>> end
该程序允许我们执行以下语句:
> print(counter.get(counter))
0
> counter.inc(counter)
> print(counter.get(counter))
1
在这 个实现中,实际的计数器变量存储在了
一个 表中。与值交互的每
一个 函数 都有
一个 名为self的参数,我们可以通过以下的
代码 很容易就创建第二个计数器:
> counter2={
>> count=15,
>> get=counter.get,
>> inc=counter.inc,
>> }
> print(counter2.get(counter2))
15
上面我们在
调用 get()和inc()
方法 的时候,我们都是使用了对应的对象作为参数,这样的写法感觉有点麻烦(同
一个 对象名打了两遍),Lua提供了
一个 简单的方式让我们能够少打一遍对象名,就是把counter.get(counter)写为counter:get()。这样,counter对象就会
自动 作为第
一个 参数被传给了get()
方法 。
这个计数器程序看上去仍然很笨拙。我们可以定义
一个 更健壮的计数器系统:
> do
>>@H_404 _7@ local get=function(self)
>>@H_404 _7@ return self.count
>>@H_404 _7@ local inc=function(self)
>>@H_404 _7@ self.count=self.count+1
>>@H_404 _7@ new_counter=function(value)
>>@H_404 _7@ if type(value)~="number" then
>> @H_404 _7@ count=value,
>> end
这个样例提供了
一个 名为new_counter的全局
函数 ,它只有
一个 参数,即计数器的初始值。它返回
一个 对象,该对象包含两个
方法 和计数器的初始值。这个
函数 就是典型的工厂
函数 ,它负责生产计数器对象,只要你传递给它
一个 计数器的初始值,它就返回
一个 计数器对象给你,每个对象都
包括 两个
方法 ,
一个 可以获得计数器当前值,另
一个 进行计数。下面运行一些测试
代码 以验证系统是否正常工作:
> counter=new_counter()
> print(counter:get())
0
> counter2=new_counter(15)
> print(counter2:get())
15
> counter:inc()
> print(counter:get())
1
> counter2:inc()
> print(counter2:get())
16
这就是面向对象带来的好处,
代码 比较简单,却完成了强大的
功能 ,当然由于
代码 被大量的重用了,在理解上也带来了一定的难度。但与在
功能 的强大和
代码 简单的优点相比,这点难度又算得了什么呢?
Lua中,我们可以对表的key-value对执行操作,访问key对应的value,遍历所有的key-value。但是我们不可以对两个table执行加操作,也不可以比较两个表的大小,除非,你使用了
Meta tables对它们如何进行相关操作
Meta tables允许我们改变表的行为,例如,使用
Meta tables我们可以定义Lua如何计算两个table的相加操作a+b(
@H_404 _7@默 然说话:这部分内容 非常象C++ @H_404 _7@里的运算符重载。)。
在Lua中,任何
一个 表在开始的时候都没有
Meta table,你可以使用set
Meta table()来将任何的表定义为别的表的
Meta table,换句话:你可以将你定义的任何表提升为
一个 父亲,让别的表做它的儿子:
> tbl1={"a","b","c"}
> tbl2={"d","e","f"}
> tbl3={}
> mt={}
大家都看到了set
Meta table()的使用了,它有两个参数:
@H_566_
502 @
@H_
404 _7@l tbl----等待
添加 Meta table的表。
@H_566_
502 @
@H_
404 _7@l mt----
Meta table
> print(getMeta table(mt))
nil
> print(getMeta table(tbl1)==mt)
true
定义
Meta table
方法 也就是重写
Meta table中已经存在的
方法 ,让它们具备我们所期望的
功能 。
Meta table
方法 很多,参数各不相同,但每个
方法 都是两个下划线开头(
@H_
404 _7@
默 然说话:下面的列表及代码 中,为了能让大家看到两个下划线,我在两个下划线之间多加了一个 空格,你们在写代码 的时候是不应该加这个空格的,因为变量名已经规定,空格是不能作为变量或函数 名的一部分的,这里加空格仅仅是为了显示 上的需要)。这里只介绍WoW中比较常用的
方法 。如果你想了解得更详细可以参考《Lua编程(Programming in Lua)》(
@H_
404 _7@
默 然说话:这本书只有
@H_404 _7@200 @H_404 _7@多页,它比较详细介绍了Lua@H_
404 _7@,如果以前没有任何编程经验的同学,可以先看一下这本书,这会获得一些有益的帮助,有编程经验的同学也可以看一下以便更详细的了解Lua)。
参数个数
描述
_ _add
2
定义
+
运算符的行为
_ _mul
2
定义
*
运算符的行为
_ _div
2
定义
/
运算符的行为
_ _sub
2
定义
-
运算符的行为
_ _unm
1
定义负号的行为
_ _tostring
1
_ _concat
2
定义
..
运算符的行为
_ _index
2
定义表如何检索下标的行为
_ _newindex
3
定义表如何创建下标的行为
1.
使用_ _add
,_ _sub
,_ _mul
,_ _div
定义自己的加减乘除
我记得有一句话是这么说的:在编程世界,程序员就是上帝,而这里所提供的四个
函数 就是最明显的写照。加减乘除不一定非要按照我们平时认为的那种方式去运算,只要我们能正确的表达我们期待的计算方式,那么计算机就会按照我们的要求在进行加减乘除的运算。当然,还是有一些限制需要我们注意。
@H_566_
502 @
@H_
404 _7@l 每个算术
方法 都带有两个参数,返回
一个 值,返回值的类型可以是任意类型。
@H_566_
502 @
@H_
404 _7@l 在重新定义算术
方法 的时候,应该考虑多重运算的情况,换句话:
一个 表达式的运算结果很可能是另
一个 更大的表达式的一部分。
还是来看个例子吧。下面的
函数 重新定义了两个表的加运算(
@H_
404 _7@
默 然说话:默 认情况下,两个表是不能进行加运算的),意思就是把第二个表的数据和第
一个 表的数据进行合并,形成
一个 新表。
> mt._ _add=function(a,b)
>> local result=setMeta ble({},mt)
>> for i=1,#a do
>>@H_404 _7@ table.insert(result,a[i])
>> end
>> for i=1,#b do
>>@H_404 _7@ table.insert(result,b[i])
>> end
>> --返回result表
>> return result
>> end
这个
函数 的参数a和b就是两个表对象。第一行
代码 新建了
一个 空的表,并设置
Meta table为mt。之后就是将a表和b表的全部
内容 都复制到新的表中,也就是完成我们期待的将a表和b表的
内容 合并的
功能 ,下面是对上面的
代码 进行的测试,这里我们使用了运算符+:
> newtbl=tbl1+tbl2
> print(#newtbl)
6
> for i=1,#newtbl do
>> print(newtbl[i])
>> end
张三
李四
王五
苹果
香蕉
梨
2.
使用_ _unm
定义负运算
在数学里,
一个 数的负运算就是取这个数据的相反数,而这里不仅仅要操作数字,我们要操作的是表,所以我们希望出现的是:当在
一个 表前面加
一个 负号的时候,会将这个表里的所有元素进行倒序排序,下面的
代码 将实现这个想法:
> mt._ _unm=function(a)
>> local result=setMeta table({},mt)
--将a表倒序输出 ,并将每一个 元素都装到result表中
>> for i=#a,1,-1 do
>>@H_404 _7@ table.insert(result,a[i])
>> end
>> return result
>> end
上面的
代码 思路和重写加法运算的思路大同小异,也是创建
一个 新的表result,然后倒着循环a表,将a表的中每个元素取出来装到result中,然后再返回result。下面是测试
代码 :
> newtbl2=-newtbl
> for i=1,#newtbl2 do
>>@H_404 _7@ print(newtbl2[i])
>> end
梨
香蕉
苹果
王五
李四
张三
在前面,每次我们都要写
一个 循环才能看到表中的每个元素,这显得很麻烦,有没有什么简单办法来完成这个过程呢?在面向对象的语言中,我们都知道有
一个 叫tostring的
方法 是用来完成这个作用的:产生
一个 可用的简单字符串
输出 。
在写
代码 之前,我们不妨先试试下面的
代码 ,看看,如果我直接print()
一个 表对象会发生什么:
> print(tbl1)
table: 003CB7C8
> print(tbl2)
table: 003CBA18
> print(newtbl)
table: 003C6850
> print(newtbl2)
table: 004626E8
> print(t)
nil
从上面的
代码 我们可以看出:直接打印
一个 表对象,得到的是
一个 table:开头,后面跟着八位十六进制数字的字符串
输出 形式,如果这个表不存在,它将
输出 一个 nil(最后的那个print(t)就是这样的情况),这就是
默 认的_ _tostring()定义的情况。我们来把它改为我们希望的情况:以“{”开头,以“}”结束,中间是所有的表元素,每个表元素之间以逗号分开,开头第
一个 元素之前不能有逗号,最后
一个 元素之后也不能有逗号,
代码 类似这样:
> mt.__tostring=function(a)
>>@H_404 _7@ local result="{"
>>@H_404 _7@ for i=1,#a do
>>@H_404 _7@ result=result .. ","
>>@H_404 _7@ result=result .. tostring(a[i])
>>@H_404 _7@ result=result .. "}"
>>@H_404 _7@ return result
>> end
tostring
函数 同样有
一个 参数:a表,里面也写了
一个 循环,但与前面两个
函数 不同的地方,它的返回值是
一个 字符串,而不是
一个 表对象,所以这里的result是
一个 字符串。我们使用循环取出a表中的每
一个 元素,并按我们希望的格式进行了字符串连接,最后返回了result,下面是测试
代码 :
> print(tbl1)
{张三,李四,王五}
> print(tbl2)
{苹果,香蕉,梨}
> print(newtbl)
{张三,王五,苹果,梨}
> print(newtbl2)
{梨,张三}
> print(t)
nil
现在我们不再看到那个table:的格式
输出 了,而是按照我们预想的格式将表中的所有元素进行了
输出 。在比较复杂的对象出,能这样
输出 一个 字符串将会非常的有用处。
5.
使用_ _index
在后备表中浏览
在前面我们提到过,如果在一张表中使用了
一个 没有值键来索引这张表的元素,那将会看到
一个 nil的值,下面的
代码 可以说明这一点:
> print(tbl1[4])
nil
nil
其实我们可以改变这一现象,因为Lua中做了如下规定:在使键去寻找值的过程中,如果可以在表中找到值,那么就直接返回值,如果找不到值,就去
调用 _ _index这个
Meta table
方法 ,由_ _index决定
输出 什么。
这个在实际中非常有用,比如我们有很多的窗口,它们具体不同的x,y坐标(在屏幕上的
显示 位置不一样),但我们希望这些窗口都具有相同的大小,如果我们直接来写,可能是下面这样:
> form1={
>> width=100,
>> height=100,
>> x=23,
>> y=10
>> }
> form2={
>> width=100,
>> x=25,
>> y=10
>> }
> form3={
>> width=100,
>> x=35,
>> y=10
>> }
这样也能完成我们希望的工作,但是我们会发现
一个 很大的问题,由于我们希望这些窗口都具备相同的宽和高,那么一旦程序要进调整所有这些窗口的宽和高,那就要把每
一个 窗口对象 的宽和高都调整一遍,这样很麻烦,而且容易出错。要是这些宽和高都独立放在一张表中,然后使用类似继承的方式,让我们所有的窗口都继承这张只
包括 宽和高的表,这样,如果要
修改 ,我们就只需要
修改 这张只
包括 宽和高的表就可以了。这时,我们就可以使用_ _index来完成我们的希望:
> form={
>> width=100,
>> }
然后创建两个三个代表窗口的表,它们只
包括 x,y坐标,并为它们都指定
Meta table:
> form1={
>> x=1,
>> y=10
>> }
> form2={
>> x=101,
>> y=10
>> }
> form3={
>> x=201,
>> y=10
>> }
这个
方法 比较特别,它有两种定义的方式,它可以指定为一张表(这里所有的
Meta table
方法 中唯
一个 可以指定为表的
方法 ,其它的都只能指定为
函数 ),也可以直接指定为
一个 函数 。如果你指定为一张表,那么,当你所索引的键在窗口中找不到时,它就会到_ _index所指定的那张表中去找,并返回找到的值。如果指定的是
一个 函数 ,那就直接返回这个
函数 的返回值。
指定为表:
> mt._ _index=form
> print(form1.width)
100
> print(form2.width)
100
> print(form3.width)
100
这个
功能 很类似于面向对象语言中的继承。这样一来,我们要进行
修改 宽和高也会变得非常容易了:
> form.width=50
> print(form1.width)
50
> print(form2.width)
50
> print(form3.width)
50
只要
修改 了form的width,form1,form2还有form3的宽都跟着变化了。
我们也可以使用
函数 的形式来实现与上面一样的
功能 ,这个
函数 返回
一个 字符串,有两个参数,第
一个 是表,第二个是键:
> mt.__index=function(a,key)
>>@H_404 _7@ return form[key]
>> end
将上面的
代码 敲入解释器中,并再次运行前面的测试
代码 ,
输出 form1、form2、form3的宽,你会发现与前面所使用的
代码 得到的
效果 是完全一致的。
大家应该明显可以看出,指定
一个 表绝对比指定
一个 函数 在
代码 方面要简单的得多。不过使用
函数 还能做更有趣的事情,我们试试下面的
代码 :
> mt.__index=function(a,key)
>>@H_404 _7@ if form[key] ~=nil then
>>@H_404 _7@ return form[key]
>>@H_404 _7@ return " 我不知道你要寻找什么"
>> end
上面这段
代码 加入了
一个 判断语句,如果这个键能在form中找到对应的值,那么就返回这个值,如果找不到,那就会返回
一个 出错信息:“我不知道你要寻找什么”:
> print(form3.width)
50
> print(form3.width2)
我不知道你要寻找什么
上面的测试
代码 已经表明,当第二个打印语句意外的将width打错的时候,
输出 的不再是
一个 让人迷惑的nil,而一句很人性化的出错信息。
6.
使用_ _newindex
增强对表赋值的处理
_ _index
方法 是在表中查找
一个 键的值却找不到的时候被
调用 ,而_ _newindex则是在
一个 表中插入新的键—值对之前被
调用 。也就是说,如果你重写了_ _newindex
方法 ,那么它就执行这个
方法 所做的规定,而不再是简单的完成键—值的赋值操作了。_ _newindex有三个参数:第
一个 参数是需要索引的表,第二个参数是需要
添加 的键,第三个参数是需要赋值的值。看个例子:
> mt.__newindex=function(a,key,value)
>> if value=="**" or value=="操" then
>>@H_404 _7@ rawset(a,"@#$!")
>> else
>>@H_404 _7@ rawset(a,value)
>> end
>> end
这是一段很简单的,用来
屏蔽 某些关键字的
代码 ,如果你插入
一个 新的键,并且给这个新键的值是“**”或者”操”,那么它将被取代为"@#$!"。rawset()
函数 是
一个 让你完成
默 认的键—值赋值操作的
函数 :
> form1.name="**"
> print(form1.name)
@#$!
本章介绍了表的方方面面,下一章我们将继续学习关于Lua
函数 的高级特征。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。