第5章 高级函数和控制结构

来源:互联网 发布:数据共享平台 编辑:程序博客网 时间:2024/04/29 11:26

魔兽世界编程读书笔记(5)


 

第5章             高级函数和控制结构
5.1        多值返回
在Lua中,return语句能返回多个值,这些值能让我们更轻松地完成一些工作。例如在WoW中,我们有时必须把十六进制的字符串转化为红绿蓝三色十进制值。
5.2        将十六进制转换成RGB
十六进制字符串的典型例子是“FFCC99”它们两个字符一组,分别代表红色(FF),绿色(CC),蓝色(99)。所以我们需要先截取字符串,string.sub()可以完成这个要求,然后再转化为数字,我们可以用tonumber()。
我们的转换函数定义如下:
> convertHexToRGB=function (hex)
>>   local red=string.sub(hex,1,2)
>>   local green=string.sub(hex,2,3)
>>   local green=string.sub(hex,3,4)
>>   local blue=string.sub(hex,5,6)
>>   red=tonumber(red,16)/255
>>   green=tonumber(green,16)/255
>>   blue=tonumber(blue,16)/255
>>   return red,green,blue
>> end
稍微说明一下两个函数的语法。string.sub()有三个参数,第一个是要被截取的字符串,第二个参数是开始下标,第三个参数是结束下标,返回值就是截取后的子字符串。而tonumber()有两个参数,第一个是要被转换为数字的字符串,第二个是可选参数,如果没写则默认转化一个十进制字符串为十进制数字,如果写了,则按指定的进制(上面代码中的16就是说按16进制来理解第一个参数)来理解第一个参数字符串,并将它转换为十进制数字。返回值就是转换后的数字。
下面看看测试情况:
> print(convertHexToRGB("FFCC99"))
1       0.8     0.6
> print(convertHexToRGB("FFFFFF"))
1       1       1
> print(convertHexToRGB("000000"))
0       0       0
5.3        指定多个值
如果要接收有多个返回值的函数,可以使用下面的语法:
var1,var2,var3,var4=somefunction()
somefunction()被调用,它的第一个返回值给var1,第二个返回值给var2,以此类推。如果返回值多于变量,则其它的返回被忽略。
5.4        返回值丢失
多个返回值在一些特殊的情况下会丢失:
如果函数调用所得的多个返回值是另外一个函数的最后一个参数,或者是多指派表达式中的最后一个参数时,所有的返回值将被传入或使用,否则,只有第一个返回值被使用或指定。
先来看函数的例子:
> print("青苹果","梨",convertHexToRGB("FFFFFF"))
青苹果        1       1       1
可以看到convertHexToRGB()的三个返回值都被print打印出来了,但是如果换个位置,不把这个函数当作print()的最后一个参数,你会发现只会打印一个返回值了。
> print("青苹果",convertHexToRGB("FFFFFF"),"梨")
青苹果 1      
然后再来看看多指派表达式的情况:
> a,b,c,d="青苹果",convertHexToRGB("FFFFFF")
> print(a,b,c,d)
青苹果 1       1       1
a,b,c,d四个变量都接收到了值。但如果换一下次序:
> a,b,c,d=convertHexToRGB("FFFFFF"),"青苹果"
> print(a,b,c,d)
1       青苹果 nil     nil
我们再一次看到,convertHexToRGB()的三个返回值,只有一个被接收到了。后两个变量都没有值。
5.5        WoW中的多个返回值
WoW中的一些API函数返回多个值,如GetRaidRosterInfo()以角色的团队索引(一个数字)作为输入,返回下面的信息:
角色名称
角色在团队中的级别
角色所属子群
角色等级
角色职业
角色职业
角色所在区域名
角色是否在线
角色是否死亡
角色是主战士或主辅助
角色是否为物品分配人员
函数返回值有多个的时候,我们不一定每一个都需要,这时就带来一个问题,我们如何去获得我们想要的那几个值呢?
1.使用哑变量
你会看到类似下面的用法:
> _,g=convertHexToRGB("FFFFFF")
第一个变量的名字就叫_,很奇怪的变量名。但是想想也没问题,因为下划线是合法的标识符,所以用它作为变量是没有问题的,而且由于这样的名字通常都不会在程序里正常使用,所以我们用它来接收那些对我们无用的返回值,这就叫哑变量。实际上这样的使用方式并不被认为是一种好的方法。
2.使用select()函数
select()函数用来解决这个问题,它允许你指定从第几个返回值开始取值。这个函数可以接受任意数目的参数,其中第一个参数用来决定函数的行为,当第一个参数为“#”时,select()简单返回其余参数的个数,当第一个参数为一个数字时,select()返回其余从这个位置开始直到最后一个的参数:
> print(select("#","a","b","c"))
3
> print(select(1,"a","b","c"))
a       b       c
> print(select(2,"a","b","c"))
b       c
> print(select(3,"a","b","c"))
c
5.6        接受可变数目的参数
类似select()这样的函数,它可以接收可变数目的参数,我们现在就来说说如何实现这样的情况。
5.7        声明变参函数
带有可变参数的函数简称变参函数,它在函数声明中使用三个点(…)来标明,它可以接收任意数目的参数。
在Lua中,三个点(…)可以作为函数声明的最后一个参数,用于声明可选的参数。一旦三个点(…)在函数体中使用,在变参空位中提供的参数就会代替它。比如我们想做一个测试的print()函数,它在一开头会打印“测试:”两个字符,可以这样写:
> test_print=function(...)
>>   print("测试",...)
>> end
这个函数接受任意数量的参数,然后将其传递给print()函数,并在最前面添加了“测试”两个字符。运行该函数的输出结果如下:
> test_print("苹果","香蕉","梨")
测试    苹果    香蕉   
当函数运行时,我们可以用以下的形式来使用三个点(…)
--将参数传入另一个函数
print(…)
--将参数指定给有限数量的变量
var1,var2,var3=…
--将参数指定为一个表中的元素
tbl={…}
这样我们就可以写一个函数,使用统一的格式来创建新表:
> newtable=function(...)
>>   return {...}
>> end
测试一下:
> tbl=newtable("苹果","香蕉","梨")
> for i=1,#tbl do
>>   print(tbl[i])
>> end
苹果
香蕉
5.8        结合select()函数使用…
由于可变参数的数量可以是任意的,所以这时就会带来一个难题:我们如何去确定用户在调用这个函数时输入了几个参数?这个问题我们可以使用select()函数来解决:
> test_inputnumber=function(...)
>> local num=select("#",...)
>> print("你输入了" .. num .. "个参数")
>> end
这是一个单纯的,仅仅测试你输入了几个参数的函数,我们可以测试一下:
> test_inputnumber(1,2,3,4,5,6)
你输入了6个参数
有了这个信息,我们就可以通过for循环来获得你所你输入的所有参数的值:
> test_select=function(...)
>>   for i=1,select("#",...) do
>>     print(i,(select(i,...)))
>>   end
>> end
在这个for循环中我们使用了两次select(),第一次使用select()获得了参数的个数,第二次使用select()作为print()函数的第二个参数,为了做到每次只输出一个值,我们又在select()的外面又打了一对小括号(默然说话:你可以试着把select()外面的那一对小括号去掉,看看会发生什么。)。看看测试结果:
> test_select("星期一","星期二","星期三","星期四","星期五","星期六")
1       星期一
2       星期二
3       星期三
4       星期四
5       星期五
6       星期六
5.9        范型for循环和迭代器
第3章我们学习了for循环,我们可以利用for循环对数组进行遍历。也就是那些下标是数字的表,我们可以很容易的进行循环遍历,但我们要知道表是一个键—值对的形式,它并不止接受数字一种情形,另外,由于是键—值形式,所以我们也认为它们不是一个连续排序的数据,也就是无次序的数列,这种表我们可以叫它为散列表。for循环对这种无次序的数据也提供了处理的方法,这就是范型for循环。
5.10    范型for语句的语法
范型for循环的语法与前面的for循环在语法上是有区别的:
for <变量列表> in <表达式> do
<循环体>
end
这个for循环的执行过程是这样的:
第一步:先计算表达式的值,这个表达式必须返回三个值:迭代函数,状态常量,控制变量。
第二步:将状态常量和控制变量传入迭代函数,并调用迭代函数。
第三步:将迭代函数的返回值依次赋值给in前面的变量列表。
第四步:如果迭代函数的第一个返回值为nil,则循环终止
第五步:重复第二步,再次调用迭代函数。
在Lua中已经有了现成的迭代函数,除非有需要,否则我们并不需要去自已编写迭代函数。所以,后面的内容将介绍给大家如何来使用Lua提供的迭代函数。
5.11    遍历表的数组部分
ipairs()是Lua提供给我们用于遍历一个表的数组部分的函数。下面是它的一个使用示例:
> tbl={"苹果","香蕉","梨"}
> for index,value in ipairs(tbl) do
>>   print(index,value)
>> end
1       苹果
2       香蕉
3      
相对于,直接使用数值的方式的for循环,我们可以看到这个基于ipairs()函数的循环在书写上显得更简单些。
5.12    遍历完整的表
pairs()是一个功能更强大的函数,它可以遍历一个表中的所有元素,无论是数组,还是键—值,还是数组和键—值的混合,它都能遍历出来,看例子:
> tbl={
>>     "苹果",
>>     "香蕉",
>>     "梨",
>>     width=100,
>>     height=100,
>> }
> for key,value in pairs(tbl) do
>>   print(key,value)
>> end
1       苹果
2       香蕉
3      
height 100
width   100
5.13    表的清除
通过前面的程序我们可以看出,pairs()函数返回的两个值,第一个是键,第二个是值。所以,我们就可以利用这一点完成对一个表的所有属性进行清除:
> for key,value in pairs(tbl) do
>>   tbl[key]=nil
>> end
--测试清除效果
> print(tbl[width])
nil
5.14    其他的迭代器
Lua里有很多函数都可以产生迭代器函数,string.gmatch()就是其中之一,它可以通过Lua的正则表达式模式匹配产生一个匹配字串的迭代器。在第6章,我们会介绍更多,这里只是举个例子:
> for word in string.gmatch("这是 一个 句子","%S+") do
>> print(word)
>> end
这是
一个
句子
5.15    对表的数组排序
表结构内置的table.sort()函数可以使用默认的方式对数字和字符串数据进行排序。如果你希望按照你的想法进行排序,那你可以把你的想法写成一个函数,然后传给table.sort(),它就会按照你的想法来完成排序。这个函数的思路,就是要告诉table.sort()排序时的两个数,哪一个更大些。
5.16    定义样例数据
在这里我们要定义一些简单的数据以进行排序:
> guid={}
> table.insert(guid,{
>>     name="默然的老婆",
>>     class="牧师",
>>     level=80,
>> })
> table.insert(guid,{
>>     name="默然的儿子",
>>     class="战士",
>>     level=18,
>> })
> table.insert(guid,{
>>     name="真默然",
>>     class="猎人",
>>     level=2,
>> })
这是一个表里又装了表的例子,数据显示得比较的复杂了,排序自然也就会变得复杂。因为现在装在guid里的每一个对象都有三个属性,姓名,职业,等级。
5.17    默认的排序顺序
在默认的情况下,我们会看到guid表中是按它们的插入顺序进行排序的:
> for key,value in pairs(guid) do
>>   print(key,value.name)
>> end
1       默然的老婆
2       默然的儿子
3       真默然
5.18    创建比较函数
如果我们直接使用table.sort()函数,它会报错,因为它不知道应该怎么对guid里的三个对象进行排序:
> table.sort(guid)
attempt to compare two table values
stack traceback:
        [C]: in function 'sort'
        stdin:1: in main chunk
        [C]: ?
所以,就需要我们来指究竟如何进行排序,也就是写一个比较函数,这个函数有两个参数,这两个参数就类似于我们表中的任意两个对象,我们在这个函数中来指明如何判断对象的大小,那么table.sort()就能知道如何对表进行排序:
> sortLevel=function(a,b)
>>   return a.level<b.level
>> end
我们这里随意的指定了按对象的等级进行排序,a和b就是guid表中的任意两个对象,我们在sortLevel中对它们的level属性进行了比较,并返回了它们的比较结果,tabl.sort()就可以利用这个比较结果来完成排序,只要象下面这样写:
> table.sort(guid,sortLevel)
然后再次用for循环输出:
> for key,value in pairs(guid) do
>>   print(key,value.name)
>> end
1       真默然
2       默然的儿子
3       默然的老婆
我们看到,已经表中的对象已经改变了顺序。
5.19    小结

本章主要介绍了变参函数,范型for循环以及对复杂数组的数据排序的概念。这些概念相对较高级,但是在设计和编写一个新插件时常常遇到。下一章我们将讨论Lua标准库,在这个库中我们将学习如何充分利用Lua提供给我们的帮助来完成我们想要进行的工作。

                                                                 ——摘自 牟勇的笔记
原创粉丝点击