天天看點

C++ 泛型程式設計(一):模闆基礎:函數模闆,類模闆,模闆原理,模闆比對規則函數模闆 類模闆

  • 類模闆
  • 函數模闆

泛型程式設計

泛型程式設計,泛型即是指具有在多種資料類型上皆可操作的含義,其實就是能夠幫助開發者編寫完全一般化并可重複使用的算法,同樣的工作不需要做多次,同樣的算法針對不同的類型也不應該寫多次,是以需要通過某種途徑,來使一個容器或者算法,能夠相容所有的類型,這就是泛型程式設計。

void Swap(int& left, int& right) {
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right) {
	double temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right) {
	char temp = left;
	left = right;
	right = temp;
}
           

就比如非常常用的swap函數,雖然c++支援重載,但是如果對于這種函數我們每一個都要自己去實作,是一件很麻煩并且多餘的工作,因為我們将重複邏輯的東西多次實作。

class Stack
{

private:
	int capacity;
	int size;
	int* arr;
};
           

又比如一個資料結構,如果我們實作了一個棧,如果他此時要存儲其他的資料類型,那我們就必須還要重新修改一個,這無疑是多餘的。

是以,c++基于重載這一項機制,實作了模闆程式設計,模闆作為類或者函數的藍圖,可以針對不同類型,來實作對應操作。

函數模闆

函數模闆

該函數模闆與類型無關,在使用時被參數化,根據實參類型産生函數的特定

類型版本。

用法

template<typename T1, typename T2,......,typename Tn>
//typename即類型名,也可以用class替代,這裡的class指的是類型,不能用struct替換
           

隻需要在對應的函數前面加上這一句即可,然後将所有需要替換類型的參數改為上面的T1,T2即可

template<class	T>
void Swap(T& left, T& right) {
	T temp = left;
	left = right;
	right = temp;
}

int main()
{
	int i = 3, j = 4;
	double a = 3.4, b = 5.6;
	char x = 'x', y = 'y';

	Swap(i, j);
	Swap(a, b);
	Swap(x, y);

	cout << i << ' ' << j << endl;
	cout << a << ' ' << b << endl;
	cout << x << ' ' << y << endl;
}
           
C++ 泛型程式設計(一):模闆基礎:函數模闆,類模闆,模闆原理,模闆比對規則函數模闆 類模闆

可以看到,這樣就可以适用于多重類型。

函數模闆的原理

下面來讨論一下,當我們調用這個模闆函數的時候,是通過這個模闆函數來實作對應的功能?還是它可能會重載成其他函數,再調用其他的函數呢?

這是隻需要看看彙編代碼就可以了

C++ 泛型程式設計(一):模闆基礎:函數模闆,類模闆,模闆原理,模闆比對規則函數模闆 類模闆

可以看到,它調用的是三個不同的函數,這時候就可以推論出來。

模闆函數本身并不是一個函數,而是一個藍圖,通過識别我們傳入的參數,然後在底層生成一個該類型的模闆函數,并調用該函數。并且這個模闆隻在第一次使用的時候才會執行個體化出來

并且這個階段是在預處理的時候就進行了,因為我們仔細思考一下,如果它是在編譯階段才進行函數的生成,那肯定是無法通過文法的檢查的,是以這一階段隻能在預處理進行。

函數模闆的執行個體化

模闆的執行個體化有兩種方法,一種是顯式執行個體化,一種是隐式執行個體化

隐式執行個體化
template<class T> T Add(const T& left, const T& right) 
{
	return left + right;
}

int main()
{
	int i = 3, j = 4;
	double a = 3.4, b = 5.6;
	Add(i, j);
	Add(a, b);

	/*
	這時編譯器就會報錯,因為需要通過參數推演出對應類型,但是此時就無法
	推演出到底是将T推演成double還是将T推演成int
	Add(i, a);
	*/

	// 此時有兩種處理方式:1. 使用者自己來強制轉化 2. 使用顯式執行個體化
	Add(i, (int)a);
	return 0;
}
           

顯式執行個體化

int main() {
 int i = 3;
 double x = 3.4;
 
 // 顯式執行個體化
 Add<int>(i, x);
 
 return 0; 
 }
           

這種則是用尖括号來顯式的聲明,STL中的容器等就是采用這種顯式的執行個體化來明确參數的類型。

函數模闆的比對規則(引用)

  1. 一個非模闆函數可以和一個同名的函數模闆同時存在,而且該函數模闆還可以被執行個體化為這個非模闆函數,這時如果使用隐式執行個體化,則會調用非模闆函數,如果使用顯式執行個體化,則調用模闆函數
//非模闆函數
int Add(int left, int right) 
{
	return left + right;
}

template<class T> 
T Add(T left, T right) 
{
	return left + right;
}

int main()
{
	Add(1, 2); // 調用非模闆函數
	Add<int>(1, 2); // 調用模闆函數
}
           
  1. 對于非模闆函數和同名函數模闆,如果其他條件都相同,在調動時會優先調用非模闆函數而不會從該模闆産生出一個執行個體。如果模闆可以産生一個具有更好比對的函數, 那麼将選擇模闆
// 專門處理int的加法函數
int Add(int left, int right) {
	return left + right;
}

// 通用加法函數
template<class T1, class T2>
T1 Add(T1 left, T2 right) {
	return left + right;
}

void Test()
{
	Add(1, 2); // 與非函數模闆類型完全比對,不需要函數模闆執行個體化
	Add(1, 2.0); // 模闆函數可以生成更加比對的版本,編譯器根據實參生成更加比對的Add函 數
}
           
  1. 模闆函數不允許自動類型轉換,但普通函數可以進行自動類型轉換

類模闆

用法

類模闆的用法和函數模闆其實一樣

template<class T>
class Stack
{

private:
	int capacity;
	int size;
	T* arr;
};
           

這裡的Stack不是一個具體的類,他是類模闆,也就是一個藍圖,通過這個模闆來識别參數的類型并生成對應的模闆類。是以可以了解為類模闆是一個類家族,模闆類是通過類模闆執行個體化的具體類

如果類中的成員函數需要在類外定義的話,需要每一個函數前都要聲明一次類模闆的參數清單

template<class T>
class Stack
{
public:
	void push();
	void pop();
private:
	int capacity;
	int size;
	T* arr;
};

template<class T>
void Stack<T>::push()
{

}

template<class T>
void Stack<T>::pop()
{

}
           

還有一個需要注意的地方就是,類模闆的所有成員函數都是模闆函數

類模闆的執行個體化

類模闆隻能夠通過顯式執行個體化

如STL中的幾個容器都是通過類模闆實作的

stack<int> s1;
	stack<double> s2;
           

stack<int>并不是類名,而是類型,stack才是類名

繼續閱讀