高階軟體工程——軟體中的一些特殊機制
軟體中的一些特殊機制
1.回撥函式
2.多型
3.閉包
4.非同步呼叫
5.匿名函式
一,回撥函式
關於回撥函式的概念,上圖可以很清晰的描述:應用程式在呼叫庫函式時向庫函式傳遞引數,引數裡面包含一個應用程式指定的函式。這樣,庫函式被呼叫的時候,就會回過頭來調用這個應用程式指定的函式。被傳遞而後被庫函式呼叫的函式稱為回撥函式(callback function)。
這麼看來,回撥函式會大大增加程式設計的靈活性。因為相比傳統僅傳遞引數的方式,這裡把函式作為引數,庫函式的動作可以根據回撥函式的不同而做出相應的改變。
舉個簡單的例子,如下:
// callBack1:接受一個非零整數,返回偶數 int cb_Even(int a){ return 2*a; } // callBack2:接受一個非零整數,返回奇數 int cb_Odd(int a){ return 2*a+1; } // 中間函式:接受函式指標並呼叫對應的回撥函式 int returnNum(int a, int (*p_func)(int)){ return (*p_func)(a); } // 使用:傳入不同的函式引數,呼叫不同的回撥函式 cout << returnNum(5, cb_Even) << endl; // 輸出10 cout << returnNum(5, cb_Odd) << endl; // 輸出11
我對回撥函式的理解:發起者將目標函式A作為引數傳遞給中間函式,中間函式隨後呼叫目標函式A。相比僅傳遞引數,將函式也傳遞過去可以執行不同的功能。
二,多型
在面向物件程式設計中,多型是指通過基類的指標或引用,在執行時,根據實際繫結的物件執行對應的函式的行為。編譯時繫結則是指過載或模板。關於多型的實現原理可以查閱虛擬函式表相關內容。
c++中,實現多型的條件:
-
要有繼承關係;
-
要有虛擬函式重寫(被 virtual 宣告的函式叫虛擬函式);
-
要有父類指標(父類引用)指向子類物件。
舉個簡答的例子,如下:
class Person{ public: // 父類中的虛擬函式 virtual void BuyTicket(){ cout << "adult need full fare." << endl; } } class Child : public Person{ public: // 子類繼承並重寫了父類的虛擬函式 virtual void BuyTicket(){ cout << "child free." << endl; } } void TicketCheck(Person& person){ // 編譯時,父類指標指向自己的虛擬函式 person.BuyTicket(); } // 呼叫時,該指標動態指向自己或任意孩子重寫的虛擬函式 Person father; Child tommy, Child bobby; TicketCheck(father); // person指標指向person TicketCheck(tommy); // person指標指向child TicketCheck(bobby); // person指標指向child
三,閉包
閉包(closure)是JavaScript中的概念,指函式與函式內部能訪問到的外部變數構成的總和。
比如:bar()函式和其內部能夠訪問到的外部變數local的組合,即為一個閉包。注意這裡有一個刻意為之的函式巢狀的結構。
function foo(){
var local = 1 // 閉包的作用就是將local變數變為區域性變數
function bar(){ // 函式巢狀,local稱為bar函式的全域性變數
locat++
return local
}
return bar // 返回bar函式,外部可以間接使用local變數
}
閉包常用來隱藏一個變數
四,非同步呼叫
首先明確一下同步呼叫的概念:當發起者A呼叫函式B時,會一直處於阻塞狀態直到函式B執行完並返回結果。非同步呼叫則是指發起者A呼叫函式B後繼續做其他的事情,不會等待B的執行。
原理比較簡單,在C++中可以通過呼叫函式async()來實現非同步呼叫。
五,匿名函式
匿名函式,顧名思義,就是沒有名稱的函式,在C++和大多數語言中又稱為lambda表示式。
下圖為lambda表示式的書寫規範,這裡討論第二種模式。
[捕獲列表](引數列表) -> 返回值型別 { 函式題 }(傳遞的引數列表);
舉個例子,看一下普通函式和匿名函式(lambda表示式)的區別:
// 普通函式:接受2個整型值返回和
int sum(int a, int b){
return a+b;
}
int main(){
int outcome=0; // 變數用於接受結果
// 呼叫普通函式
outcome = sum(1, 2);
cout << outcome << endl;
// 呼叫lambda表示式,注意,lambda表示式可以在函式內部巢狀定義,這是優勢
outcome = [](int a, int b) -> int { return a+b; }(1, 2);
cout << outcome << endl;
// 或者可以寫成這樣
[](int a, int b){ cout << a+b << endl; }(1, 2);
return 0;
}
一般而言在C++中,無法在main函式或者其他函式內部定義新的函式,這會發生函式巢狀。從上面的例子可以看出,使用lambda表示式可以隨用隨定義,不用跳出函式內部。另一方面,不用給函式取名,也省了一些命名的煩惱 :)。
關於lambda表示式的進階用法這裡不再贅述,請自行查閱相關資料。