title: 模板初階
date: 2019-03-18 20:37:56
tags: Cpp
categories: Cpp
toc: true
泛型編程
告訴編譯器一個模子,讓編譯器根據不同的類型利用該模子來生成代碼。
泛型編程:編寫與類型無關的通用代碼,是代碼複用的一種手段。模板是泛型編程的基礎。
函數模板
函數模板代表了一個函數家族,該函數模板與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本。
函數模板的格式
template<typename T1, typename T2,......,typename Tn>
返回值類型 函數名(參數列表){}
template<typename T> // 模板頭
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
⚠️ 注意:typename 是用來定義模板參數關鍵字,也可以使用 class (切記:不能使用 struct 代替 class)
函數模板的原理
模板是一個藍圖,它本身並不是函數,是編譯器用使用方式產生特定具體類型函數的模具。所以其實模板就是將本來應該我們做的重複的事情交給了編譯器。
在編譯器編譯階段,對於模板函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數以供調用。
⚠️ 注意:理解一下,這裏。比如調用上面的 Swap 函數,傳入 int 和 double 最終編譯後其實調用的是兩個函數,這個可以通過查看彙編代碼或者打印函數地址來驗證。
函數模板的實例化
用不同類型的參數使用函數模板時,稱爲函數模板的實例化。模板參數實例化分爲:隱式實例化和顯式實例化。
隱式實例化
讓編譯器根據實參推演模板參數的實際類型。
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
/*
該語句不能通過編譯,因爲在編譯期間,當編譯器看到該實例化時,需要推演其實參類型 通過實參a1將T推演爲int,通過實參d1將T推演爲double類型,但模板參數列表中只有一個T, 編譯器無法確定此處到底該將T確定爲int 或者 double類型而報錯
注意:在模板中,編譯器一般不會進行類型轉換操作,因爲一旦轉化出問題,編譯器就需要背黑鍋
Add(a1, d1);
*/
// 此時有兩種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化
Add(a, (int)d);
return 0;
}
顯示實例化
顯式實例化:在函數名後的 <> 中指定模板參數的實際類型。
如果類型不匹配,編譯器會嘗試進行隱式類型轉換,如果無法轉換成功編譯器將會報錯。
Add<int>(a, b);
模板參數的匹配原則
1. 懶
一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化爲這個非模板函數。
編譯器也很「懶」,哈哈。編譯器:“這裏你已經有了,我就不給你再推斷再生成一個函數去覆蓋你的了。”
// 非模板函數
int Add(int left, int right)
{
return left + right;
}
// 通用加法函數 template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 與非模板函數匹配,編譯器不需要特化
Add<int>(1, 2); // 調用編譯器特化的Add版本
}
2. 勤快
對於非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數, 那麼將選擇模板。
Add(1, 2.0);
// 模板函數可以生成更加匹配的版本,編譯器根據實參生成更加匹配的Add函數
3. 小氣
模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換。
類模板
格式
class 類模板名
{
// 類內成員定義
};
⚠️ 注意:叫法上有一點需要注意一哈!
注意:
Date類:
類名 Date 類型 Date
模板類:
類名 Vector 類型 Vector<int>
🌰 栗子:Vector 動態順序表
template<class T> class Vector
{
public :
Vector(size_t capacity = 10)
: _pData(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
// 使用析構函數演示:在類中聲明,在類外定義。 ~Vector();
void PushBack(const T& data)
{
// _CheckCapacity();
_pData[_size++] = data;
}
void PopBack()
{
--_size;
}
size_t Size()
{
return _size;
}
T& operator[](size_t pos)
{
assert(pos < _size);
return _pData[pos];
}
private:
T* _pData;
size_t _size;
size_t _capacity;
};
⚠️ 注意:
-
重載操作符
[]
、實現 Size 接口是爲了實現類似於數組的方式訪問 private 成員。 -
類模板中函數放在類外進行定義時,需要加模板參數列表
template <class T> Vector<T>::~Vector() { if(_pData) { delete[] _pData; } }
實例化
類模板實例化與函數模板實例化不同,類模板實例化需要在類模板名字後跟 <>,然後將實例化的類型放在 <> 中即可,類模板名字不是真正的類,而實例化的結果纔是真正的類。
Vector<int> s1;
s1.PushBack(1);
s1.PushBack(2);
s1.PushBack(3);
Vector<double> s2;
s2.PushBack(1.0);
s2.PushBack(2.0);
s2.PushBack(3.0);
for(size_t i = 0; i < s1.Size(); ++i)
{
cout<<s1[i]<<" ";
}
cout<<endl;
for(size_t i = 0; i < s2.Size(); ++i)
{
cout<<s2[i]<<" ";
}
cout<<endl;
STL 簡介(入坑😆)
真的真的真的方便啊,吼吼吼。
網上有句話說:“不懂STL,不要說你會C++”,入坑學習咯。
STL(standard template libaray-標準模板庫):是 C++ 標準庫的重要組成部分,不僅是一個可複用的組件庫,而且
是一個包羅數據結構與算法的軟件框架。
STL 的版本
原始版本 Alexander Stepanov、Meng Lee 在惠普實驗室完成的原始版本,本着開源精神,他們聲明允許任何人任意 運用、拷貝、修改、傳播、商業使用這些代碼,無需付費。唯一的條件就是也需要向原始版本一樣做開源使 用。 HP 版本–所有STL實現版本的始祖。
P. J. 版本 由P. J. Plauger開發,繼承自HP版本,被Windows Visual C++採用,不能公開或修改,缺陷:可讀性比較低, 符號命名比較怪異。
RW版本 由Rouge Wage公司開發,繼承自HP版本,被 C+ + Builder 採用,不能公開或修改,可讀性一般。
✨ SGI版本 由Silicon Graphics Computer Systems,Inc公司開發,繼承自HP版 本。被 GCC(Linux) 採用,可移植性好, 可公開、修改甚至販賣,從命名風格和編程風格上看,閱讀性非常高。
STL 的六大組件
容器(Container)
算法(Algorithm)
迭代器(Iterator)
仿函數(Function object)
適配器(Adaptor)
空間配置器(allocator)
學習 STL 三個階段
能用,明理,能擴展。
STL 缺陷
-
STL 庫的更新太慢了。這個得嚴重吐槽,上一版靠譜是C++98,中間的C++03基本一些修訂。C++11出來已經相隔了13年,STL才進一步更新。
-
STL 現在都沒有支持線程安全。併發環境下需要我們自己加鎖。且鎖的粒度是比較大的。
-
STL 極度的追求效率,導致內部比較複雜。比如類型萃取,迭代器萃取。
-
STL 的使用會有代碼膨脹的問題,比如使用 vector/vector/vector 這樣會生成多份代碼,當然這是模板語法本身導致的。