ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
类函数是在类中被原型化的函数。每个应用中的函数都会执行特定的任务,例如解析一个 email 地址或者从配置文件读取数据。在下一章中将要学习的,这些作为类函数存在的函数,通常执行和父类相关的一些处理。所有类函数都有如下结构: ~~~ [public/private] [static] function <name>( param1:paramType [, param2 : paramType], ...]):returnType { //... } ~~~ `public`,`private` 和 `static` 标识符在下一章中讨论,所以现在可以跳过它们。接下来是 `function` 关键字,表示接下来要声明一个函数。函数的名字会紧跟 `function` 关键字,和 Haxe 变量具有相同的命名规则。尽管一个函数取什么名字是你的权利,但通常在 Haxe 的命名约定中,函数名总是以一个小写字母开始。 函数名后是声明参数的部分,使用圆括号包围。这些参数和声明局部变量的方式相似,不用使用 `var` 关键字,并且遵循相同的类型推断规则。可以使用任意希望的参数数量,尽可能的少一些,易于使用。当你的函数被调用,值会通过函数调用传递到函数中,对应函数原型中提供的参数,必须是相同类型或者参数的扩展类型。然后函数中可以通过访问他们和参数名使用这些值,因为它们可能是任何变量。 函数的最后一部分是返回值。返回值是所有必须的处理结束之后从函数返回数据的值。如果没有值被返回,类型定义必须是Void 。这是一个简单的函数: ~~~ public static function main() { var i : Int = 0; for ( j in 1...10 ) { i = add( i, j ); trace( i ); } } public static function add( num : Int, numToAdd : Int ) : Int { num += numToAdd; return num; } ~~~ 这里,函数 `add` 添加值 `numToAdd` 到值 `num` 。当循环第一次迭代,`j` 的值是 `1` ,被添加到变量 `i` 。在随后的迭代, `j` 的值在添加到变量 `i` 之前 递增 `1` ,所以 `i` 的值包含和每次迭代都是一个三角数。如果运行这个例子,会被呈现的值是 1,3,6,10,15,21,28,36 和 45 。 # 从函数返回 * * * * * `return` 关键字允许一个函数在函数体的任何地方结束。例如,如果你希望提前退出一个函数,可以使用 `return` 关键字到希望函数退出的地方,然后其余的函数代码就会被跳过: ~~~ public static function someFunction() { // do code if ( i < 20 ) return; // more code } ~~~ 这里,模拟的函数 `someFunction` 会在变量 `i` 小于 `20` 的时候退出。 使用 `return` 关键字,还可以使函数返回一个值,只需要把值放到 `return` 关键字之后: ~~~ public static function someFunction() : Int { // do code if ( i < 20 ) return i; // more code } ~~~ 这里,`i` 的值被从函数退出时返回。 没有返回值的函数,实际上返回 `Void` 。 # 函数类型推断 * * * * * 分配给每个函数参数的类型不需要被指定,但是会被编译器通过类型推断设置。这意味着参数被推断为在一个值被传递到它之前没有类型。一旦传递了值,参数会被设定为值的类型然后继续向前。 由第一次被传递到函数的值的类型设定参数类型,这可能引起一个小问题,例如: ~~~ public static function main() { display( 2 ); display( 2.5 ); } public static function display( num ) { trace( num ); } ~~~ 这里的 `display` 函数几乎是完全没有用场的函数,但是它能说明一个问题。你知道类型 `Int` 是一个 `Float` 的扩展,所以一个参数类型为 `Float`,可以接受其它类型,但是如果第一次传递一个 `Int` 类型到函数,参数会被类型化为 `Int`,所以 `Float` 就不能再被接受。唯一安全的方式是添加 `Float` 类型到 `num` 参数原型,而不是依赖类型推断。 # 动态函数 * * * * * 使用 `Dynamic` 类型,可以使用实际类型本身适用的一些处理。许多处理可以应用到许多数据类型的值。例如,加法运算符(`+`)可以用在 Float,Int 和 String 类型。对于 Float 和 Int,加法运算符把值加到一起,而用在一对字符串,则会连接它们。这意味着下面的例子是完全有效的: ~~~ public static function main() { // Outputs: 4 trace( double( 2 ) ); // Outputs: 5 trace( double( 2.5 ) ); // Outputs: haXehaXe trace( double( “haXe” ) ); } public static function double( num : Dynamic ) : Dynamic { return num + num; } ~~~ 当使用 Std.is() 函数,它应该能为一些不太适用某些处理的类型提供替代函数。例如,可以重写上面的例子: ~~~ public static function main() { // Outputs: 4 trace( double( 2 ) ); // Outputs: 5 trace( double( 2.5 ) ); // Outputs: haXe haXe trace( double( “haXe” ) ); // Outputs: [ 1, 2, 3, 1, 2, 3 ] trace( double( [ 1, 2, 3 ] ) ); // Outputs: null trace( double( { param : “value” } ) ); } public static function double( val:Dynamic ) { if( Std.is( val, Int ) || Std.is( val, Float ) ) return val + val; else if( Std.is( val, String ) ) return val + " : " + val; else if( Std.is( val, Array ) ) return val.concat( val ); else return null; } ~~~ # 递归函数 * * * * * 一个递归函数,是指调用它本身的函数。在这样做时,函数创建一个循环,类似 `while` 和 `for` 循环,需要设置某些条件来结束递归。递归的好处是,你可以在代码中更好的控制循环,并且只需要一个层次的功能而不是两个更多的循环嵌套函数。 递归最大的好处,是函数调用为每个迭代返回值。这如果没有一些类型的视觉帮助比较难以解释,所以看下图: ![](https://box.kancloud.cn/2016-07-08_577f739bd35c5.png) 如你所见,正常的循环,你开始设置一组值,然后每个循环发生时,值被通过某些方式修改,从循环体的代码创建一个稍有不同的结果。你可以使用递归函数模仿它,同样,但是真正的能力来自它们返回的值。每次调用函数都被前一个调用处理,所以实际上调用时嵌套的。而使用递归,执行点把自己埋进每一个新的函数调用。然后,当递归最终结束,外部的函数调用执行点都返回一个值。这使循环沿着一个方向,然后转进下一个。 看一下这几个例子。首先你可以模仿一个 `while` 循环的效果: ~~~ public static function main() { var i = 0; i = loop( i ); trace( i ); // Outputs: 20 } public static function loop(num:Int):Int { return if( num<20 ) loop( num + 1 ); else num; } ~~~ 当执行时,`loop` 函数以一个类似 `while` 循环的方式执行,例外是 `loop` 函数返回值代表迭代器迭代的次数。`loop` 循环也传递每个递归的返回值回传给 `loop` 函数,虽然在这个例子中不是特别明白。要查看这个动作,最好提供代码来跟踪调用递归前后: ~~~ public static function main() { var i = 0; i = loop( i ); trace( i ); // Outputs: 0 } public static function loop( num : Int ) : Int { var ret = 0; return if ( num < 6 ) { trace( num ); ret = loop( num + 1 ); trace( ret ); --ret; } else num; } ~~~ 在这个例子中,第一次传递到 `loop` 函数的值每次调用都会递增。因为递归调用在每个 `loop` 函数中发生,每个调用在这个点结束执行,而新的调用发生。然后,递增值达到 `6` ,所以 `loop` 循环不再递归调用,而最后传递的值被返回。这然后对于每个父循环调用有一个撞击,在传递它回去之前递减这个值。结果显示为 0,1,2,3,4,5,6,5,4,3,2,1,0 。