可变参数
lua函数可以接受可变数目的参数,和C语言类似在函数参数列表中使用三点(...)表示函数有可变的参数。lua将函数的参数放在一个叫arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数
例:
printResult = ""
function test(...)
print ("haveing " .. arg.n .. " param")
for i,v in ipairs(arg) do
printResult = printResult .. tostring(v)
end;
print (printResult)
end
test('a','b','c','d','e')
变长参数
Lua中的函数还可以接受不同数量的实参。例如,在调用print时可以传入一个、二个或多个实参。虽然print
是用C语言编写的,但也可以用Lua编写这种能接受不同数量实参的函数。
下面是一个简单例子,这个函数返回所有参数的总和:
function add(...)
local s=0
for i,v in ipairs{...} do
s=s+v
end
return s
end
print(add(3,4,10,25,12))-->54
具名实参
Lua中的参数传递机制是具有“位置性”的,也就是说在调用一个函数时,实参是通过它在参数表中的位置与形参匹配
起来的。第一个实参的值与第一个形参相匹配,依次类推。但有时通过名称来指定实参也是很有用的。
os.rename,这个函数用于文件改名。通常会忘记第一个参数是表示新文件名还是旧文件名。因此,会希望这个函数能够接受两个具有名称的实参。
rename{old="temp.lua",new="temp1.lua"}
另一方面,将rename改为只接受一个参数,并从这个参数中获取实际的参数:
function rename(arg)
return os.rename(arg.old, arg.new)
end
若一个函数拥有大量的参数,而其中大部分参数是可选的话,这种参数传递风格会特别有用。例如在一个GUI库中,一个用于创建新窗口的函数可能会具有许多的参数,而其中大部分是可选的,那么最好使用具名实参:
w=Window{x=0,y=0,width=300,height=200,
title="Lua",background="blue",
border=true
}
Window函数可以根据要求检查一些必填参数,或者为某些参数添加默认值。假设“_Window”才是真正用于创建窗口的函数,它要求所有参数以正确的次序传入,那么Window函数可以这么写:
function Window(options)
if type(options.titile) ~= "string" then
error("no title")
elseif type(options.width) ~= "number" then
error("no width")
elseif type(options.height) ~= "number" then
error("no height")
end
--其他参数都是可选的
_Window(options.title,
options.x or 0
options.y or 0
options.width,options.height,
options.background or "white",
options.border)
end
有时候一个函数有很多参数,而大部分时候不需要指定众多参数值使用默认即可,下边利用逻辑运算实现参数默认值
function func(a,b,c)
a=a or 10
b=b or 10
c=c or 10
return a,b,c
end
在参数特别多的情况下,记忆参数的顺序会很头大,我们可以使用表模拟命名参数
下边是我写的SkiaUI显示label的函数,结合适用了命名参数和默认参数
function Label(skfm,arg)
local lb=UI.Label(arg.X or 0,arg.Y or 0,arg.W or 50,arg.H or 30,arg.A or 255)
skfm:Add(lb,true)
lb.ForeColor=UI.MeColor(unpack(arg.C or {255,255,255,255}))
lb.FontSize=arg.F or 10
lb:show()
return lb
end
调用时
Label(skfm,{X=10,Y=10,C={100,100,100,1000}})
即可
0 什么是Lua?
Lua本身是以简单优雅为本,着眼于处理那些C不擅长的任务;
Lua本身完全遵循ANSI C而写成,只要有C编译器的地方,Lua便可发挥她的力量;
对那种在产品的生命周期内变化比较多的应用方向,使用Lua可以更方便的适应变化;
Lua有很高的执行效率,统计表明Lua是目前平均效率最高的脚本语言;
Lua的使用者分为三类:使用Lua嵌入到其他应用中的、独立使用Lua的、将Lua和C混合使用的;
1 Lua简单介绍
n 和其他脚本一样,Lua有两种执行方式。
(1) 命令行交互式。即不带参数运行,使用文件结束符退出Ctrl-D in Unix, Ctrl-Z in DOS/Windows,或者调用os.exit()退出。
(2) 调用脚本执行(带脚本文件参数运行)。
(3) 在命令行交互过程中,可以使用dofile函数调用外部的脚本文件来加载需要的library。
n Lua是动态类型语言,变量不用类型定义。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量。访问一个没有初始化的全局变量也不会出错,只是得到结果为 nil。当且仅当一个变量不等于nil时,这个变量才存在。
Lua中有8个基本类型:
nil, boolean, number, string, userdata,function, thread, table
注意:
(1) 函数type可以测试给定变量或值的类型。
(2) 变量没有预定义的类型,每一个变量都可能包含任一种类型的值。
(3) 可以使用函数像使用其他值一样使用(将一个函数赋值给一个变量)。
(4) nil是Lua中特殊的类型,一个全局变量没有被赋值以前默认值为nil;给全局变量赋值为nil可以删除该变量。
(5) boolean有两个取值false和true。Lua中所有的值都可以作为条件。在控制结构的条件中除了false和nil为假,其他值都为真。所以,Lua认为0和空串都是真。
(6) number表示实数,Lua中没有整数。Lua的number可以处理任何长整数不用担心误差。
(7) string指字符的序列。string和其他对象一样,Lua自动进行内存分配和释放,一个string可以只包含一个字母也可以包含一本书,Lua可 以高效地处理长字符串,1M的string在Lua中是很常见的。可以使用单引号或双引号表示字符串。为了风格统一,最好使用一种,除非两种引号嵌套情 况,对于字符串中含有引号的情况还可以使用转义符\来表示。
(8) Lua中的转义序列和C语言中类似,特殊的有:
\[ 左中括号
\] 右中括号
(9) 还可以使用 [[...]] 表示字符串。这种形式的字符串可以包含多行,可以嵌套且不会解释转义序列。这种形式的字符串用来包含一段代码是非常方便的。
(10) Lua会自动在string和number之间自动进行类型转换,当一个字符串使用算术操作符时,string就会被转成数字。反之,当Lua期望一个string而碰到数字时,会将数字转成string。
Note: 尽 管字符串和数字可以自动转换,但两者是不同的,像 10 == "10" 这样的比较永远都是错的(比较的时候,不会自动转换,需要显示转换!)。如果需要显示将string转成数字可以使用函数tonumber(),如果 string不是正确的数字该函数将返回nil。反之,可以调用tostring()将数字转成字符串。
(11) .. 在Lua中是字符串连接符,当在一个数字后面写 .. 时,必须加上空格以防止被解释出错。
(12) Function和其他变量相同,函数可以存储在变量中,可以作为函数的参数,也可以作为函数的返回值。(这个特性给了Lua很大的灵活性:一个程序可以重新定义函数增加新的功能或者为了避免运行不可靠代码创建安全运行环境而隐藏函数)
(13) Userdata可以将C数据存放在Lua变量中,userdata在Lua中除了赋值和相等比较外没有预定义的操作。Userdata用来描述应用程序或者使用C实现的库创建的新类型。
n 任何一门语言都有词法约定。
Lua的标识符约定与C语言相同。
注意:
(1) Lua的保留字,不能当作标识符。
and; break; do; else; elseif; end;false; for; function; if; in; local; nil; not; or; repeat; return; then; true;until; while;
(2) Lua是大小写敏感的。
(3) 单行注释的方法:--
(4) 多行注释的方法:--[[ --]]
n 一些常用的命令行选项
-e 直接将命令传入Lua
-l 加载一个文件
-i 进入交互模式
-PROMPT 内置变量作为交互模式的提示符
注意:
全局变量arg存放Lua的命令行参数。在运行以前,Lua使用所有构造参数构造arg表,脚本名索引为0,脚本的参数从1开始增加。脚本前面的参数从-1开始减少。
n 表达式
Lua中的表达式包括:数字常量、字符串常量、变量、一元和二元运算符、函数调用。
(1) 算术运算符
二元运算符:+ - * /^ 加减乘除幂
一元运算符:- 负值
这些运算符的操作数都是实数。
(2) 关系运算符
< > <= >= == ~=
(3) 逻辑运算符
and or not
注意:
逻辑运算符认为false和nil是假(false),其他为真(0也是真)。
a and b
如果a为false,则返回a,否则返回b
a or b
如果a为true,则返回a,否则返回b
C语言中的三元运算符
A ? B : C
在Lua中可以实现为:
(A and B) or C
not的结果一直返回false或者true。
(4) 连接运算符
.. 两个点
注意:
字符串连接,如果操作数为数字,Lua将数字转成字符串。
n 表的构造(table)
构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。可以直接初始化数组。
注意:
(1) 不推荐数组下标从0开始,否则很多标准库不能使用。
(2) 在构造函数的最后的“,”是可选的,可以方便以后的扩展。
(3) 在构造函数中域分隔符逗号可以用分号代替,通常使用分号来分割不同类型的表元素。
2 基本语法
n 赋值语句
Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。(遇到赋值语句,Lua会先计算右边所有的值,然后再执行赋值操作)
注意:
当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
A. 变量个数 > 值的个数 (按变量个数补足nil)
B. 变量个数 < 值的个数 (多余的值会被忽略)
Note: 如果要对多个变量赋值,必须依次对每个变量赋值(常见错误)。
n 局部变量与代码块
使用local创建一个局部变量。与全局变量不同,局部变量只在被声明的那个代码块内有效。
应该尽可能使用局部变量:
(1) 避免命名冲突;
(2) 访问局部变量的速度比全局变量更快;
可以给block划定一个明确的界限:
do .. end 内的部分(当想更好地控制局部变量的作用域的时候这是很有用的)
n 控制结构语句
(1) if有3种形式
(2) while语句
(3) repeat-until语句
(4) for有2种形式
A: 数值for循环
for var = exp1, exp2, exp3 do
Loop-part
end
for将用exp3作为step从exp1(初始值)到exp2(终止值),执行loop-part。其中,exp3可以忽略,默认step = 1。
注意:
(1) for中的三个表达式只会被计算一次,并且是在循环开始前。
(2) 控制变量var是局部变量自动被声明,并且只在循环内有效。
(3) 如果需要保留控制变量的值,需要在循环中将其保存。
(4) 循环过程中不要改变控制变量的值,那样做的结果是不可预知的。如果要退出循环,使用break语句。
B:范型for循环
for i, val in ipairs(a) do print(v) end
范型for遍历迭代子函数返回的每一个值(val)。
3 函数
注意:
(1) 调用函数的时候,如果参数列表为空,必须使用()表明是函数调用。但是,当函数只有一个参数并且这个参数是字符串或者表结构的时候,()是可选的。
(2) Lua也提供了面向对象方式调用函数的语法,比如o:foo(x)与o.foo(o, x)是等价的。
(3) Lua使用的函数可以是Lua编写的也可以是其他语言编写的,对于Lua程序员来说用什么语言实现的函数使用起来都一样。
(4) Lua函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用nil补足。
(5) Lua函数可以返回多个结果值。比如,string.find,其返回匹配串开始和结束的下标,如果不存在匹配串返回nil。Lua函数中,在return后列出要返回的值的列表即可返回多值。
Note:
return f() 这种类型返回f()返回的所有值;
print((foo())) 可以使用圆括号强制使调用返回一个值;
(6) 函数多值返回的特殊函数unpack,其接受一个数组作为输入参数,返回数组的所有元素。
(7) Lua函数可以是可变参数,和C语言类似在函数参数列表中使用“...”表示函数有可变的参数。Lua将函数的参数放在一个arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数。
(8) 命名参数
再论函数
(1) 闭包
当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界。
(2) 非全局函数(局部函数)
Lua中函数可以作为全局变量也可以作为局部变量。函数作为表table的域:
A: 表和函数放在一起
B: 使用表构造函数
Lib_wcdj = {}
Lib_wcdj.add = function (x, y) return x + yend
Lib_wcdj.subtract = function (x, y) return x -y end
Lib_gerry = {
multiply = function (x, y) return x * y end,
divide = function (x, y) return x / y end
}
print(Lib_wcdj.add(1, 2))
print(Lib_wcdj.subtract(1, 2))
print(Lib_gerry.multiply(2, 3))
print(Lib_gerry.divide(5, 2))
--[[
3
-1
6
2.5
--]]
(3) 正确的尾调用
尾调用是一种类似在函数结尾的goto调用,当函数最后一个动作是调用另外一个函数时,称这种调用为尾调用。例如:g的调用是尾调用
function f(x)
return g(x)
end
f 调用g后不会再做任何事情,这种情况下当被调用函数g结束时,程序不需要返回到调用者f,所以尾调用之后程序不需要在栈中保留关于调用者的任何信息。一些 编译器比如Lua解释器利用这种特性在处理尾调用时,不使用额外的栈,称这种语言支持正确的尾调用。由于尾调用不需要使用栈空间,那么尾调用递归的层次是 可以无限制的,不会导致栈溢出。
注意:
必须明确什么情况属于尾调用。