1. 程式人生 > 其它 >高階軟體工程——軟體中的一些特殊機制

高階軟體工程——軟體中的一些特殊機制

軟體中的一些特殊機制

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++中,實現多型的條件:

  1. 要有繼承關係;

  2. 要有虛擬函式重寫(被 virtual 宣告的函式叫虛擬函式);

  3. 要有父類指標(父類引用)指向子類物件。

舉個簡答的例子,如下:

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表示式的進階用法這裡不再贅述,請自行查閱相關資料。