1. 程式人生 > >C++:模板總結

C++:模板總結

前提:我們要實現一個通用的交換函式swap,就是讓它適用於任何一個型別。比如:int,char,double......,但是我們要寫很多交換的函式,對swap函式進行過載。那我們如何解決這個問題?可不可以給編譯器一個模子,讓編譯器來給我們實現不同型別的轉化?

函式模板

1.概念:函式模板代表了一個函式家族,在使用時被引數化,根據實參型別產生特定的函式。

2.函式模板格式

template<class T>//不能用struct代替class
T add(const T& a,const T&b)
{
	return a + b;
}
int main()
{
	int a = 1, b = 2;
	double c = 1.2, d = 2.4;
	cout << add(a, b) << endl;;
	cout<<add(c, d)<<endl;
	system("pause");
	return 0;
}

3.函式模板的原理

在編譯器編譯階段,編譯器會根據傳入的實參型別來推演出對應生成的型別來呼叫,就是將T確定成相應的函式型別,比如:int,double...。

4.函式模板的例項化

用不同型別的引數使用模板時,就是對函式模板的例項化。例項化可以分成兩種:隱形例項化和顯性例項化。

        (1)隱形例項化:讓編譯器來推演出模板引數的實際型別。

template<class T>
T add(const T& a,const T&b)
{
	return a + b;
}
int main()
{
	int a = 1, b = 2;
	double c = 1.2, d = 2.4;
	add(a, b);
	add(c, d);
	//add(a, d);error C2782: “T add(const T &,const T &)”: 模板 引數“T”不明確	

	add(a, (int)d);
	system("pause");
	return 0;
}

上述程式碼為何會出錯?

因為在編譯階段,看到實參a時打算將T推演成int型別,然後又遇到實參d,是double型別,所以編譯器就不知道是將T推演成哪一個引數,就會很直接的報錯。

所以,這裡的錯誤處理方式有兩種:1.自己強轉;2.使用顯示例項化

            (2)顯示例項化:在函式名字後的<>中指定模板引數的實際型別

template<class T>
T add(const T& a,const T&b)
{
	return a + b;
}
int main()
{
	int a = 1, b = 2;
	double c = 1.2, d = 2.4;
        //顯示的例項化
	add<int>(a, d);
	system("pause");
	return 0;
}

5.模板函式的匹配原則

(1)一個非模板函式可以和一個同名的函式模板同時存在,而且該函式模板任然可以被例項化。

(2)如果一個非模板函式和同名的函式模板同時存在,那麼編譯器會優先呼叫非模板引數。

template<class T1,class T2>//函式模板
T2 add(const T1& a,const T2&b)
{
	return a + b;
}

int add(const int a, const int b)//非模板函式
{
	return a+b;
}
int main()
{
	int a = 1, b = 2;
	double c = 1.2, d = 2.4;

	cout<<add(a, b)<<endl;//呼叫非模板函式
	cout << add(a, c) << endl;/呼叫函式模板
	system("pause");
	return 0;
}

3.模板函式不允自動型別轉化,但是普通函式可以進行自動型別轉化。

類模板

用類模板實現順序表的一部分

template<class T>//類模板的格式
class myvector
{
public:
	myvector()
		:data(new T[10])
		, size(0)
		, capacity(10)
	{

	}
	void PushBack(const T& mydata)
	{
		data[size++] = mydata;
	}
	void PopBack()
	{
		--size;
	}
	int Size()
	{
		return size;
	}
	T& operator[](int pos)
	{
		assert(pos < size);
		return data[pos];
	}

	~myvector()
	{
		if (data)
		{
			delete[] data;
		}
	}

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

int main()
{
	myvector<int> a;//類模板的例項化
	a.PushBack(1);	
        a.PushBack(2);
	a.PushBack(3);
	a.PushBack(4);
	for (int i = 0; i < a.Size(); i++)
		cout << a[i]<< " ";
	system("pause");
	return 0;
}

類模板的例項化和函式模板的例項化不同,類模板例項化需要在類模板名字後加<>,然後將型別放在<>即可。

非型別的模板引數

模板引數有兩種,一種是型別引數,另一種是非型別引數。

型別引數就是在模板引數列表中,跟class或者typename之類的引數型別名稱

非型別形參就是用一個常量作為類模板的一個引數,在類模板中可以將這個引數當成常量來使用。

//定義一個靜態的陣列
template<class T,int N=10>
class ARRAY
{
public:
	T& operator[](int index)  
	{ 
		return ARRAY[index]; 
	}      
	const T& operator[](int index)const    
	{
		return ARRAY[index]; 
	}       
	int Size()const   
	{ 
		return _size; 
	}       
	bool Empty()const   
	{ 
		return 0 == _size; 
	}

private:
	T _array[N];//N在類中是個常量,可以直接使用
	int _size;
};

需要注意的是:浮點型(float)、類物件和字串是不允許作為非型別模板引數的,非型別的模板引數必須在編譯器就能確認結果,可以將它定義成一個巨集的常量。

模板的特化

通常情況下,模板可以實現一些與型別無關的程式碼,但是對於一些特殊型別的可能會得到一些錯誤的結果。所以我們就需要對模板進行特化,就是在原模版的基礎上,針對特殊型別所進行的特殊的實現方式。模板的特化分成:函式模板特化和類模板特化。

1.函式模板的特化

步驟:(1)必須有一個基礎的函式模板

          (2)在關鍵字templaste後邊加<>

          (3)<>中需要加指定的型別

          (4)引數列表必須和基礎函式模板引數型別完全相同

template<> bool IsEqual<char*>(char*& left, char*& right) 
{   
     if(strcmp(left, right) > 0)       
          return true;      
     return false; 
}

其實這麼實現沒有必要,我們可以採用更加簡單的直接寫出函式就可以。

2.類模板的特化

類模板的特化分成兩種,一種全特化,另一種是偏特化。

(1)全特化就是模板引數列表中所有的引數都被確化

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

template<>
class Data<int ,char>//全特化
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	int _d1;
	char _d2;
};

(2)偏特化是對模板引數進行進一步的條件限制的特化版本

             第一種:部分特化

template<class T>
class Data<T, char>//對第二個引數型別特化成char
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T _d1;
	char _d2;
};

           第二種:對引數更進一步的條件限制

template <typename T1, typename T2>//兩個引數特化成指標型別
class Data<T1*, T2*>
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

模板特化的應用之型別萃取

1.實現一個通用的拷貝函式

拷貝我們可以使用memcpy函式,也可以用for迴圈一個一個的賦值,但是我們需要了解到這兩種拷貝的優缺點。

memcpy函式可以對任意的內建型別進行拷貝,但是拷貝自定義型別的時候就會出錯,而且它是淺拷貝的方式。

迴圈賦值的方式雖然可以對自定義型別進行拷貝,但是程式碼的效率比較差。

所以我們在實現通用的拷貝函式的時候,可以將這兩種拷貝方式結合在一起。

bool isPOD(const char *name)
{
	const char *t[] = { "long", "char", "short", "int", "float" };
	for (size_t i = 0; i < sizeof(t) / sizeof(t[0]); i++)
	{
		if (strcmp(t[i], name) == 0)
			return true;
	}
	return false;
}
template<class T>
void Copy( T* dst,T* src,size_t size)
{
	if (isPOD(typeid(T).name()))//判斷是不是內建型別
		memcpy(dst,src,size);
	else
	{
		for (size_t i = 0; i < size; i++)
			dst[i] = src[i];
	}
}

int  main()
{
	string strarr1[3] = { "11", "22", "33" };
	string strarr2[3];
	Copy(strarr2, strarr1, 3);//用迴圈賦值
	int a = 100;
	int b = 0;
	Copy(&b, &a, 4);//用memcpy
	system("pause");
	return 0;
}

2.型別萃取

struct TrueType//內建型別
{
	static bool Get()
	{
		return true;
	}
};

struct FalseType//自定義型別
{
	static bool Get()
	{
		return false;
	}
};
template<class T> 
struct TypeTraits
{
	typedef FalseType IsPODType;
};

template<> struct TypeTraits<char>  { typedef TrueType   IsPODType; };
template<> struct TypeTraits<int>   { typedef TrueType   IsPODType; };
template<> struct TypeTraits<short> { typedef TrueType   IsPODType; };
template<> struct TypeTraits<float> { typedef TrueType   IsPODType; };
template<> struct TypeTraits<long>  { typedef TrueType   IsPODType; };
//......
template<class T>
void Copy(T* dst, T* src, size_t size)
{
	if (TypeTraits<T>::IsPODType::Get())
		memcpy(dst, src, size);
	else
	{
		for (size_t i = 0; i < size; i++)
			dst[i] = src[i];
	}
}

int main()
{
	string strarr1[3] = { "11", "22", "33" };
	string strarr2[3];
	Copy(strarr2, strarr1, 3);
	int a = 100;
	int b = 0;
	Copy(&b, &a, 4);
	system("pause");
	return 0;
}

我們使用兩個結構體區分開了是自定義型別還是內建型別?

當我們拷貝strarr1和strarr2的時候,由於TypeTraits<string>沒有被特化,所以該類模板中的IsPODType剛好為類FalseType,而FalseType中Get函式返回true,自定義型別使用賦值方式拷貝 

當我們拷貝a和b的時候,由於TypeTraits<int>被特化,所以該類模板中的IsPODType剛好為類TrueType,而TrueType中Get函式反悔true,證明是內建型別,內建型別需要用memcpy