使用函数参数
请参考:定义函数
形参 ( parameter ) 指函数定义时被包含在括号中具名参数,形参名也是局部有效的变量名字。
实参( argument)指调用时括号中包含的实际参数,实际参数是一个右值表达式。
示例:
//这里的 param1,param2,param3 称为形参,可以将形参看成函数内部的局部变量名字
var func = function(param1,param2,param3){
//形参可以在函数体内部作为局部变量使用
return param1+param2+param3;
}
//这里的2,3,4 称为实参
var result = func(2,3,4);
实参的数目如果多于形参的数目,多余部分被丢弃。
实参的数目如果少于形参的个数,不足的部分添加 null 值。
请参考:调用函数
在形参列表尾部定义可变参数
在定数定义的**形参**列表尾部,可以用三个连续的圆点(...
)表示可变参数(不定参数)。
可变参数必须是最后一个参数,如下:
import console.int;
//使用三个连续的圆点表示任意个数、任意类型的参数
func = function( a,b, ... ){
//不定参数转换为数组
var args = { ... }
/*
args 是典型的可能包含 null 值的稀疏数组(数组索引不连续)。
所以应使用 table.range() 取最小最大索引,而不是用 # 取数组长度。
*/
var _,len = table.range(args)
console.log(len + "个参数")
//遍历这种稀疏数组应当使用 table.eachArgs() 创建迭代器。
for i,v in table.eachArgs(args){
console.log(i,v)
}
}
func(1,2,99,888,null,777);
在调用实参列表尾部指定可变参数
在实参列表尾部可以使用可变参数
import console;
var str ="abcd"
//在实参尾部可以使用不定参数
console.log( "string.unpack有多个返回值",string.unpack(str) );
//可变参数不放在最后面就只能取第一个值
console.log( string.unpack(str) ,"string.unpack的第一个返回值");
注意上面的 string.unpack(str) )
会返回多个值。
实参可以省略,省略的实参传递 null 空值。如下:
import console;
console.log( , ,2,3,) //下面都是等价的写法
console.log( null , null , 2 , 3 , null ) //与上面一行的作用相同
console.log( null , null , 2 , 3 ) //与上面一行的作用相同
console.pause();
在调用实参的尾部指定 null 值是无意义的,在 aardio 中 null 等价于未指定任何值。
在函数定义的形参表中,可以为任意位置的参数指定默认值。 如果省略相应实参,或调用实参为 null 值时,则将对应形参指定的默认值作为实参。
在其他一些编程语言中,只能为尾部的参数的指定默认参数,而在 aardio 中没有这个限制。
示例如下:
import console;
//在函数定义的形参中,使用等号指定形参的默认值;
function func(a,b=123,c="字符串",d=2,e=true) {
console.log(a,b,c,d,e)
}
//调用函数
func(1,,,4,5 )
形参默认值仅允许使用以上示例中演示的字面值常量(数值、字符串、布尔值),不可使用其他变量或表达式。
当函数的调用实参有且只有一个表构造器(并且表不是结构体),并且第一个名值对使用等号=
分隔时,则可以省略外层的 {}
符号。
例如 func( k=1,k2=2 )
等价于 func({ k=1,k2=2 })
。
只要参数中的第一个表构造器的第一个名值对使用=
分隔,后面再指定其他表成员时只要符合构造表的语法就可以了。
例如在表参数中添加数组成员可以这样写:
func( k="值",123,456,789 )
注意这种写法不是其他编程语言里的"命名参数", aardio 语言并不存在"命名参数"这个特性。但利用这种语法确实可以模拟命名参数的效果,例如 aardio 在调用 Python 时如果在 Python 函数名前加一个
$
字符就可以利用 aardio 省略{}
的表参数特性模拟出 Python 命名参数的效果,示例:import py3; var requests = py3.import("requests"); var ses = requests.Session(); var res = ses.$get(verify=false,"https://www.aardio.com");
如果一个函数需要较复杂的经常变动的参数,使用表作为参数可以增强函数的可扩展性与可读性。
示例:
函数调用(
名字 = "某某";
住址 = "北京";
家庭成员 = 统计函数(
男 = 6,
女 = 3,
老 = 1,
幼 = 1,
)
);
纯函数
在 aardio 中函数是纯函数(Pure Function)- 输入输出数据流都是显式(Explicit)的。函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部,即函数的数据只有一个入口( 参数 ),一个出口( 返回值 )。函数可以返回多个值。
aardio 的函数只有输入参数,没有输出参数,不能在函数体中改变实参的数据。 原生 API 函数中的输出参数同样被转换为返回值 - 然后附加到返回值列表中。
具有副作用的 table 参数
在虚函数中传址参数仍然具有副作用。
table 对象在赋值或传参中都是传址的,传递过程中并不改变指向的对象,而table 的成员又是可修改的,这就使 table 对象在函数的传参过程中具有副作用,对 table 参数的成员进行修改会作用到外部对象,并不局限于函数体内部。
如果需要在函数内部改变外部变量的值,那么应当把数据封装到一个 table 对象中。
import console;
// tab 参数指定 table 对象时具有副作用
var set = function (tab) {
//改变 tab 对象的成员值会作用到函数外部
tab.x = 256;
//输出参数针对的是参数本身,副作用影响的是参数指向的数据
//tab 不是输出参数,下面的代码仍然不能改变外部 table 对象
t = 123;
}
var tab ={x=10,y=20}
//table 对象作为参数是按引用传递的
set(tab);
//tab.x 被 set 函数改变了
console.dumpTable(tab);
console.pause();
这个有一点利用潜规则走后门的意思,不声不响的修改了外部的对象,又没有显式的交待,应当尽量避免这么做。
当然,我们也没有必要总是墨守成规,可以使用更友好的函数命名,清晰地注明副作用。
例如:
import win;
var rc = ::RECT(1,2,3,4);
//函数命名注明了它会改变 rc 中的坐标
win.toScreenRect(0,rc);
有经验的程序员总是不断提醒我们,多打几个字,减少大堆的麻烦!
虽然从语法上说,aardio 函数没有定义输出参数的这个概念,但是 aardio 函数支持在函数中返回多个返回值,通过返回值也可以实现修改输出参数的值,举个例子:
var test = function(x,y){
x = x + 1;
y = y + 1;
return x,y;
}
var x,y = 1,2;
//通过返回值接收了 x,y 的新值
x,y = test(x,y);
aardio 在调用外部 COM 函数,或者外部 API 函数时,同样是使用上面的方法来支持输出参数。
例如调用 COM 函数时,所有输出参数都会增加一个返回值( 按参数的前后位置排序返回 )。调用外部 API 函数时同样的可以自返回值获取输出参数的值,在 API 函数声明中定义为输出参数的参数(参数的原生类型后面加 &
,例如int &
) - 都会增加一个对应的返回值。
如果在 aardio 中定义外部 COM 函数或外部API函数需要用到的回调函数时,同样使用多个返回值来修改输出参数的值。
例如,在调用API函数时定义 stdcall 回调函数,下面的 x 就是输出参数:
var stdCallback = raw.tostdcall(
function(x){
x = x + 1;
return 123,x;
},"int(int &x)" )
或者在调用 COM 控件时定义各种回调函数(例如定义 COM 事件)同样是在返回值中指定输出参数的新值。
例如在使用 web.form 时如下定义 NewWindow3 事件:
wb.NewWindow3 = function(ppDisp,cancel,flags,urlContext,url ) {
//这里 ppDisp,cancel 都是输出参数,下面修改了cancel 的值为true
return ppDisp,true;
};
aardio 中所有函数都有一个隐藏的 owner 参数,可以认为是第 1 个调用参数前面隐藏的第 0 个参数。
owner 参数不用写在调用参数列表里,由 aardio 自动传递并接收 owner 参数。
owner 指的是在调用成员函数时拥有该成员函数的所有者。
例如执行 object1.method()
时,object1.method 函数接收到的 owner 参数就指向 object1 。
如果我们先写 object2.method = object1.method()
,然后再调用 object2.method()
时,object2.method 函数接收到的 owner 参数就指向 object2 。
onwer 参数可能指向不同的对象。
请参考:owner
在调用一个类构造实例对象时,如果实例对象的成员函数不改变所有者,则调用实例对象的成员函数时,owner 默认指向类的实例对象 - 也就是类主体内的 this 对象。区别在于 this 无法在外部改变,而 owner 可以在外部改变的。
在 元方法 中 owner 表示左操作数。
在 数组排序 序时,owner 参数表示将要与下一个参数比较的元素。