局部函數作為變量值存在的函數。也就是說,你構造一個函數,并傳遞一個它的引用到一個變量。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
- 附加部分