三、模版與泛型
Author: XFFer_
文章目錄
01 模版概念,函數模版定義、調用
1 概述
2 函數模版的定義
3 函數模版的使用
4 非類型模版參數
概述
vector實際上就是一個模版,vector纔是一個類
- 所謂泛型編程是以獨立於任何類型的方式編寫代碼。使用泛型編程時,我們需要提供具體程序實例所操作的類習慣或者值
- 模版是泛型編程的基礎,模版是創建類或者函數的藍圖或者公式。我們給這些藍圖或者公式提供足夠的信息,讓這些藍圖或者公式真正的轉變成具體的類或者函數,發生在編譯時
- 模版支持將類型作爲參數的程序設計方式,從而實現了對泛型程序設計的直接支持
函數模版的定義
overloaded function(重載函數)實際上就是兩個同名函數,但是形參列表有明顯不同。
template<typename T>
T funcadd(T a, T b)
{
T add_result = a + b;
return add_result;
}
- 模版定義是用
template
關鍵開頭的,後邊跟<>
,裏面叫模版參數列表(模版實參) <>
裏必須有一個模版參數,模版參數前typename/class
,模版參數列表裏表示函數定義中用到的“類型”或“值”- 多個模版參數時,需要用多個
template <typename A, typename B>
函數模版的使用
和正常調用函數相似,系統會實例化出推斷出的形參類型對應的函數
模版函數可以是內聯inline
(加在template的下一行)的,模版定義不會生成代碼,只會在調用函數模版時,纔會實例化出一個特定版本的函數。
非類型模版參數
用傳統的類型名來指定非類型參數,當模版被實例化時,這些非類型模版參數的值必須是常量表達式,在編譯時實例化。
template<int a, int b>
int func()
{
int result = a + b;
return result;
}
int answer = func<12, 13>(); //顯式的指定模版參數--在尖括號中提供額外的信息
02 類模版概念、類模版定義、使用
1 概述
2 類模版定義
3 類模版的成員函數
4 模版類名字的使用
5 非類型模版參數
概述
用類模版實例化一個特定的類,需要在模版名後用
<>
提供額外的信息。實例化類模版需要包含全部信息,包括類成員函數的定義
類模版定義
template <typename 形參名1, typename 形參名2, ..., typename 形參名n>
class 類名
{
...
};
template<typename T>
class myvector
{
public:
typedef T* myiterator;
public:
myiterator mybegin(); //返回值是myiterator,其實就是指向容器內部對象的指針
myiterator myend();
void func();
myvector& operator=(const myvector&);
};
類模版的成員函數
- 類模版的成員函數可以寫在類定義中,這種成員函數會被隱式聲明爲inline函數
- 類模版一旦被實例化之後,每個實例都會有自己版本的成員函數
- 類模版的成員函數具有和這個類模版相同的模版參數
- 定義在類模版之外的成員函數必須以
template<模版參數表>
開始,類名後要用<>
把模版參數列表裏的參數名列出
template<typename T>
void myvector<T>::func
{
...
}
template<typename T>
myvector<T>& myvector<T>::operator=(const myvector&)
{
return *this;
}
浮點型/類類型 不能做類模版的非類型模版參數
03 用typename場合、默認模版參數、趣味寫法分析
1 typename的使用場合
2 函數指針做其他函數的參數
3 函數模版趣味用法舉例
4 默認模版參數
typename的使用場合
類型成員
typedef T* iterator;
vector<int>::iterator iter;
這個typedef
出的就是類型成員。
#include <vector>
vector<int> contain;
for (vector<int>::iterator iter = contain.begin(); iter != contain.end(); iter++) {}
這裏的contain是一個類對象;iterator是實例化vector得到的類中的類型成員;iter是一個指向int對象的指針,因爲typedef *T iterator
,contain是一個儲存整形數據的類對象,可以調用類成員函數begin()
、end()
,返回值也是指向整形變量的指針。
template<typename T>
typename myvector<T>::myiterator myvector<T>::begin() {}
模版實例出的類後跟作用域標識符::
默認被系統處理成成員,而myiterator在這裏作爲類型成員(返回值類型),必須使用typename聲明。
函數模版趣味用法舉例
template<typename T, typename F>
void func(const T& i, const t& j, F funcpoint)
{
funcpoint(i, j);
}
int mcfunc(int i, int j)
{
return i+j;
}
func(2, 4, mcfunc); //T->int,F->int (*) (int, int)的函數指針
04 成員函數模版,顯式實例化、聲明
1 普通類的成員函數模版
2 類模版的成員函數模版
3 模版顯式實例化,模版聲明
普通類的成員函數模版
成員函數都可以是函數模版,成爲“成員函數模版”,虛函數。
類模版的成員函數模版
template <typename T>
class A {
public:
template <typename P>
void func(P& p);
};
template <typename T>
template <typename P>
void A<T>::func(P& p) {}
類模版的成員函數(普通成員函數/模版成員函數)只有爲程序所用纔會實例化。
模版顯式實例化、模版聲明
Q:爲了防止在多個.cpp文件中都實例化相同的類模版
A:C++11提出“顯式實例化”
//“顯式實例化”手段中的“實例化定義”
template A<float>;
//“顯式實例化”手段中的“實例化聲明”
extern template A<float>; //不會再在本文件中生成這個實例化版本
//目的是告訴編譯器,在其他文件中已經有了該模版的實例化版本
05 using定義模版別名,顯式指定模版參數
1 using定義模版別名
2 顯式指定模版參數
using定義模版別名(類型模版)
typedef std::map<std::string, int> map_s_i;
但是typedef
定義出的是固定的格式。
C++98中:
template <typedef M>
struct map_s
{
typedef map<string, M> map_c;
};
//很類似iterator定義了一個類型成員
map_s<int>::map_c mapl;
C++11中:
template <typename T>
using str_map = map<string, T>;
06 模版全特化、偏特化(局部特化)
1 類模版特化
類模版全特化
類模版偏特化2 函數模版特化
函數模版全特化
函數模版偏特化
類模版特化
類模版全特化
特化:對特殊的類型(類型模版參數)進行特殊的處理。
template <typename T, typename F>
struct test {
void functest() {
cout << "調用了泛型版本" << endl; }
};
//特化類模版
template <>
struct test<int, double> {
void functest() {
cout << "調用了特化版本" << endl; }
};
//特化成員函數
template <>
void test<double, float>::functest() {
cout << "調用了特化版本的成員函數" << endl;
}
類模版偏特化
//參數數量進行偏特化
template <typename T, typename F, typename L>
struct test {
... };
template <typename F>
struct test<double, F, int> {
... };
//模版參數範圍上的特化版本
template <typename T>
struct test {
... };
template <typename T>
struct test<const T> {
...}; //const特化版本
函數模版全特化
template <typename T, typename P>
void func(T& tmprv, P& tmprc)
{
... }
template <>
void func(double& tmprv, int& tmprc)
{
... }
函數模版沒有偏特化!
必須都有泛型模版,纔可以定義特化模版。模版定義、實現都放在一個.h
文件中。
07 可變參模版
1 可變參函數模版
簡單範例
參數包的展開2 可變參類模版
通過遞歸繼承方式展開參數包
可變參函數模版
...
的位置很關鍵!
template <typename... T>
void myfunc(T... argc)
{
cout << sizeof...(T) << endl; //返回T...的類型數
cout << sizeof...(argc) << endl;
}
T中存放的是任意個不同類型,稱作可變參類型; argc中存放着任意個形參,稱作可變形參。
參數包的展開
一個參數typename T
和一包參數typename... F
,這種可變參函數模版更容易展開!
//參數包用迭代方式展開
void func() {} //終止迭代
template <typename T, typename... F>
void func(const T& src, const F&... stv)
{
cout << src << endl;
func(stv...);
}
可變參類模版
通過遞歸繼承的方式展開參數包
template <typename... Args> class myclass { }; //爲了保證可變參模版定義成功
template<> class myclass<> { //特化空模版
public:
myclass() {
cout << "myclass<>::myclass()執行" << endl;
}
};
template <typename First, typename... Others>
class myclass<First, Others...> : private myclass<Others...> //偏特化
{
public:
myclass() : m_i(0)
{
cout << "myclass::myclass執行了" << ' ' << "this: " << this << endl;
}
First m_i;
};
int main
{
myclass<int, float, double> cls();
}
(Debug結果:感覺挺難的)
- 首先聲明構造函數的執行是從父類->子類的
- 實例化一個類對象,類
cls()
括號內的參數是根據構造函數中的參數確定的 - (每次取可變參)類對象
<int, float, double>
繼承於<float, double>
;<float, double>
繼承於<double>
;<double>
繼承於< >
。模版參數同樣也是特化版本 - 就這樣
< >
是基類對象,控制檯輸出*“myclass<>::myclass()執行”;緊接着<double>
-><float, double>
-><int, float, double>
都會輸出"myclass:myclass執行了"*
08 可變參模版續、模版模版參數
1 可變參類模版
通過遞歸組合方式展開參數包
通過tuple和遞歸調用展開參數包
總結2 模版模版參數
可變參類模版
通過遞歸組合方式展開參數包
組合關係(複合關係):類A中包含B對象(即在類A定義內部定義一個類B的對象)。
template <typename... Args> class myclass {}; //保證遞歸組合方式能夠成功複合
template <>
class myclass<>
{
public:
myclass() {
cout << "myclass<>::myclass()執行" << endl; }
};
template <typename First, typename... Others>
class myclass<First, Others...>
{
public:
myclass(First prao, Others... pano) : m_i(0), m_t(pano)
First m_i;
myclass<Others...> m_t;
};
通過tuple和遞歸調用展開參數包
實現思路:計數器從0開始,每處理一個參數,計數器+1,一直到把所有參數處理完;最後搞一個模版偏特化,作爲遞歸調用結束。
#include <tuple>
//mycount用於統計,從0開始,mymaxcount表示參數數量
template <int mycount, int maxmycount, typename... T>
class myclass {
public:
static void mysfunc(const tuple<T...>& t)
{
cout << "value= " << get<mycount>(t) << endl;
//get是tuple元組的用法get<整數(表示位置從0開始)>(tuple對象)
myclass<mycount + 1, maxmycount, T...>::mysfunc(t);
//遞歸調用,計數器+1,取下一個tuple位置中的值
}
};
//特化一個結束版本
template<int maxmycount, typename... T>
class myclass<maxmycount, maxmycount, T...> {
public:
static void mysfunc(const tuple<T...>& t)
{
}
};
template <typename... T>
void myfunc(const tuple<T...>& t)
{
myclass<0, sizeof...(T), T...>::mysfunc(t);
}
int main()
{
tuple<float, int ,double> mytuple(3.2f, 3, 5);
myfunc(mytuple);
}
(也挺難的能看懂就好)
- get< >( )是元組的一個方法,目的是取各索引下的值,0 ~ len - 1,< >中存索引,( )中存tuple對象名
- 這個例子中的可變模版參數T…,實際作用是用來代表tuple元組中保存的多種類型
- myclass是一個計數器,mycount是get的索引位,maxmycount是get方法的終止位,在函數myfunc中給mycount初始化爲0,maxmycount初始化爲傳入元組內的元素個數
sizeof...(T)
- 計數器內每get到tuple的一個元素,就迭代調用mycount索引值+1的計數器版本,maxmycount不變
- 直到mycount==maxmycount時,也就是特化的版本,終止執行,因爲索引值執行到len - 1之後就沒有內容了
模版模版參數
用於在模版中還要使用類模版的情況下!
這是一個模版參數,前面都叫類型模版參數,這個模版參數本身,又是一個模版。
第種寫法(第二種直接放裏面了)
template <
typename T, //類型模版參數
template<class> class Container //模版 模版參數
//template<typename W> typename Container
>
class myclass
{
public:
T t_i;
Container<T> myc;
myclass()
{
for (i = 0; i <10; i++)
{
myc.push_back(i);
}
}
};
template<typename T>
using myvec = vector<T, allocator<T>>; //需要確定一個分配器,系統沒能自己確定
myclass<int, myvec> mvectobj;
//這裏是因爲vector有第二個參數allocator分配器,所以需要自己用using手動定義一個
(理解)
- 模板模板參數
template<class/typename> class/typename 名字
- 這裏的class不代表類定義,而是和typename可互換
- 下面使用
Container<T>
時,第一個typename系統確定爲T,第二個確定爲class(類)
using myvec = vector<T, allocator<T>>
是固定寫法