类函数是在类中被原型化的函数。每个应用中的函数都会执行特定的任务,例如解析一个 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 。
- 本书目录
- 第一章:Haxe介绍
- 互联网开发的一个问题
- Haxe是什么,为什么产生
- Haxe编译工具
- Haxe语言
- Haxe如何工作
- 那么Neko是什么
- Haxe和Neko的必须条件
- 本章摘要
- 第二章:安装、使用Haxe和Neko
- 安装Haxe
- 使用Haxe安装程序
- 在Windows上手动安装Haxe
- Linux上手动安装Haxe
- 安装Neko
- Windows上手动安装Neko
- 在Linux上安装Neko
- Hello world! 一式三份
- 编译你的第一个Haxe应用
- 你的程序如何编译
- HXML编译文件
- 编译到Neko
- 编译为JavaScript
- 程序结构
- 编译工具开关
- 本章摘要
- 第三章:基础知识学习
- Haxe层级结构
- 标准数据类型
- 变量
- 类型推断
- 常数变量
- 简单的值类型
- 浮点类型
- 整型
- 选择数值类型
- 布尔类型
- 字符串类型
- 抽象类型
- Void 和 Null
- 动态类型
- unknown类型
- 使用untyped绕过静态类型
- 注释代码
- 转换数据类型
- Haxe数组
- Array
- List
- Map
- Haxe中使用日期时间
- 创建一个时间对象
- Date组件
- DateTools类
- 操作数据
- 操作符
- Math类
- 使用String函数
- 本章摘要
- 第四章:信息流控制
- 数据存放之外
- 条件语句
- if语句
- switch语句
- 从条件语句返回值
- 循环
- while循环
- for循环
- 循环集合
- Break和Continue
- 函数
- 类的函数
- 局部函数
- Lambda类
- 本章摘要
- 第五章:深入面向对象编程
- 类和对象
- 实例字段
- 静态字段
- 理解继承
- Super
- 函数重载
- 构造器重载
- toString()
- 抽象类和抽象方法
- 静态字段,实例变量和继承
- 继承规则
- 使用接口
- 高级类和对象特性
- 类的实现
- 类型参数
- 匿名对象
- 实现动态
- Typedef
- 扩展
- 枚举
- 构造器参数
- 本章摘要
- 第六章:组织你的代码
- 编写可重用代码
- 使用包
- 声明一个包
- 隐式导入
- 显式导入
- 枚举和包
- 类型查找顺序
- 导入一个完整的包
- 导入库
- Haxe标准库
- Haxelib库
- 其他项目中的库
- 外部库
- 使用资源
- 文档化代码
- 离线文档
- 在线文档
- 单元测试
- haxe.unit包
- 编写测试
- 本章摘要
- 第七章:错误调试
- trace函数
- trace输出
- haxe的trace和ActionScript的trace
- 异常
- 异常处理
- CallStack和ExceptionStack
- 异常管理类
- 创建完全的异常处理类
- 异常类代码
- 本章摘要
- 第八章:跨平台工具
- XML
- XML剖析
- Haxe XML API
- 正则表达式
- EReg类
- 模式
- 定时器
- 延迟动作
- 队列动作
- MD5
- 本章摘要
- 第九章:使用Haxe构建网站
- Web开发介绍
- Web 服务器
- 使用Web服务器发布内容
- HTML速成课程
- Haxe和HTML的区别
- NekoTools Web Server
- Apache安装mod_neko
- Windows安装Apache和mod_neko
- Linux安装Apache和Mod_Neko
- 第一个Haxe网站
- 使用Neko作为网页Controller
- neko.Web类
- Neko作为前端控制器
- 本章摘要
- 第十章:使用模板进行分离式设计
- 什么是模板
- Template类
- Template语法
- 使用资产
- 何时在模板中使用代码
- 服务器端模板的Templo
- 安装Templo
- 使用Templo
- haxe.Template和mtwin.Templo表达式上的区别
- Attr表达式
- Raw表达式
- 逻辑表达式
- 循环表达式
- set, fill, 和 use表达式
- Templo中使用宏
- 手动编译模版
- 第十一章:执行服务端技巧
- 第十二章:使用Flash构建交互内容
- 第十三章:使用IDE
- 第十四章:通过JavaScript制作更多交互内容
- 第十五章:通过Haxe远程通信连接所学
- 第十六章:Haxe高级话题
- 第十七章:Neko开发桌面应用
- 第十八章:用SWHX开发桌面Flash
- 第十九章:多媒体和Neko
- 第二十章:使用C/C++扩展Haxe
- 附加部分