文章目录

  • 为什么要有模板?
  • 函数模板
    • 函数模板的格式
    • 函数模板原理
    • 函数模板实例化
    • 模板参数匹配
  • 类模板
    • 定义格式
    • 类模板实例化
  • 模板特化
    • 全特化 & 偏特化
  • 模板的分离编译

为什么要有模板?

如果要交换两个值,如果两位数都是(int,int)则需要调用下面第一个函数void Swap(int &left, int &right),如果要交换的是(double,double)则要调用的是void Swap(double &left, double &right)。那么如果需要调用别的float,long等类型就需要在此实现函数重载,虽然也可以实现,但是相对代码的复用率比较低,代码维护性也比较低效。于是就有了泛函编程

void Swap(int &left, int &right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double &left, double &right)
{
	double temp = left;
	left = right;
	right = temp;
}

泛函编程:编写与类型没有关系的通用代码,是代码复用的一种手段。模板作为泛函编程的基础。对于模板有以下两种,函数模板和类模板。

函数模板

函数模板是一个函数家族,该函数模板与类型无关,在使用过程中被参数化,根据调用的实参类型产生函数的特定类型的版本。

函数模板的格式

template <typename T1, typename T2, ... , typename Tn>
返回值类型 函数名 (参数列表){}
这里将上述的交换函数写成函数模板为下面形式:

template<typename T> //定义模板参数T可使用typename或class
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

注释:typename可以换成class 但是 class不能换成struct。

函数模板原理

函数模板本质不是函数,是编译器用使用方式产生特定具体类型函数的模具。模板也就是说将我们该做的重复的事交给编译器去完成。
查看下图,仔细分析发现调用函数的模板实现交换,但是调用函数的地址不一样,可以说明低层调用的是不同的函数。

总结:
在编译器阶段,对于模板函数的使用,编译器是依据传入的实参类型来推演出对应类型的函数以供调用。(当我们使用int类型去调用函数模板时,编译器根据实参类型推演,将T确定为int类型,然后产生一份用来处理int类型的代码,就如上图call不同的地址的函数)。

函数模板实例化

函数模板实例化:是用不用类型的参数使用函数模板。
模板参数实例化:

  1. 隐式实例化
  2. 显示实例化。

隐式实例化:
(编译器根据实参推演模板参数的实际类型)
显示实例化:
(语法:函数名<指定的类型>(实参列表) 。)

模板参数匹配

  1. 一个非模板函数可以和函数模板同时存在,该函数模板仍然可以被实例化为这个非模板函数。
  2. 对于非模板函数和函数模板同时存在时,在同样情况下,一般情况下,优先选择调用非模板函数,而不用选用函数模板来去实例化一个函数,除非此函数模板可以实例化出来更好的匹配的函数。
    简述:有现成的匹配的函数,就调用现成的。实例化的更合适就选择匹配实例化出来的函数。
  3. 函数模板不允许自动类型的转换,这点普通函数可以实现自动类型转换。

类模板

定义格式

template <class T1,class T2,...,class Tn>
class 类模板名
{
	//类内的成员函数和成员变量
} 

类模板实现

namespace yumoz
{
	template <class T>
	class vector
	{
	public:
		vector()
			:_a(nullptr)
			, _size(0)
			, _capacity(0)
		{}

		~vector()
		{
			delete[] _a;
			_a = nullptr;
			_size = _capacity = 0;
		}

		void push_back(const T& x)
		{
			if (_size == _capacity)
			{
				int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				T* temp = new T[newcapacity];
				if (_a)
				{
					memcpy(temp, _a, sizeof(T)*_size);
					delete[] _a;
				}
				_a = temp;
				_capacity = newcapacity;
			}
			_a[_size] = x;
			++_size;
		}

		//类内实现
	/*	T& operator[](size_t pos)
		{
			assert(pos < _size);
			return _a[pos];
		}
		size_t size()
		{
			return _size;
		}*/

		//类外实现 部分1
		T& operator[](size_t pos);
		size_t size();

	private:
		T* _a;
		size_t _size;
		size_t _capacity;
	};

	//类外实现  部分2
	template<class T>//不可省略
	T& vector<T>::operator[](size_t pos)
	{
		assert(pos < _size);
		return _a[pos];
	}
	template<class T>//不可省略
	size_t vector<T>::size()
	{
		return _size;
	}
}

int main()
{
	yumoz::vector<int>v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	//v1.operator[](3)
	for (size_t i = 0; i < v1.size(); ++i)
	{
		v1[i] *= 3;//扩大三倍,返回值是引用
	}

	for (size_t i = 0; i < v1.size(); ++i)
	{
		cout << v1[i] << " ";
	}
	cout << endl;

	yumoz::vector<double>v2;
	v2.push_back(1.1);
	v2.push_back(2.2);
	for (size_t i = 0; i < v2.size(); ++i)
	{
		cout << v2[i] << " ";
	}
	cout << endl;

	return 0;
}

类模板实例化

类模板实例化需要在类模板名字后跟上<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果是真正的类。

vector<int>v1;
vector<double>v2;

模板特化

先看一下下面这个图:

看上图,明显发现明明都是“hello yumoz”,但是比较结果竟然不一样。问题出在哪了???此时就需要对模板进行特化处理。特化后结果如下:

全特化 & 偏特化

全特化就是对模板参数列表中所有的参数都确定化;偏特化(或半特化)有部分特化(将模板参数类列表中的一部分参数特化)还有参数更进一步限制(偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本)

模板的分离编译

分离编译?
一个项目由若干源文件共同实现,而每个源文件单独编译生成目标文件,最后将目标文件链接形成单一的可执行文件的过程称之为分离编译模式。
一图看懂模板的分类编译改变办法:

编译时只有声明没有函数定义;
只能确定函数名称,检查参数匹配,但没有函数地址,地址链接时再去找;
模板分离编译,定义的地方不实例化;实例化的地方没有定义,只有声明。

更多推荐

C++模板(函数模板、类模板、模板特化、分离编译)