天天看點

C++學習之函數模闆與類模闆

泛型程式設計(Generic Programming)是一種程式設計範式,通過将類型參數化來實作在同一份代碼上操作多種資料類型,泛型是一般化并可重複使用的意思。泛型程式設計最初誕生于C++中,目的是為了實作C++的STL(标準模闆庫)。

模闆(template)是泛型程式設計的基礎,一個模闆就是一個建立類或函數的藍圖或公式。例如,當使用一個vector這樣的泛型類型或者find這樣的泛型函數時,我們提供足夠的資訊,将藍圖轉換為特定的類或函數。

一、函數模闆

一個通用的函數模闆(function template)就是一個公式,可用來生成針對特定類型或特定值的函數版本。模闆定義以關鍵字template開始,後面跟一個模闆參數清單,清單中的多個模闆參數(template parameter)以逗号分隔。模闆參數表示在類或函數定義中用到的類型或值。

1、類型參數

一個模闆類型參數(type parameter)表示的是一種類型。我們可以将類型參數看作類型說明符,就像内置類型或類類型說明符一樣使用。類型參數前必須使用關鍵字class 或typename:

template <typename T>  // typename和class一樣的
T function(T* p)
{
	T tmp = *p;   // 臨時變量類型為T
	//...
	return tmp;   // 傳回值類型為T
}
           

關鍵字typename和class是一樣的作用,但顯然typename比class更為直覺,它更清楚地指出随後的名字是一個類型名。

編譯器用模闆類型實參為我們執行個體化(instantiate)特定版本的函數,一個版本稱做模闆的一個執行個體(instantiation)。當我們調用一個函數模闆時,編譯器通常用函數實參來為我們推斷模闆實參。當然如果函數沒有模闆類型的參數,則我們需要特别指出來:

int a = 10;
cout << function(&a) << endl;     // 編譯器根據函數實參推斷模闆實參

cout << function<int>(&a) << endl;   // <int>指出模闆參數為int 
           

2、非類型參數

在模闆中還可以定義非類型參數(nontype parameter),一個非類型參數表示一個值而非一個類型。我們通過一個特定的類型名而非關鍵字class或typename來指定非類型參數:

// 整形模闆
template<unsigned M, unsigned N>
void add()
{
	cout<< M+N << endl;
}

// 指針
template<const char* C>
void func1(const char* str)
{
	cout << C << " " << str << endl;
}

// 引用
template<char (&R)[9]>
void func2(const char* str)
{
	cout << R << " " << str << endl;
}

// 函數指針
template<void (*f)(const char*)>
void func3(const char* c)
{
	f(c);
}

void print(const char* c) { cout << c << endl;}

char arr[9] = "template";   // 全局變量,具有靜态生存期

int main()
{
	add<10, 20>();
	func1<arr>("pointer");
	func2<arr>("reference");
	func3<print>("template function pointer");
	return 0;
}
           

當執行個體化時,非類型參數被一個使用者提供的或編譯器推斷出的值所替代。一個非類型參數可以是一個整型,或者是一個指向對象或函數的指針或引用:綁定到整形(非類型參數)的實參必須是一個常量表達式,綁定到指針或引用(非類型參數)的實參必須具有靜态的生存期(比如全局變量),不能把普通局部變量 或動态對象綁定到指針或引用的非類型形參。

二、類模闆

相應的,類模闆(class template)是用來生成類的藍圖。與函數模闆的不同之處是,編譯器不能為類模闆推斷模闆參數類型,是以我們必須顯式的提供模闆實參。與函數模闆一樣,類模闆參數可以是類型參數,也可以是非類型參數,這裡就不再贅述了。

template<typename T>
class Array {
public:
	Array(T arr[], int s);
	void print();
private:
	T *ptr;
	int size;
};

// 類模闆外部定義成員函數
template<typename T>
Array<T>::Array(T arr[], int s)
{
	ptr = new T[s];
	size = s;
	for(int i=0; i<size; ++i)
		ptr[i]=arr[i];
}

template<typename T>
void Array<T>::print()
{
	for(int i=0; i<size; ++i)
		cout << " " << *(ptr+i);
	cout << endl;
}

int main()
{
	char a[5] = {'J','a','m','e','s'};
	Array<char> charArr(a, 5);
	charArr.print();

	int b[5] = { 1, 2, 3, 4, 5};
	Array<int> intArr(b, 5);
	intArr.print();

	return 0;
}
           

類模闆的成員函數

與其他類一樣,我們既可以在類模闆内部,也可以在類模闆外部定義其成員函數。定義在類模闆之外的成員函數必須以關鍵字template開始,後接類模闆參數清單。

template <typename T>
return_type class_name<T>::member_name(parm-list) { }
           

預設情況下,對于一個執行個體化了的類模闆,其成員函數隻有在使用時才被執行個體化。如果一個成員函數沒有被使用,則它不會被執行個體化。

類模闆和友元

當一個類包含一個友元聲明時,類與友元各自是否是模闆是互相無關的。如果一個類模闆包含一個非模闆的友元,則友元被授權可以通路所有模闆的執行個體。如果友元自身是模闆,類可以授權給所有友元模闆的執行個體,也可以隻授權給特定執行個體。

// 前置聲明,在将模闆的一個特定執行個體聲明為友元時要用到
template<typename T> class Pal;

// 普通類
class C {
	friend class Pal<C>;  // 用類C執行個體化的Pal是C的一個友元
	template<typename T> friend class Pal2; //Pal2所有執行個體都是C的友元;無須前置聲明
};

// 模闆類
template<typename T> class C2 {
	// C2的每個執行個體将用相同類型執行個體化的Pal聲明為友元,一對一關系
	friend class Pal<T>;
	// Pal2的所有執行個體都是C2的每個執行個體的友元,不需要前置聲明
	template<typename X> friend class Pal2; 
	// Pal3是普通非模闆類,它是C2所有執行個體的友元
	friend class Pal3;
};
           

類模闆的static成員

類模闆可以聲明static成員。類模闆的每一個執行個體都有其自己獨有的static成員對象,對于給定的類型X,所有class_name<X>類型的對象共享相同的一份static成員執行個體。

template<typename T>
class Foo {
public:
	void print();
	//...其他操作
private:
	static int i;
};

template<typename T>
void Foo<T>::print()
{
	cout << ++i << endl;
}

template<typename T>
int Foo<T>::i = 10;  // 初始化為10

int main()
{
	Foo<int> f1;
	Foo<int> f2;
	Foo<float> f3;
	f1.print();   // 輸出11
	f2.print();   // 輸出12
	f3.print();   // 輸出11
	return 0;
}
           

我們可以通過類類型對象來通路一個類模闆的static對象,也可以使用作用域運算符(::)直接通路靜态成員。類似模闆類的其他成員函數,一個static成員函數也隻有在使用時才會執行個體化。

繼續閱讀