閱讀233 返回首頁    go 阿裏雲 go 技術社區[雲棲]


【C++】模板總結

模板

模板是一種泛型編程的機製,也是一種複用的手段。

//模板函數的格式:
template<class 形參1,... ,class 形參n>
返回值 fun(參數列表)
{...}

//模板類的格式:
template<class 形參1,... ,class 形參n>
class A
{...};

如何實例化

編譯器調用模板函數時,編譯器會根據實參的類型,推演出模板的類型,並再生產相應的代碼。

A<int> a;  //類隻能顯式實例化,沒法推演。 
fun<int>(1,2);  //顯式實例化,不需要推演
fun(1,2)    //需要推演


如果你想學習C/C++可以來這個裙,首先是330,中間是859,中間是766,裏麵可以學習和交流,也有資料可以下載。

模板函數的重載

根據模板函數的形參類型的不同,調用函數時編譯器會去找和自己類型更匹配的模板進行推演並實例化。

  • 浮點數和類類型不可以作為非類型模板參數

模板的特化

為什麼需要特化,因為在對不同類型的參數進行一些相同的處理時,如增容,拷貝等,不同類型的處理方式可能有些不同,如string增容時和其他類型就不同,它需要考慮深拷貝,所以要另外處理,這時候就用特化。

定義模板類時,會先去找實例化的好的且類型匹配的模板,所以調用的模板類的優先級 全特化,半特化,普通模板

  • 全特化 特化所有的模板,就是製定所有模板的類型
template<>
class<int, char>
{...};
  • 偏特化 特化部分模板參數+指定模板參數是某種特殊的類型 如指針或引用:template<T*>

用模板實現容器的適配器

我們可以用其他容器實現某一個容器,如我們可以用順序變實現stack,也可以鏈表實現stack,那麼可以寫一個模板類,

template<class T, class Container>
class stack
{...};

這樣我們想要什麼容器實現,就可以在定義時,傳什麼容器。

 隊列適配器和棧適配器代碼:github


類型萃取

模板特化的延伸,提取出類型,提前把處理動作一樣的類型typedef成一個類型,其他自定義的類型就也可以typedef成一個類型去同一個指定的動作。這樣就可以根據提取出來的類型判斷它屬於萃取後的哪種類型,然後進行相應的操作。

為什麼要有類型萃取?

如果一個類裏的成員函數對不同類型需要有不同操作的的地方隻有那麼一小塊代碼,如有一個順序表的類,string需要深拷貝,其他內置類型可以淺拷貝。那麼模板顯然很不劃算,因為隻能對函數或者類進行特化,上麵如果要用模板的話,需要再特化一個相同的類。現在要根據不同類型對部分代碼塊進行相應的操作,這時就需要類型萃取,通過把類型萃取後,在需要不同操作的地方可以通過萃取的這個類型重載這個函數,也可以在函數裏需要不同操作的地方進行if else判斷相應類型來進行相應的操作。

看下麵代碼理解萃取

#include<iostream>
#include<string>
#include<assert.h>
using namespace std;

struct TypeFalse
{
	bool Get()
	{
		return false;
	}
};
struct TypeTrue
{
	bool Get()
	{
		return true;
	}
};

template<class T>
struct TypeTraits
{
	typedef TypeFalse IsFodType;
};

template<>
struct TypeTraits<int>
{
	typedef TypeTrue IsFodType;
};
template<>
struct TypeTraits<char>
{
	typedef TypeTrue IsFodType;
};
template<>
struct TypeTraits<double>
{
	typedef TypeTrue IsFodType;
};



//順序表
template<typename T>
class Seqlist
{
public:
	Seqlist()
		:_a(NULL)
		, _size(0)
		, _capacity(0)
	{}
	void _CheckCapacity()
	{
		if (_capacity <= _size)
		{
			_capacity = _capacity * 2 + 3;
			T* tmp = new T[_capacity];
			if (TypeTraits<T>::IsFodType().Get() == false) //自定義類型或者string
			{
				for(size_t i = 0; i < _size; i++)    ///深拷貝
				{
				    tmp[i] = _a[i];
				}
			}
		    else
			{
				memcpy(tmp, _a, sizeof(T)*_size);         //值拷貝 
			}
			delete _a;
			_a = tmp;
		}
	}
	Seqlist(const Seqlist<T>& s)
		:_a(NULL)
		, _size(0)
		, _capacity(0)
	{
		while (_size < s._size)
		{
			PushBack(s._a[_size]);
		}
		_capacity = s._capacity;
	}
	void PushBack(const T& x)
	{
		_CheckCapacity();
		_a[_size] = x;
		_size++;
	}

	~Seqlist()
	{
		if (_a != NULL)
		{
			delete[] _a;
			_size = _capacity = 0;
			_a = NULL;
		}
	}

	void print()
	{
		for (int i = 0; i < _size; i++)
		{
			cout << _a[i] << endl;
		}
	}
private:
	T* _a;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Seqlist<int> s1;
    s1.PushBack(2);
    s1.PushBack(1);
    s1.PushBack(3);
    s1.PushBack(4);
	s1.print();
	Seqlist<string> s2;
	s2.PushBack("rrrrrrrrrrrrrrrrrrrrrr");
	s2.PushBack("qqqq");
	s2.PushBack("wwww");
	s2.PushBack("eeee");
	s2.print();
	return 0;
}

模板的分離編譯

模板類的成員函數定義放在data.cpp源文件,聲明放在data.h源文件,在另外一個main.cpp文件裏調用使用模板類調用成員函數則會發生鏈接錯誤。所以模板不支持分離編譯

模板為什麼不支持分離編譯?

鋪墊:
程序源文件在執行鏈接時,如果函數的定義和聲明不在同一文件,那麼鏈接時就會去其他.o文件的符號表裏去找函數定義的處的地址,找到函數定義的地址後,並添加到調用函數處的call命令後麵(call fun 0xadd),才能鏈接成功,才可生生執行程序。
解釋:
因為模板沒有實例化時是不生成相應的代碼的,因為編譯的時候兩個原文件互不影響,所以沒有實例化,因此在鏈接的時候就找不函數定義的地方,那麼就會出現鏈接錯誤。

解決模板的分離編譯

  • 顯式實例化,在定義模板的地方,主動的去實例化相應類型的模板。template class<int>;
  • 把聲明定義都寫在.hpp裏,main.c調用模板類的函數時就可以了,其實.hpp相當於.h,預處理時就會頭文件展開在.cpp裏。

最後更新:2017-09-18 15:33:56

  上一篇:go  初來咋道的鵬小少
  下一篇:go  【C++】多態總結