c++函式模板和執行機制

ivanlee717發表於2024-04-19

C++_template

c++提供了函式模板(function template.)所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體制定,用一個虛擬的型別來代表。這個通用函式就成為函式模板。凡是函式體相同的函式都可以用這個模板代替,不必定義多個函式,只需在模板中定義一次即可。在呼叫函式時系統會根據實參的型別來取代模板中的虛擬型別,從而實現不同函式的功能。

  • c++提供兩種模板機制:函式模板和類别範本
  • 類屬:型別引數化,又稱引數模板

函式模板

函式模板是 C++ 中的一種特性,它允許你編寫一個通用的函式,能夠處理不同型別的資料,而不需要針對每種型別編寫多個函式。透過函式模板,你可以編寫一次程式碼,然後使用不同的資料型別來例項化該函式,從而生成針對不同資料型別的具體函式。

函式模板可以處理各種不同的資料型別,只要這些資料型別支援所執行的操作。例如,你可以使用函式模板來編寫一個通用的排序函式,它可以對整數、浮點數、字串等進行排序,而無需為每種型別編寫一個特定的排序函式。

假設現在有幾個類似函式:

void SwapInt(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

void SwapChar(char& a, char& b) {
	char temp = a;
	a = b;
	b = temp;
}

如果要交換double,bool等其他型別時,需要重複寫很多個功能相同的函式。寫的函式越多,當交換邏輯發生變化的時候,所有的函式都需要修改,無形當中增加了程式碼的維護難度。

template<class T>

void Swap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}
void test() {
	int a = 10;
	int b = 20;
	cout << "a:" << a << " b:" << b << endl;
	//1. 這裡有個需要注意點,函式模板可以自動推導引數的型別
	Swap(a, b);
	cout << "a:" << a << " b:" << b << endl;
	cout << "-----------------\n";
	char c1 = 'a';
	char c2 = 'b';
	cout << "c1:" << c1 << " c2:" << c2 << endl;
	//2. 函式模板可以自動型別推導,那麼也可以顯式指定型別
	Swap<char>(c1, c2);
	cout << "c1:" << c1 << " c2:" << c2 << endl;
}

image-20240419092905080

普通函式和函式模板區別

  1. 定義
    • 普通函式:定義時指定了具體的引數型別和返回型別,如 int add(int a, int b)
    • 函式模板:定義時使用關鍵字 template 並在尖括號中宣告模板引數,如 template <typename T> T add(T a, T b)
  2. 引數型別
    • 普通函式:引數型別是固定的,無法處理不同型別的引數。
    • 函式模板:引數型別可以是模板引數,因此可以處理不同型別的引數。
  3. 具體化
    • 普通函式:只有一份具體的實現,不支援根據引數型別動態生成多個版本。
    • 函式模板:可以根據不同的模板引數生成多個具體化的版本,以應對不同的引數型別。
  4. 型別推斷
    • 普通函式:引數型別必須顯式指定。
    • 函式模板:可以透過函式引數的型別自動推斷模板引數的型別。
  5. 使用
    • 普通函式:直接呼叫,引數型別必須與函式宣告中指定的型別相匹配。
    • 函式模板:使用時可以透過顯式指定模板引數的方式例項化模板,也可以讓編譯器自動推斷模板引數的型別。

函式模板和普通函式在一起呼叫規則

  1. c++編譯器優先考慮普通函式
  2. 可以透過空模板實參列表的語法限定編譯器只能透過模板匹配
  3. 函式模板可以像普通函式那樣可以被過載
  4. 如果函式模板可以產生一個更好的匹配,那麼選擇模板
template<class T>
T MyPlus(T a, T b) {
	T ret = a + b;
	return ret;
}
//普通函式
int MyPlus(int a, int b) {
	int ret = a + b;
	return ret;
}
void test() {
	int a = 10;
	int b = 20;
	char c = 'a';
	char d = 'b';
	//如果函式模板和普通函式都能匹配,c++編譯器優先考慮普通函式
	cout << MyPlus(a, b) << endl;
	//如果我必須要呼叫函式模板,那麼怎麼辦?
	cout << MyPlus<>(a, b) << endl;
	//此時普通函式也可以匹配,因為普通函式可以自動型別轉換
	//但是此時函式模板能夠有更好的匹配
	//如果函式模板可以產生一個更好的匹配,那麼選擇模板
	cout << MyPlus(c, d);
}

image-20240419094504835

為什麼函式模板可以和普通函式放在一起?c++編譯器是如何實現函式模板機制的?

hello.cpp程式是高階c語言程式,這種程式易於被人讀懂。為了在系統上執行hello.c程式,每一條c語句都必須轉化為低階的機器指令。然後將這些機器指令打包成可執行目標檔案格式,並以二進位制形式儲存於磁碟中。

預處理(Pre-processing) -> 編譯(Compiling) ->彙編(Assembling) -> 連結(Linking)

image-20240419095156067

函式模板機制結論:

  • 編譯器並不是把函式模板處理成能夠處理任何型別的函式: 函式模板本身並不是完整的可執行程式碼,而是一種通用的模板。編譯器在編譯過程中不會將函式模板直接處理成能夠處理任何型別的函式,而是根據模板定義和實際使用情況生成特定型別的函式程式碼。這意味著函式模板並不直接對應於實際可執行的函式程式碼,而是在使用時根據模板引數生成相應的函式程式碼。
  • 函式模板透過具體型別產生不同的函式: 當程式中使用函式模板並提供具體型別的引數時,編譯器會根據模板定義生成針對這些具體型別的函式程式碼。這個過程稱為模板例項化。換句話說,函式模板在編譯過程中會根據模板引數的具體型別產生不同的函式實現,以適應不同型別的引數。
  • 編譯器會對函式模板進行兩次編譯,在宣告的地方對模板程式碼本身進行編譯,在呼叫的地方對引數替換後的程式碼進行編譯: 這是指編譯器在處理函式模板時的兩個重要階段:模板宣告和模板例項化。在模板宣告階段,編譯器會對函式模板的語法進行檢查,並確保模板定義的正確性。在模板例項化階段,編譯器會根據模板引數的具體型別,對模板程式碼進行替換並生成實際的函式程式碼。這兩個階段都是在編譯過程中發生的,但它們的重點和操作物件略有不同。

相關文章