C++進階教程之模板3--一些知識的填充(霜之小刀)
QQ:2279557541
Email:[email protected]
博客地址:http://blog.csdn.net/lihn1987
優酷主頁:http://i.youku.com/shuangzhixiaodao
1. typename的另外一種用途
前面的幾期內容中,我們知道了typename的一個功能,就是用於聲明模板參數中的類型參數。比如
template<typename T>
void func(){
....
}
但是typename還有另外一種用途。用於定義後面跟着的是一個類型。
比如我們定義一個模板.
#include <vector>
template<typename T>
class TestClass
{
private:
T::iterator iter;
};
int main()
{
TestClass<std::vector<int>> a;
system("pause");
return 0;
}
我們想這個模板接受的模板參數都是標準庫中的容器,然後在容易中定義一個容器的迭代器(iterator)類型的變量。
但是我們發現程序報錯了。爲何報錯?
他把T::iterator看成了iterator是T的一個靜態成員變量。。。。
那麼怎樣讓編譯器知道T::iterator是一種類型呢?就是在前面加一個typename告知編譯器,後面跟着的是一個類型。
template<typename T>
class TestClass
{
private:
typename T::iterator iter;
};
int main()
{
TestClass<std::vector<int>> a;
system("pause");
return 0;
}
目前看到的這種情況是發生在成員變量的定義時,那要是在函數中定義依賴模板參數T的成員類型的變量會發生什麼情況呢?
template<typename T>
class TestClass
{
public:
void function()
{
T::iterator pos;
}
private:
typename T::iterator iter;
};
int main()
{
TestClass<std::vector<int>> a;
a.function();
system("pause");
return 0;
}
我所使用的vs2015的編譯器並沒有報錯。但是爲了防止其他的一些編譯器會報錯,我們可以養成一種習慣,就是任何依賴傳入模板參數的類型前都加一個typename。
2. 模板的模板參數
聽着就有點繞,這一節也會比較繞。
比如我們寫一個類,想用來封裝一個標準庫的容器。
我們期望的使用方式是這樣的
MyClass<int, std::vector>
就是分裝一個用std::vector<int>來存儲數據的類型。
#include <iostream>
#include <string>
#include <vector>
#include <list>
template<typename T>
class MyList:public std::list<T>
{
};
template<typename T>
class MyVector :public std::vector<T>
{
};
template <typename T1, template<typename TMP> class T2>
class TestClass
{
private:
T2<T1> m_list;
};
int main()
{
TestClass<int, MyVector> a;
system("pause");
return 0;
}
首先代碼中先寫了兩個模板類MyList與MyVector分別繼承自std::list與std::vector,這裏爲什麼要寫這兩個完全沒有用單單是繼承的模板類呢?
這個問題我們放到後面再講,這裏只需要把MyList當成std::list,把MyVector當成std::vector的功能即可。
然後看我們的TestClass,他的模板參數是這樣定義的
template <typename T1, template<typename TMP> class T2>
其中第一個參數容易理解,但是第二個參數怎麼理解呢?因爲我們傳入的不是一個固定的類型,而是一個模板類的名稱,所以這裏就要先聲明一個模板類
template<typename TMP> class T2
這就是我們定義的模板類,其中T2就是我們那個模板類的名稱,而TMP其實是完全沒有用到的,可以省略因此我們這裏可以吧這一行改爲
template <typename T1, template<typename> class T2>
這就是模板的模板參數。
本來呢說到這其實就已經完了,但是我們看這段代碼不覺得很彆扭麼?
爲什麼非要用MyList與MyVector???如果我們直接用標準庫中的容器會如何?
於是我們把
TestClass<int, MyVector> a;
改爲
TestClass<int, std::vector> a;
結果,編譯器報錯!!!!爲何????
於是看一下std::vector的定義爲
template<class _Ty,
class _Alloc = allocator<_Ty> >
class vector{
.....此處省略無數代碼
}
原來他的模板定義不止有一個參數
於是我們修改下我們的TestClass
然後源代碼變爲:
#include <iostream>
#include <string>
#include <vector>
#include <list>
template <typename T1, template<typename TMP, typename _Alloc = std::allocator<TMP>> class T2>
class TestClass
{
private:
T2<T1> m_list;
};
int main()
{
TestClass<int, std::vector> a;
system("pause");
return 0;
}
這樣貌似簡介多了,再也沒有中間的那個什麼MyList,MyVector這種鬼了。但是我們看看模板參數這一行
template <typename T1, template<typename TMP, typename _Alloc = std::allocator<TMP>> class T2>
這個裏面的TMP由於後面要用到,沒法省略了,但是那個_Alloc貌似沒啥用了啊,這個能省麼???
嘗試一下改爲
template <typename T1, template<typename TMP, typename = std::allocator<TMP>> class T2>
沒錯!果然是可以省略的!
至此,我們的需求終於實現了,這一節的內容也就講完了
3. 模板中字符串的推導
首先先看下面這個例子
template<typename T>
void func1(T a, T b)
{
std::cout << typeid(T).name() << std::endl;
}
template<typename T>
void func2(T& a, T& b)
{
std::cout << typeid(T).name() << std::endl;
}
int main()
{
func1("aaa", "bbb");
func1("aaa", "cccc");
func2("aaa", "bbb");
//func2("aaa", "cccc");編譯器報錯
system("pause");
return 0;
}
輸出爲:
char const *
char const *
char const [4]
這種輸出的原因是,當模板參數傳入的類型不是引用時,數組會自動被推導爲指針,而第二個模板由於函數模板由於傳入的是引用,則使用了原來的數組類型,所以備註釋報錯的那一行中的”aaa”,”cccc”得類型分別是char const [4]和 char const[5]的類型。
這裏多講一點,字符串常量,也即是我們代碼裏直接寫的”aaa”這種,其實編譯器會將其識別爲一個char const [4]的數組,而數組裏的四個元素分別爲字符’a’,’a’,’a’,’a’,’\0’
相關視頻請在我的優酷主頁中查找。