C++模板

  • 1.模板概念
  • 2.函数模板
    • 2.1函数模板概念
    • 2.2定义函数模板的一般形式
    • 2.3函数模板的原理
    • 2.4函数模板的实例化
  • 3.类模板
    • 3.1定义类模板的一般形式
    • 3.2类模板的实例化

1.模板概念

扯在前面:
  现在过中秋时一般都是从外边买月饼回来吃,但我在小的时候家里经常会自己动手来做中秋所需的月饼,我也在中秋节前见过家里的大人亲手做月饼的过程,不过写这篇博客自然不是想介绍做月饼的方式,而且很多的步骤我也没有太多的印象了。但其中有很重要的一步我却仍旧记忆犹新,那便是为了使月饼真正成形,都必须经过类似下面图片所示的一种月饼模具,模具也许只有一个,却可以因它产生成百上千甚至更多数量的月饼,而且只要月饼的馅不同,那便可以说是不同的月饼。不论做月饼的配料有什么不同,都可以经过这同样的模具,从而产生不同口味的月饼。

  上边扯了那么多,其实也并不完全是废话,只是想借之引出这篇博客所要谈的重头戏----模板

理解模板:在C++中,对于模板的理解,其实完全可以与上述的模具相对应,给这样的模具之中填充不同的配料(类型),来产生不同口味的月饼(生成具体类型的代码),使原本可能比较复杂的工作变得简单,这便完成了模板所想要完成的内容。

模板是泛型编程的基础。
泛型编程:是一种代码编写的方式,通过使用泛型编程,我们可以编写出独立于任何特定类型(与类型无关)的代码。

模板一般分为函数模板类模板

2.函数模板

2.1函数模板概念

所谓函数模板,实际上就是建立一个通用的函数,这个函数的函数类型与参数类型不具体指定,而是用一个虚拟的类型来代表,这个通用的函数就称为函数模板。这个函数模板,在使用时被参数化,根据具体类型的实参,才会产生对应类型的函数。

2.2定义函数模板的一般形式

template <typename T> 或 template <class T> 
通用函数定义

类型参数可以不止一个,根据需要确定参数,如

template <class T1, class T2>

template的含义为“模板”,尖括号中先写关键字typenameclass,表示类型名的意思,后面跟一个类型参数T,这个T只是一个虚拟的类型名,并未指定是哪一种具体的类型。

2.3函数模板的原理

函数模板并不是真正意义的函数,只是一个蓝图,对于不同类型的实参,编译器会去完成类型的推演,从而产生了具体的函数。

观察下列代码及其函数调用部分的汇编代码:

#include <iostream>
using namespace std;

template <class T>
T Add(T x, T y)
{
	T ret = x + y;
	return ret;
}

int main()
{
	int i1 = 10;
	int i2 = 20;
	Add(i1, i2);
	double d1 = 10.0;
	double d2 = 20.0;
	Add(d1, d2);
	char c1 = 'a';
	char c2 = 'b';
	Add(c1, c2);

	return 0;
}

从上述汇编代码即可看出,在编译阶段,编译器会根据函数模板以不同的实参类型,类推演生成具体类型的函数以供调用。 如,当int类型的实参使用函数模板时,编译器通过对实参的类型进行推演,从而确定T为int类型,然后产生处理int类型的函数。而double类型和char类型也同样如此。

2.4函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。函数模板的实例化分为隐式实例化显式实例化

隐式实例化:让编译器根据实参的类型自己推演模板函数参数的实际类型。
上边原理部分所演示的代码就是采用隐式实例化的方式。

但要注意,就上述代码而言,如果是隐式实例化,对于同一次调用,不同的实参的类型必须一致,否则编译器无法成功推演出T的类型纠结是哪种类型,便会报错。 如以下调用:

Add(i1, d2); //其他代码在上边

对于以上调用,编译器无法确定T的类型到底是int还是double,所以就会报错,因此一定要杜绝这样的写法。

显式实例化:在函数名后的<>中指定模板参数的实际类型。

Add<int>(i1, d2); //会将d2隐式类型转换为int

对于显式实例化,如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

3.类模板

理解了函数模板之后再来认识类模板就十分简单了,概念及原理都大同小异。

3.1定义类模板的一般形式

template <class T1, class T2, ..., class Tn> //一个或多个参数
class 类模板名
{
   // 类内成员定义
};

如定义一个顺序表的类模板:

template <class T>
class SeqList //这里的SeqList并不是具体的类,而是类模板
{
public:
	//构造函数
	SeqList(size_t capacity = 10)
		: _array(new T[capacity])
		, _capacity(capacity)
		, _size(0)
	{}

	//下方俩个成员函数仅为举例说明,未具体实现
	SeqList(const SeqList<T>& s); //拷贝构造函数
	SeqList<T>& operator=(const SeqList<T>& s); //赋值运算符的重载

	//析构函数
	~SeqList()
	{
		if (_array)
		{
			delete[] _array;
			_capacity = 0;
			_size = 0;
		}
	}

	/*未具体实现*/
	void push_back(const T& data);
	void pop_back();
	//...等等

	//下标运算符的重载
	T& operator[](size_t index)
	{
		assert(index < _size);
		return _array[index];
	}

	const T& operator[](size_t index)const
	{
		assert(index < _size);
		return _array[index];
	}

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

3.2类模板的实例化

需要注意的是,类模板的实例化与函数模板的实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中。类模板名字不是真正的类,而实例化的结果才是真正的类。如对上述顺序表的类模板进行实例化:

// SeqList是类名,SeqListr<int>才是类型
SeqList<int> s1;
SeqList<char> s2;

再次观察其汇编代码,进一步验证上述结果。

更多推荐

简单理解C++模板