C++入門到精通 ——第三章 模板與泛型

三、模版與泛型

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>>是固定寫法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章