1. 程式人生 > >C++(15):模板(Template)

C++(15):模板(Template)

引言

還是那句話。為了更多的相容性。為了寫更少的程式碼。

有了模(mu,二聲)板。模板不是類。

模板分函式模板,類模板。以下分別總結。

函式模板

基本寫法

template <typename T>
void swap1(T & t1, T & t2){
	T temp;
	temp = t1;
	t1 = t2;
	t2 = temp;
}
	int x1 = 1;
	int x2 = 3;
	cout<<"x1 = "<<x1<<", x2 = "<<x2<<endl;
	swap1(x1,x2);
	cout<<"x1 = "<<x1<<", x2 = "<<x2<<endl;
x1 = 1, x2 = 3
x1 = 3, x2 = 1
因為是引用傳遞,所以確實交換了值。 注意。 1. 要寫關鍵字template以示這是模板,之後寫<模板引數>; 2. 儘量不要寫class T,class U,不要寫“class”,這樣具有誤導性。儘管java就是這麼寫。儘量寫“typename”; 3. 之後寫函式體。 4. 編譯器在編譯的時候,會推斷出這些T、U究竟是什麼型別,進而例項化一個特定版本的函式; 5. 上面可以寫swap1(x1,x2),但是不能寫swap(1,3)。編譯器報錯,說不是int型變數… 6. 不能寫swap(i,d),其中i是int型,d是double型。因為這個函式模板宣告的時候,兩個形參都是T,實參在傳引數的時候,型別要一致! 第5條的解決方案。函式形參加const,實參就可以是常量了
template <typename T>
bool compare(const T & v1,const T & v2) {
	return (v1<=v2);
}
此時,呼叫這個函式時,實參可以是同一型別的常數,也可以是同一型別的變數。
	cout<<compare(2.3,3.4)<<endl;
	cout<<compare(x1,x2)<<endl;
1 0
第6條的解決方案。模板引數列表中,引數寫兩個不同的。同時函式形參,也寫兩個不同的,就支援不同型別的變量了。
template <typename T1,typename T2>
bool compare(const T1 & v1,const T2 & v2) {
	return (v1<=v2);
}
	cout<<compare(x1,0.3)<<endl;
越來越相容了……。 甚至還可以寫成:compare(const T1 & t1, int i)這種樣子…

函式模板的過載

Can overload function templates only when each version takes a different argument list
– allow compiler to distinguish
template <typename T>
T FindMax(T x, T y) {
T max = x;
if (y > max)
max = y;
return max;
}

template <typename T>
T FindMax(
T x, T y, T z) {
T max = x;
if (y > max)
max = y;
else if (z > max)
max = z;
return max;
}

【經典案例】 用模板實現從3個學生中,找出最高分的同學。 連返回的資料型別都可以寫成模板!太靈活了。
#include <iostream>
using namespace std;

// 三個數找大小都不會了 -_-!
// 先假設第一個數是最大的,然後跟第二個比,再跟第3個比!

// 【嚴重警告】 最基本的查詢、排序還是要會的啊!
// 【嚴重警告】 最基本的三種資料結構必須要會啊!

template <typename T>
T findMax(T & a, T & b, T & c) {

	T maxT = a;
	
	if (maxT<b)
	{
		maxT = b;
	}
	if (maxT<c)
	{
		maxT = c;
	}

	return maxT;
}

// overloading function findMax
template <typename T>
T findMax(T & a, T & b) {
	
	return (a<b)?b:a;
}


class CStu
{
	/* friend function */
	friend ostream & operator<<(ostream & os, CStu & cStu);
	// friend bool operator<(CStu & o1, CStu & o2); // overloading operator <
		
public:
	CStu(double score_para):score(score_para){};
	~CStu(){};

	// overloading operator < as a member function
	bool operator<(CStu & o2);

	void setScore(double score) {
		this->score = score;
	}

	double getScore() const {
		return this->score;
	}

private:
	double score;
};

/* friend function 需要在類外面定義 */
 ostream & operator<<(ostream & os, CStu & cStu){
	 os<<cStu.getScore();
	 return os;
 }
//  bool operator<(CStu & o1, CStu & o2) {
// 	 return (o1.getScore()<o2.getScore()) ?  true : false;
//  }

 bool CStu::operator<(CStu & o2) {
	 return (this->getScore()<o2.getScore()) ? true : false;
 }

int main () {

	CStu cStu1(88.6);
	CStu cStu2(77.4);
	CStu cStu3(66.3);

	CStu cStuMax = findMax(cStu1,cStu2,cStu3);
	cout<<"最高分:"<<cStuMax<<endl;

	CStu cStuMax2 = findMax(cStu2,cStu3);
	cout<<"最高分:"<<cStuMax2<<endl;

	system("pause");
	return 0;

}
執行結果。 最高分:88.6
最高分:77.4
幾點注意。 1. 3個數,找出最大的。先假設第一個數是最大的,然後跟第二個比,再跟第3個比! 2. 現在形參確實是T & ,是一個類了!所以要對<運算子進行過載!注意啊,"<"也是一個函式!返回值是bool型別的! 3. 過載可以通過成員函式過載,也可以用友元函式進行過載。 Review:運算子過載

顯式地指定型別

When calling a template function, the arguments dictate the types to be used
To override a deduced type:
someFunction<char>(someArgument);
- useful when at least one of the types you need to generate in the function is not an argument
【經典案例】
#include <iostream>
#include <string>
using namespace std;

// 在呼叫時顯示地指定模板的型別
template <class T> 
T doubleVal(T val) {
	val *= 2;
	return val;
}

template <class T, class U>
T tripleVal(U val) {
	T temp = val * 3;
	return temp;
}

template <class T, class U>
U tripleVal2(T val) {
	T temp = val * 3;
	return temp;
}

template <class U, class T>
T tripleVal3(U val) {
	T temp = val * 3;
	return temp;
}


int main () {

	int a = 4;
	double b = 8.8;

	cout <<a<<" & "<<doubleVal(a)<<"\n";
	cout <<b<<" & "<<doubleVal(b)<<"\n";
	cout <<b<<" & "<<doubleVal<int>(b)<<"\n"; // 這個<int>的意思是,把T直接指定int型
	// 因此,輸入的時候,double就直接被轉成了int型

	cout << tripleVal<int>(a) << "\n"; // 把模板裡第一種型別,T,指定為int --> 輸出12
	cout << tripleVal<int>(b) << "\n"; // 把模板裡的第一種型別,T,指定為int --> 8.8*3 = 26.4 --> 轉int 輸出26
	cout << tripleVal<int,double>(b)<< "\n"; // --> 把第一種型別,T,指定為int,把第二種型別,U,指定為double --> 8.8*4=26.4 --> 轉int // 輸出26
	cout << tripleVal<int, int>(b) <<"\n"; // 把 T 和 U 都指定為 int型, --> 8.8變成8, 24
	cout << tripleVal2<int,double>(b)<< "\n";  // 第一種型別,還是T!
	cout << tripleVal2<int, int>(b) <<"\n"; // 
	cout << tripleVal3<int,double>(b)<< "\n"; 
	cout << tripleVal3<int, int>(b) <<"\n"; 


	system("pause");
	return 0;

}

執行結果。 4 & 8
8.8 & 17.6
8.8 & 16
12
26
26
24
24
24
24
24 注意,模板的顯式指定,是從template <typename T1, typename T2, ...> 這裡開始,從左往右,顯式指定!

類模板

寫法

A class template defines a family of classes
– serve as a class outline to generate many classes  --> 在編譯時會根據你的程式碼,推測生成哪些類。
– specific classes are generated during compile time --> 注意,是在編譯時! Class templates promote code reusability --> 類模板提高了程式碼的可重複利用性
– reduce program development time
– used for a need to create several similar classes at least one type is generic (parameterized) 當有一個或幾個引數都是比較泛化的情況時,可以考慮用類模板。
Terms “class template” and “template class” are used interchangeably.
/************************************************************************/
/* 
 類模板。
 qcy
 2016年11月26日14:31:11
 * 
/************************************************************************/

#include <iostream>
using namespace std;

// 類中含有暫未指定型別的成員,就是類模板。
template <class T>
class CPerson
{
public:
	CPerson() {}
	CPerson(T num):number(num) {}
	~CPerson() {}

	void setNumber(T num) {
		this->number = num;
	}

	T getNumber() {
		return this->number;
	}

private:
	T number;
};


int main () {

	// 在例項化的時候,就要指明是用什麼型別的了。
	double d = 1.3;
	// CPerson cPerson2(d); // 錯誤。缺少模板的引數列表

	CPerson<double> cPerson1;
	cPerson1.setNumber(19.5);
	cout<<"cPerson的number:"<<cPerson1.getNumber()<<endl;

	// 連宣告一個空指標,都要指定用什麼型別。
	CPerson<double> * cPersonPt1 = nullptr;
	
	CPerson<char> cPerson3(97);
	cout<<"cPerson的number:"<<cPerson3.getNumber()<<endl;

	CPerson<int> cPerson4('A');
	cout<<"cPerson的number:"<<cPerson4.getNumber()<<endl;

	CPerson<char> cPerson5('b');
	cout<<"cPerson的number:"<<cPerson5.getNumber()<<endl;

	system("pause");
	return 0;
}
結果。 cPerson的number:19.5
cPerson的number:a
cPerson的number:65
cPerson的number:b 注意。 1. 在例項化的時候,就要指明是用什麼型別的了。
2. 連宣告一個空指標,都要指定用什麼型別。

模板引數

3 forms of template parameters
– type parameters
– non-type parameters
– template parameters
1. A type parameter defines a type identifier – when instantiating a template class, a specific datatype listed in the argument list substitute for the type identifier
– either class or typname must precede a template type parameter
2. A non-type parameter can be – integral types: int, char, and bool
– enumeration type
– reference to object or function
– pointer to object, function or member
A non-type parameter cannot be
– floating types: float and double
– user-defined class type
– type void
Good e.g. template <int A, char B, bool C> // A也就是一種型別了。在這裡,就是int型了。
class G1 { //... };
template <float* D, double& E> // D從此以後,就是float * 型別
class G2 { //... };
Bad e.g.  template <double F>
class B1 { //... }; //cannot be double
template <PhoneCall P>
class B2 { //... };  //cannot be class
3. A template parameter may have a default argument.
e.g. template <class T=int, int n=10>
class C3 { //... };
C3< > a; // 預設把T當int型,但是要加“<>”
C3 b; //error: missing < >
C3<double,50> c;
C3<char> d;
C3<20> e; //error: missing template argument!要指定T的型別,這裡的20只是說int n的n要是20。編譯器沒有這麼聰明……

自己用模板寫一個Stack

【非常重要的一個例子】
/************************************************************************/
/* 
我自己寫的一個stack
qcy
2016年12月25日10:21:43
*/
/************************************************************************/

#include <iostream>
using namespace std;

template<typename T,int MAX_SIZE>
class MyStack
{
public:
	MyStack();
	~MyStack();

	bool full() {
		return (top == MAX_SIZE);
	}

	bool empty() {
		return (top == 0);
	}

	bool push(T obj) {
		
		bool result;
		
		if (full()) {
			cout<<"stack full"<<endl;
			result =  false;
		}
		else {
			elements[top] = obj;
			top++ ;
			result = true;
		}

		return result;
	}

	T pop() {

		if (empty()) {
			cout<<"stack empty"<<endl;
			exit(-1); // 如果不想退出呢?
		}
		else {
			top--;
			T temp = elements[top];
			return temp;
		}

	}

private:
	int top;
	T elements[MAX_SIZE];
};

template<typename T,int MAX_SIZE>
// 【注意】 在例項化的時候,這裡不是寫int,是寫MAX_SIZE
MyStack<T,MAX_SIZE>::MyStack()
{
	top = 0;
}

template<typename T,int MAX_SIZE>
MyStack<T,MAX_SIZE>::~MyStack()
{
}

int main () {

	MyStack<double,5> stack;
	
	int i = 0;
	while (!stack.full())
	{
		stack.push(i);
		i++;
	}

	while (!stack.empty())
	{
		cout<<stack.pop()<<" ";
	}
	cout<<"\n";


	MyStack<char,10> stack2;
	char c = 'a';
	while (!stack2.full())
	{
		stack2.push(c);
		c++;
	}

	while (!stack2.empty())
	{
		cout<<stack2.pop()<<" ";
	}
	cout<<"\n";

	system("pause");
	return 0;
}
執行結果。 4 3 2 1 0
j i h g f e d c b a
(first in, last out.) 注意。 1. 建構函式如果在類的外面寫,一定要指定模板引數列表! // 【注意】 在例項化的時候,這裡不是寫int,是寫MAX_SIZE template<typename T,int MAX_SIZE>
MyStack<T,MAX_SIZE>::MyStack()  // 要統一。T是一種型別,MAX_SIZE也是一種型別了…
{
// ...
}
2. template<typename T,int MAX_SIZE> // 這裡的MAX_SIZE也是int型了。後面例項化的時候,竟然可以傳一個常數進來!
class MyStack
{ // ... } 3.  不能把建構函式寫到另一個CPP檔案單獨裡面去…!好像暫時我發現是這樣……

模板中的友元和繼承

Friend functions can be used with template classes
– same as with ordinary classes
– simply requires proper type parameters
– common to have friends of template classes, especially for operator overloading Nothing new for inheritance Derived template classes
– can derive from template or non-template class
– derived class is naturally a template class
示例。 1. 定義一個基類模板-->注意,這是模板!一個basic的class template。基礎的類模板。
template <class T>
class TBase {
private:
T x, y;
public:
TBase() {}
TBase(T a, T b) : x(a), y(b) {}
~TBase() {}
T getX();
T getY();
};
template <class T>
T TBase<T>::getX() const { return x; }
template <class T>
T TBase<T>::getY() const { return y; }
2. 從基礎類模板,派生一個(非模板的)類(注意,此時派生的東西,不是模板了!已經成為一個類了!) Derive non-class template from class template --> easy to understand – behave like normal classes 把所有的T啊、U啊這些模板引數,在程式碼裡都指定型別了。現在,TDerived1就不是一個類模板了!而是一個類!
class TDerived1: public TBase<int> {
private:
int z;
public:
TDerived1(int a, int b, int c):
TBase<int>(a,b), z(c) {}
int getZ() { return z; }
};
3. 從基礎類模板,派生一個類模板(注意,此時派生的東西還是模板!) Derive class template from class template
– same as the normal class inheritance 只要還有模板引數沒有指定型別,就還是類模板,而不是類。 這就像,只要還沒有把抽象基類基類所有的純虛擬函式都override實現一遍的話,就還是抽象基類。
template <class T>
class TDerived2 : public TBase<T> {
private:
T z;
public:
TDerived2(T a, T b, T c):
TBase<T>(a,b), z(c) {}
T getZ() { return z; }
};
4. 從基礎類,派生一個類模板(由類生出模板。使用要小心!)
template <class T>
class TDerived3 : public TDerived1 {
private:
T w;
public:
TDerived3(int a, int b, int c, T d): TDerived1(a,b,c), w(d) {}
T getW() { return w; }
};
call TDerived1 constructor with known datatypes for parameters!
5. 主函式這樣呼叫。
TBase<int> c1(0,1);
cout << "TBase: x=" << c1.getX() << " y=" <<
c1.getY() << endl;
TDerived1 c2(1,3,5);
cout << "TDerived1: x=" << c2.getX() << " y=" <<
c2.getY() << " z=" << c2.getZ() << endl;
TDerived2<double> c3(2.2, 4.4, 6.6);
cout << "TDerived2: x=" << c3.getX() << " y=" <<
c3.getY() << " z=" << c3.getZ() << endl;
TDerived3<int> c4(3.5, 6.5, 9.5, 12.5);
cout << "TDerived3: x=" << c4.getX() << " y=" <<
c4.getY() << " z=" << c4.getZ() << " w=" <<
c4.getW() << endl;
結果。 TBase: x=0 y=1 // 類模板的例項化(指定用int型)
TDerived1: x=1 y=3 z=5 //  例項化一個類!已經是類了。
TDerived2: x=2.2 y=4.4 z=6.6 // 例項化一個類模板!(還是模板!)指定用double型!
TDerived3: x=3 y=6 z=9 w=12 // 例項化一個由類派生的類模板!注意,是例項化的模板!所以要指定型別!
// 此時,指定的是int型,所以, // template <typename T1, ... > 的第一個模板引數 T1,在編譯時就已經變成了int! // 因此,後面的12.5也會被轉為12

補充:列舉型別

列舉型別……