局部函数作为变量值存在的函数。也就是说,你构造一个函数,并传递一个它的引用到一个变量。C开发者可能认为相当于函数指针,事实上,局部函数可以用来提供类似的功能。类方法和局部函数之间的不同在于其结构。局部函数没有名称。然而,某种程度上,两种函数可以互相转换,稍后您会看到这个。
局部函数采用以下结构:
~~~
var functionVariable = function( param : ParamType [, ...] ) : ReturnType
{
// function code...
}
~~~
如你所见,包含参数的圆括号直接跟在 `function` 关键字后,并没有给予名字。函数的原型直接通过引用分配到一个变量。一旦赋值完成,就可以拷贝一个新的变量的引用。然而,局部变量的结构也包含一个类型结构,必须和任何函数要分配到的变量保持一致。
# 局部函数类型结构
* * * * *
当声明一个变量来包含一个局部函数,变量的类型可以通过类型推断分配,或者也可以在设计时分配。为一个局部函数的变量分配类型遵循一个很实用的语言方法,警告编译器期望一个函数还不够。反而,每个参数类型,和函数的返回类型,必须被指定所以任何函数引用分配到变量的交换要至少期望相同的数据类型输入和输出。一个变量期望局部函数必须被类型化,以下面的形式:
~~~
var functionVariable : ParamOne [[- > ParamTwo] ...]- > ReturnValue;
~~~
这里是一个例子:
~~~
var fun1 = function() : Void
{
// code...
}
var fun2 = function( p1 : String ) : Void
{
// code...
}
var fun3 = function( p1 : Int, p2 : Float, p3 : Int ) : String
{
// code...
return someString;
}
~~~
如果你在分配函数引用之前声明这些变量,需要这样做:
~~~
var fun1 : Void - > Void;
var fun2 : String - > Void;
var fun3 : Int - > Float - > Int - > String;
~~~
在第一个示例中, `fun1` ,在函数定义时没有参数,所以 `Void` 被使用。如果函数期待一个或者多个 `Void` 类型的参数,应该加上括号,像这样:
~~~
var fun1 : (Void) - > (Void) - > Void;
fun1 = function( p1 : Void, p2 : Void ) : Void {
// do code...
}
~~~
# 传递函数到局部函数
* * * * *
局部变量主要目的之一就是可以作为参数传递到其他函数。局部函数可以接受其他局部函数作为参数。当这样做时,参数化函数的类型必须在接收函数的参数类型中设定。这是由括起来的每个参数化函数类型做的,所以他们代表一个单独的类型:
~~~
var fun1 : String - > String;
fun1 = function( p1 : String ) : String
{
// do code...
return someString;
}
var fun2 : String - > ( String - > String ) - > String;
fun2 = function( p1 : String, p2 : ( String - > String ) ) : String
{
return p2( p1 );
}
var tmp = fun2( “someString”, fun1 );
~~~
如你所见,这可能有点类似于应用在函数的Void类型的圆括号。然而,局部函数的圆括号Void 只需要一个类型被封闭,而一个完整的函数定义总是需要两个甚至更多的类型被关闭。
# 多函数递归
* * * * *
能够给一个变量类型来在函数分配之前保存函数,允许一些非常有趣的技巧。甚至可能两个或者两个以上的函数可以互相利用,有效的创建一个递归循环跨越多个函数。这种方式捆绑函数被证明在解析树结构时是非常有用的:
~~~
var add2 : Int - > Dynamic - > Int;
var minus1 : Int - > Dynamic - > Int;
add2 = function( p1 : Int, p2 : Dynamic ) : Int
{
p1 += 2;
trace( p1 );
if ( p1 < 5 ) p1 = p2( p1, add2 );
return p1;
}
minus1 = function( p1 : Int, p2 : Dynamic ) : Int
{
p1--;
trace( p1 );
p1 = p2( p1, minus1 );
return p1;
}
add2( 0, minus1 );
// Outputs: 2, 1, 3, 2, 4, 3, 5
~~~
这里,函数 add2 分配到指定变量,而其他函数从变量减去 1 直到某个最小值。就像例子中看到的,作为参数的函数的类型被指定为 Dynamic 。因为两个函数以函数作为第二个参数,形成一个递归类型的规范。依赖类型推断的类型化会产生一个错误,所以分配一个Dynamic类型是仅有的合适选择。如果你希望添加一些函数类型声明类型的控制,可以总是指定递归函数类型为动态,确保函数类型至少保证会传递到第一个待用。这至少使函数得到适当的使用当委托任何更深入的对象类型检查进行调用。
# 局部函数变量作用域
* * * * *
局部函数的变量范围包括容器类的静态变量、变量被声明在和引用函数的变量同一个水平,也和声明在函数内的变量一样:
~~~
class LocalFunctionVars
{
public static var myStaticVar1: String;
public var myVar1:String;
public static function main()
{
var myLocalVar1 : String;
var myFunction = function()
{
var innerFunction : String;
innerFunction = “haXe”;
myLocalVar1 = “is”;
myStaticVar1 = “really”;
// will throw a compiler error
myVar1 = “amazing”;
}
}
}
~~~
这覆盖类中的多数变量,但是不包括非静态变量 myVar1 。要访问这些变量,需要通过类的关键字 this 引用。然而,这个this关键字在局部变量并不允许,因为局部函数作为静态考虑,所以一个技巧是必须的。如果局部于变量的变量是可访问的,即使在一个类的实例,this 的值可以被分配到一个这样的局部变量然后再函数中引用:
~~~
class LocalFunctionVars
{
public static var myStaticVar1 : String;
public var myVar1 : String;
public static function main()
{
var l = new LocalFunctionVars();
}
public function new()
{
var myLocalVar1 : String;
var me = this;
var myFunction = function()
{
var innerFunction : String;
innerFunction = “haXe”;
myLocalVar1 = “is”;
myStaticVar1 = “really”;
// will now compile
me.myVar1 = “amazing”;
}
}
}
~~~
静态变量和函数,还有 this 关键字在下一章讨论。然而,为了完整已经在这里进行了一些讨论。
# 在集合对象使用函数
* * * * *
早些时候,当你查看数组的遍历,看到为了循环时直接修改存储在数组中的项,数组必须被使用一个迭代的整数值索引。然而,List和对象呢?列表集合不能被像数组那样索引,所以List中包含的值在循环中怎么修改?答案是通过使用List 的map 方法。
## List的map方法
List集合的map方法应用一个函数到集合中的每个项,并返回一个新的List,包含了修改后的值。它不像至今为止你看到的其他类方法,因为它以一个函数作为参数。这个函数必须使用一个集合结构,因为严格类型,但是可以通过使用局部函数创建它。定义类型,然后:
~~~
var fun : X - > T;
~~~
参数,或者说X,是对象类型包含在原始列表中,而返回值,或者T,是对象类型,包含在新的列表中。
~~~
class FunctionMapping
{
public static function main()
{
var strLst : List < String > = new List();
strLst.add(“Bob”);
strLst.add(“Burt”);
strLst.add(“James”);
strLst.add(“Rupert”);
var intLst : List < Int > ;
var fun : String - > Int = function( x : String ) : Int
{
return x.length;
}
intLst = strLst.map( fun );
// Outputs: {3, 4, 5, 6}
trace(intLst);
}
}
~~~
strLst列表初始化填充多个字符串值。然后构造一个局部函数,接收一个String类型值然后返回Int类型,即字符串的长度。这个函数被传递到List 的map 方法,创建一个新的Int类型列表,然后迭代原来的列表,应用局部函数到每个包含的项兵传递它们到新的Lsit。如果返回的类型匹配原列表包含项的类型,则会分配返回的list 到原有的列表变量,可以提供包含的值直接影响的外观。
## 列表过滤方法
List集合的 filter 方法以和 map 方法类似的方式工作,差异在于,它不是接收影响项的值的方法,传递到这个方法的函数对比每个值然后返回一个布尔类型结果。如果函数结构为true,那么该项返回到一个新的List对象,否则它被排除在外。这带来的影响是,过滤了你的值。
~~~
class ListFiltering
{
public static function main()
{
var strLst = new List < List > ();
strLst.add(“Bob”);
strLst.add(“Burt”);
strLst.add(“James”);
strLst.add(“Rupert”);
var newLst : List < String > = new List();
var fun1 : String - > Bool = function( x : String ) : Bool
{
return ( x.length % 2 != 0 );
}
var fun2 : String - > Bool = function( x : String ) : Bool
{
return StringTools.startsWith( x, “B” );
}
// Outputs: [“Bob”, “James”]
newLst = strLst.filter( fun1 );
trace(newLst);
// Outputs: [“Bob”, “Burt”]
newLst = strLst.filter( fun2 );
trace(newLst);
}
}
~~~
## 数组排序方法
数组的 sort 方法和 filter 方法类似,它接收一个函数作为参数,用来进行排序数据。然而,这个函数接收两个参数,二者有和数组中项具有共同的类型,因为两个参数是数组中的两个项。返回值是Int类型的0,如果属性对比两个项结果是相等,如果第一个参数大于第二个参数则返回大于0,如果第一个参数小于第二个则返回值小于0 。项然后会在原有的数组排序,通过传递每个项和它相邻的项到这个函数并对他们进行适当的排位。项会在原有数组重新排位,所以sort 方法返回 Void 。
~~~
class ArraySorting
{
public static function main()
{
var strArr = [“one”,”two”,”three”,”four”,”five”,”six”];
var fun1 = function( a : String, b : String ) : Int
{
var x : String = a.charAt(0);
var y : String = b.charAt(0);
return if ( x == y ) 0 else if ( x < y ) -1 else 1;
}
var fun2 = function( a : String, b : String ) : Int
{
return if ( a == b ) 0 else if ( a < b ) -1 else 1;
}
// Outputs: [“four”, “five”, “one”, “six”, “two”, “three”]
strArr.sort( fun1 );
trace(strArr);
// Outputs: [“five”, “four”, “one”, “six”, “three”, “two”]
strArr.sort( fun2 );
trace(strArr);
}
}
~~~
如你所想象的,你可以执行一些非常复杂的排序方法在很多东西。许多语言有一个单独的 sort 方法尝试排序一些包含项的表现,Haxe Array sort 方法提供最终的控制根据你的数据组织。
- 本书目录
- 第一章: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
- 附加部分