1. 程式人生 > >C++複習筆記(六)之函式指標和函式模板、類模板

C++複習筆記(六)之函式指標和函式模板、類模板

一、函式指標

函式指標在C語言中的作用類似於c++中的多型,都是可以實現框架的搭建,程式碼的相容性高。

函式三要素:名稱、引數、返回值

C語言可以通過typedef為函式型別重新命名,語法 typedef  返回值型別(型別名稱)(引數列表);如下程式碼所示:

#include<iostream>
using namespace std;

int func(int a, int b)
{
	return a + b;
}
int main()
{
	//函式指標型別定義的第一種方式 : 宣告一個 函式型別 
	typedef int(Func1)(int, int); 
	Func1 *hello1= func;
	cout << hello1(7, 8) << endl;

	//函式指標型別定義的第二種方式 : 宣告一個函式指標型別
	typedef int(*Func2)(int, int);
	Func2 hello2 = func;
	cout << hello2(7, 8) << endl;

	//函式指標定義的第三種方式 : 定義一個指向函式入口地址的函式指標
	int(*Func3)(int a, int b);
	Func3 = func;
	cout << Func3(7, 8) << endl;

	system("pause");
	return 0;
}

二、函式指標的本質

對於編譯器來講,資料型別的本質就是,不同大小的記憶體塊的別名,同樣函式也是一種資料型別,那麼函式名就是函式的入口地址。函式指標就是向編譯器申請類一個型別函式大小的記憶體,這個塊記憶體存放的是一個函式的入口地址

我們看函式指標定義的前面兩種方式,我們通過typedef對函式型別進行了重新命名: typedef int(Func1)(int, int)     就是告訴編譯器我現在自定義了一種資料型別 Func1,這個資料型別需要傳入兩個int型的引數,返回一個int型引數(此時編譯器並沒有對該資料型別進行世實際的記憶體分配)。 當   Func1 *hello1= func   這個時候便器器對hello這個指標分配了一個具有Func1特性與大小的記憶體,並讓這個塊記憶體的初始值為func這個函式的入口地址。

三、函式指標做函式引數

當函式指標做函式引數時、傳遞給一個被呼叫函式,被呼叫函式就可以通過這個指標呼叫外部的函式,這樣就形成了回撥。

回撥函式的本質:提前做了一個協議約定(把函式的引數,返回值提前約定)所有被呼叫的函式只要符合這個約定,無論函式內部如何實現都是可以的(實現瞭解耦合)

例項演示:

需求:我們現在要求求出兩個數的加減乘三種運算,我們可以先通過約定函式模型,然後進行分工實現,一個人A實現上層的展示與呼叫,一個人B實現三種運算。

A是實現的部分

#include<iostream>
using namespace std;
typedef int(*Func)(int, int);  //通過函式指標提前約定呼叫規範。
void player(Func p)
{
	cout << p(10, 8) << endl;
}
int main()
{
	system("pause");
	return 0;
}

B實現的部分:

int func1(int a, int b)
{
	return a + b;
}
int func2(int a, int b)
{
	return a * b;
}
int func3(int a, int b)
{
	return a - b;
}

B需要給A一個myfun.h標頭檔案,裡面放了三個函式的宣告,和一個func.cpp檔案裡面放了三個函式的實現。B只需要這兩個檔案給A,A就可以在實際中將myfun.h標頭檔案聲明後對,裡面的函式進行呼叫,完全不需要考慮B如何是實現的。

最終A的程式碼:

#include<iostream>
using namespace std;
#include"myfunc.h"

typedef int(*Func)(int, int);   //通過函式指標提前約定呼叫規範。
void player(Func p)
{
	cout << p(10, 8) << endl;
}
int main()
{
	player(func1);
	player(func2);
	player(func3);
	system("pause");
	return 0;
}

這樣就是簡單實現了程式碼的解耦合.

四、函式模板

所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。

函式模板定義語法:

函式模板呼叫:

                       模板函式名<引數資料型別>(引數列表)    //顯示引數型別呼叫(強制呼叫)

                       模板函式名<>(引數列表)                          //顯示呼叫(強制呼叫)

                       模板函式名(引數列表)                              //自動資料型別推導(非強制型別呼叫)

函式過載與模板函式

模板函式和普通函式一起時的呼叫規則:

1 、函式模板可以向普通函式一樣被過載

2、C++編譯器優先考慮普通函式

3、如果函式木板可以產生一個更好的匹配(普通函式會發生引數型別的強制轉換),那麼選擇模板

3、可以通過空模板實參列表(   模板函式名<>(引數列表)   )的語法限定編譯器只能通過模板匹配。

4、函式模板不允許自動型別轉換(引數型別轉換),普通函式能夠發生自動型別轉換。

函式模板機制結論

編譯器並不是把函式模板處理成能夠處理任意類的函式,編譯器是從函式模板通過具體型別產生不同的函式,編譯器會對函式模板進行兩次編譯:(1)在宣告的地方對模板程式碼本身進行編譯;(2) 在呼叫的地方對替換引數後的程式碼進行編譯

五、類模板

1、模板類中如果使用了建構函式,則遵守以前的類的建構函式的呼叫規則

2、當子類從模板類繼承時,需要指定模板類的資料型別。

3、類模板做函式引數時,需要我們將模板類例項化,因為編譯器會對函式進行記憶體分配(包括引數等),因此需要知道具體的資料型別,只有資料型別固定下來,才可以分配記憶體。

類模板和函式模板的機制一樣都是兩次編譯,所以不同的模板類接收不同的引數列表之後,生成的類是完全獨立的,擁有獨立的內記憶體空間。

有static 關鍵字定義的成員變數的模板類,每個類模板在確定引數後都會有獨自的static關鍵字的儲存單位,之間相互不影響,但是static關鍵自定義的成員變數的屬性在同一個類中的屬性不變。

比如有一個類模板

template <typename T> 
class A
{
public:
   static int a;
private:
   T data;
}
int A::a = 0;

我們使用它例項化引數後生成了兩個模板類 A<int> 、A<char> 並建立了 兩個不同型別的物件a1,c1;

A<int> a1;
A<char> c1;

所有的A<int> 模板類建立的物件共同使用同一塊記憶體存放static關鍵字定義的成員變數;所有的A<char> 模板類建立的物件共同使用同一塊記憶體存放static關鍵字定義的成員變數,兩個互不相同。

六、類模板的好處

1、它是型別無關的,具有高度的可複用性

2、它在編譯時而不是執行時檢查資料型別,保證了型別安全。

3、與平臺無關,可移植性。

  函式模板與類模板的區別:函式模板的例項化是由編譯程式在處理函式呼叫時自動完成的;而類模板的例項化必須由程式設計師在程式中顯式地指定。