函数
一、函数简介
函数首先是一个子程序,可通过这段子程序计算并返回一个或多个值。
1、函数首先是作为一个子程序,封装一段可复用的代码。
所以我们可以把一段使用频繁的代码写到函数中。然后在需要使用时调用函数就行了。
2、函数可以接授一个或多个参数,计算并返回一个或多个值。
--用function语句定义函数,end;语句表示函数结束。括号里声明参数名字 function test
(a,b
) --用括号指定形参(参数列表) return a+b,
"哈哈";
--函数中用return语句返回一个或多个值 end;
--语句表示函数结束 c,msg = test
(2,
3);
--括号内部指定实参(传递给函数参数的数据),--c的结果为5,msg的结果为"哈哈" 
与其他编程语言不同的是,在LAScript函数可以有多个返回值。
二、定义函数
定义函数的基本语法
function 函数名字(参数名字列表)
return 返回值列表;
end;
因为函数也是变量,所以我们也可以通过变量赋值语句定义函数。
函数名字 = function(参数名字列表)
return 返回值列表;
end;
调用函数很简单
变量列表 = 函数名字(实际参数列表);
函数名字(实际参数列表);
函数名字 "参数";
函数名字();

形参:函数定义时括号中指定的参数名字列表。

实参:函数调用时括号中包含的实际数据列表。
function test
(a,b
,c) --这里的a,b,c称为形参,
可以将形参看成函数内部的局部变量名字。 return a+b+c;
end;
c,msg = test
(2,
3,4);
--这里的2,3,4 称为实参
实参的数目如果多于形参的数目,多余部份被丢弃。
实参的数目如果少于形能的个数,不足的部份添加nil值。
三、函数局部变量、变量作用域
用local语句声明只能在函数内部使用局部变量,
函数中的局部变量与全局变量命名相同时,使用局部变量,二者并不冲突,各自有自已的作用域
str = "a"
function func()
local str = "b"
win.messageBox(str)
end;
func();
win.messageBox(str)
函数的参数是一个局部变量。参数与全局变量命名相同时,使用参数值,二者并不冲突,各自有自已的作用域
str = "a"
function func(str)
win.messageBox(str)
end;
func("参数");
win.messageBox(str);
如果在函数中的变量没有用local语句声明的变量,使用全局变量
str = "a"
function func()
str = "b"
win.messageBox(str)
end;
func();
win.messageBox(str)

在函数中使用全局变量并不安全,因为全局变量大家都可以访问,定义函数时无法预期到全局变量什么时候会被修改或删除。

尽可能不要在函数中使用全局变量!
四、局部函数
函数可以作为全局变量也可以作为局部变量。局部函数像局部变量一样仅在作用域内有效。
定义局函数的方法一
local function 函数名字(参数名字列表)
return 返回值列表;
end;
定义局函数的方法二
local 函数名字 = function(参数名字列表)
return 返回值列表;
end;

声明为局部函数的递归问题。

如果在局部函数中调用自身,因为局部函数定义时并不知道自已是一个局部函数,这样就会在全局表中找不到自已。
local dg =
function (a
) if a <=
0 then return a
else return dg
(a-
1) -- 出错了找不到全局函数dg endend
win.consoleOpen
()print(dg
(5) )

下面是正确的写法
local dg;
dg =
function (a
) if a <=
0 then return a
else return dg
(a-
1) -- 正确,已经声明了局部变量dg end end
win.consoleOpen
() print(dg
(5) )
五、为table类型的变量定义成员函数
因为函数也是变量,所以函数可以作为table成员,我们也可以通过变量赋值语句定义成员函数。
tab = {};
tab.函数名字 = function(self,参数名字列表)
return 返回值列表;
end
tab.函数名字(tab,参数列表);
我们还可以用下面的方法定义成员函数,作用完全相同。
tab = {};
function tab.函数名字(self,参数名字列表)
return 返回值列表;
end
tab.函数名字(tab,参数列表);
上面的示例中,self参数是可选的,并非必需,如果一定要添加self参数,可以使用冒:号来声明或调用函数。
冒:号放在函数名前面时会隐式的传递一个self参数(隐含的意思是不需要在参数列表中声明、赋值self参数)
tab = {};
function tab:函数名字(参数列表)
win.messageBox(""..type(self));
return 返回值列表;
end;
tab:函数名字(参数列表);
如果需要访问表自身应当使用self参数而不要使用外部变量名,隐含或显示的使用self参数都可以。
因为函数不知道外部变量名什么时候被删除或者指向别的对象。
错误的用法:
tab = {x=0};
function tab.func()
return tab.x;
end;
tab2 = tab;
tab = nil;
tab2.func();
正确的用法
tab = {x=0};
function tab.func(self)
return self.x;
end;
tab2 = tab;
tab = nil;
tab2.func(tab2);
如果你喜欢添加一个参数,还可以通过在函数名前使用冒号隐含的传递self参数。
这也是在LAScript中应用最广泛的一种写法。
tab = {x=0};
function tab:func()
return self.x;
end;
tab2 = tab;
tab = nil;
tab2:func();
六、使用函数的返回值
--定义一个函数
gethw =
function()
return "hello",
"world";
--这个函数有两个返回值
end;
--用多个变量名匹配多个返回值 h,w = gethw
();
--用两个变量名匹配返回值 --结果:等于"hello",w等于"world" --增加变量名只能取到nil值 h,w,w2 = gethw
();
--变量名的个数多于返回值个数,多余的变量赋值为nil --结果:h等于"hello",w等于"world",w2等于nil --
减少变量名丢弃不需要的返回值 h = gethw
();
--变量名的个数少于返回值个数,多余的返回值被丢弃 --结果:h等于"hello", --不需要的返回值可以用_占位符替代变量名。 _ ,w =gethw
();
--用一个_占位符丢弃相应的返回值 --结果:w等于"world" --
括号中的表达式总是返回一个值 h =
( gethw
() );
--用括号强制取函数的第一个返回值,丢弃其他的返回值 --多个返回值也可以放在table构造器中 tab =
{ gethw
() };
--把所有的返回值放到表构造器中生成一个tab --结果:tab[1]等于"hello",tab[2]等于"world" --select函数能在多个返回值中取出指定位置的返回值 w = select
(2 , gethw
() );
--将test的所有返回值作为其他函数的参数 --select函数将指定位置(用第一个参数加一)的参数作为返回值 --结果:w = "world" --
多个返回值可以直接作为其他函数的参数 print( gethw
() );
--将test的所有返回值作为其他函数的参数 -->输出 hello world --将函数的返回值放在函数的其他参数前面,仅使用第一个返回值 print( gethw
(),
"123" );
-->输出 hello 123 ,
注意world被丢弃了 --将函数的返回值放在函数的其他参数后面,
使用所有返回值 print( "123",gethw
());
-->输出 123 hello world
七、使用函数的参数
- 按值传递参数、按引用传递参数
函数的参数遵循赋值语句的规则,对于普通类型拷贝一个副本按值传递。
function set(x) --参数是按值传递的
x = 200;--改变x不会改变外部变量的值
end;
x = 100;
set(x);--参数是按值传递的
win.consoleOpen();--打开控制台窗口
print(x); -->还是100
对于table,function,userdata类型的参数,是按引用传递的。
如果在函数内部对参数赋值指向其他对象,仍然不会改变外部变量的值。
function set(cl) --参数是按引用传递的
cl = 256;--仍然不会改变外部变量的值,因为cl不再指向cl2而是存储数字值256
end;
cl2 = color(123);
set(cl2);--userdata参数是按引用传递的
win.consoleOpen();--打开控制台窗口
print(cl2); -->cl2还是一个color对象,而不是数值256
对于table,userdata类型的参数,如果在函数内部不改变参数的指向,那么可以改变外部变量的成员变量的值。
function set(t) --参数是按引用传递的
t.x = 256;--在这里成功改变外部变量的值
end;
tab ={x=10,y=20}
set(tab);--table参数是按引用传递的
win.consoleOpen();--打开控制台窗口
print(tab.x); -->显示256,tab.x被函数改变了
如果您需要在函数内部改变外部变量的值,那么您应当把数据包装到一个table变量中。
- table在函数参数中的应用
上面我们已经介绍了,将变量封装到table中,可以在函数内部改变函数外部变量的值,因为table是按引用传递的;
function set(t) --参数是按引用传递的
t.x = 256;--在这里成功改变外部变量的值
end;
tab ={x=10,y=20};
set(tab);--table参数是按引用传递的
将参数封装在table中还有一个好处,如果有很多的参数,又要省略一些参数时-不需要用实参一个个的匹配形参的位置。 win.consoleOpen();
function test(a,b,c,d,e,f,g,h,i,j,k,l,m)
print(i,m);
end
--假设上面的函数中所有的参数都可以省略,只有i和m是不可以省略的。
--那么我们可能要这样写
test(nil,nil,nil,nil,nil,nil,nil,nil,2,nil,nil,nil,3);
因为实参与形参是按位置匹配的,所以最后的可省略参数可以直接省略。
如果在可省略的参数后面还有其他的参数要写,我们只有通过赋值为nil表示省略。 下面是更好的方案:
-- 下面我们用table来封装参数实现同样的功能,是不是简单的多了呢?
function test(t)
print( t.i , t.m );
end
tab ={i=2,m=3}
test(tab);
分离table的所有成员并作为函数的参数
win.consoleOpen();
function set(x,y)
print(x,y);
end;
tab ={10,20}
set(unpack(tab));
--上面的代码等效于
set(tab[1],tab[2]);
- 在函数中使用可变参数
三个连续的圆点表示可变参数,使用函数内部的arg列表可以索引所有的参数。
win.consoleOpen
();
function set
(...
) --
三个连续的回点表示任意个数、任意类型的参数 print( table.maxn
(arg
) );
-->显示可变参数的个数 print(arg
[1],arg
[2]);
-->显示第一个可变参数,第二个可变参数 end;
set
(12,
23);
其实,我们经常使用的pint函数就是接收可变参数的,可以给print传递任意类型任意个数的参数。
在可变参数前面可以添加固定名称的参数,例如:
win.consoleOpen();
function set
(x,y,...
) --
三个连续的回点表示任意个数、任意类型的参数
print( table.maxn
(arg
) );
-->显示可变参数的个数(

不包括前面的固定名称的参数)
print(arg
[1],arg
[2]);
-->显示第一个可变参数,第二个可变参数 end;
set
(x,y,12,
23);
可变参数被存储在arg列表中,arg列表是一个table变量.有时候我们需要展开arg表得到多个值,例如展开arg作为其他函数的参数。
我们可以使用unpack函数展开arg表。下面是一个例子。
function print2(...)
print( unpack(arg) );
end
print2(12,23,"abc")
注意 如果函数没有使用可变参数,则不会创建局部变量arg。但是每个fap程序或LAS脚本都有一个默认的全局变量arg,存储命令行参数。用下面的代码可以检视所有命令行参数。
win.consoleOpen();
for k,v in pairs(arg) do
print(k,v);
end
- 函数也可以作为参数
函数本身也是一个变量,也可以作为参数,例:
function test()
win.messageBox("我是test")
end
function func( f )
win.messageBox("我是func,下面我执行我接收到的函数f()")
f();
end;
func(test);--将函数test作为参数传递给了func函数
八、闭包、作用域
一个函数可以在内部包括另一个函数。
函数内部的函数如果加上local语句则定义的函数仅能在函数内部使用。
1、函数外部不能访问函数内部的局部变量。
2、内部函数可以访问外部函数的局部变量。
3、每次执行函数都会定义新的内部函数,创建新的闭包。闭包在函数结束后仍然存在(直到没有指向闭包内部函数的引用而被回收)。
4、闭包中的局部变量能在函数结束后保持其值。闭包中的内部函数可以访问闭包中的局部变量。
如果与c++中的类、对象机制进行比较。

LAScript中的table即是类(可以声明结构)又是对象(可以存储数据),拥有公开成员,但是却没有构造新对象的默认方法(可以通过std库中的class函数实现这个功能)。

LAScript中的函数虽然没有公开成员,但是却可以构造新的对象(闭包),
函数是没有公开成员的
类,
闭包是没有公开成员的
对象。
内部函数可以看作这个
对象的公用接口。
闭包中的局部变量可以看作是这个
私有成员。

闭包实际上并不是一个作用域,也不是函数,更不是语句块,而是函数被执行时创建的一个新的对象。在函数结束后仍然存在
win.consoleOpen();
function upfunc2()
local upfunc = function()
local upv = 23;
function func() --函数里面可以包括函数
print(upv); -->23,upfunc函数的局部变量在他的内部函数func中同样有效
end
func();-->可以调用当前作用域中已经定义的函数
end;
print(upv);-->nil 在upfunc外部不能访问upfunc内部的变量
upfunc();-->可以调用当前作用域中已经定义的函数
end;
upfunc2();
在函数内部的函数,如果不在函数定义前面加上local语句,实际上等于定义了一个全局函数。
但是他同样遵循闭包的规则,可以访问定义他的外部函数中的局部变量。
win.consoleOpen();
function upfunc()
local upv = 23;
function func() --函数里面可以包括函数
print(upv); -->23,upfunc函数的局部变量在他的内部函数func中同样有效
upv = upv+1;
end
end;
upfunc();
--这句代码会执行 upfunc内部的 local upv = 23; 同时upv被初始化为23
--在执行upfunc()也同时定义了 func函数
--注意,如果不执行外部函数则函数内部的下层函数是不存在的。
--因为func函数没有加上local语句,所以成为全局函数,可以直接使用了
func(); -->显示23,通过闭包规则访问upfunc函数中的局部变量并将其加1
func(); -->显示24,可以看到upv已经被加1了
func(); -->显示25,可以看到upv已经被加1了
func2=func; -->将第一次调用upfunc()创建的函数存储到一个新的变量中
upfunc();
--再次执行upfunc(),创建了一个新的func函数
func(); -->显示23,每次调用外部函数都会创建新的闭包,在这个闭包里 upv有自已独立的值
func(); -->显示24,可以看到upv已经被加1了
func2(); -->显示26,可以看到每次调用upfunc()都会创建新的闭包,各自有自已独立的局部变量
-->func2的闭包仍然独立存在,并不会与新建的func共享一个闭包
函数每次执行都会创建新的闭包。我们再看一个例子:
下面的函数与上面的函数执行的效果完全相同。将内部的函数作为返回值,强制我们为每次创建的内部函数赋于一个新名字。
win.consoleOpen
();
function upfunc
() local upv =
23;
local func =
function() --与上面的函数不同func加了local语句被声明为局部函数 print(upv
);
-->23,upfunc函数的局部变量在他的内部函数func中同样有效 upv = upv+
1;
end return func;
--将内部函数作为返回值,强制我们给每次创建的函数一个变量名end;
--上面的函数定义也可以用下面的写法:
function upfunc()
local upv = 23;
return function() --返回一个"匿名函数",LAScript可以创建没有名字的函数赋值给其他变量
print(upv); -->23,upfunc函数的局部变量在他的内部函数func中同样有效
upv = upv+1;
end
end;
f2 = upfunc
();
-->这样我们必须给func函数一个新的名字了 f2
();
-->显示23,通过闭包规则访问upfunc函数中的局部变量并将其加1f2
();
-->显示24,可以看到upv已经被加1了f2
();
-->显示25,可以看到upv已经被加1了 f3 = upfunc
();
--再次执行外部函数,同时upv被初始化为23 f3
();
-->显示23 f2
();
-->f2的闭包仍然独立存在,并不会与f3共享一个闭包
九、尾调用
请看下面的例子:
function f (x)
return f2(x)
end
如果一个函数在调用另外一个函数以后不再做任何事称为尾调用。
尾调用不会返回原来的函数(类似goto),所以不需要额外的栈保留调用函数的数据。
正确的尾调用
1、必调用必须是在最后一个return语句中
2、return语句后面只能有一个函数调用,不能使用其他表达式
例如 return g(x) + 1 不是尾调用
3、参数中可以使用表达式。
递归调用函数是第浪费资源的,但是如果使用尾调用就需要大量的压栈,
因为无论递归多少次都不会导致栈溢出。
实际上,我们前面例子中的递归函数就是一个典型的尾调用函数
dg =
function (a
) if( (nStop()==false) or (a <=
0) ) then return a
else return dg
(a-
1) --
如果一个函数在调用另外一个函数以后不再做任何事称为尾调用 end end
win.consoleOpen
() print(dg
(99999999999999999999999999999999999) );
如果我们把 return dg(a-1) 改成 return dg(a-1)+1就不再是尾调用了,你会看到内存不断的增加直到栈溢出。