C++之初识模板

泛型

了解C++的人都知道,在C++中有函数重载,可以通过函数重载来实现相同功能,不同类型的函数。
比如说下面的交换函数

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

void Swap(double& a, double& b)
{
	double tmp = a;
	a = b;
	b = tmp;
}

void Swap(char& a, char& b)
{
	char tmp = a;
	a = b;
	b = tmp;
}

我们可以通过将相同功能,不同数据类型的函数命名相同函数名,在使用时调用不同的函数来实现。

但是这也有两点不足:
1、重载的函数仅仅是类型不同,但是其他都是一样的,代码的复用率比较低。有新类型出现,就需要增加一个新的重载函数。
2、代码的可维护性比较低,如果这些重载的函数有一个出错,那么可能所有的重载都会有问题。

那么能不能告诉编译器有这样一个模板,让编译器根据不同的数据类型来生成相应的代码呢?

答案是可以的。C++中可以通过定义模板,来实现不同的代码。

模板

模板分为函数模板和类模板。

函数模板

函数模板相当于一个家族,它与类型无关,在使用的时候根据参数类型来生成特定的类型版本。也就是说,模板在定义的时候,其中的变量不写具体的变量名,而是通过一个泛型参数代替。

1、函数模板格式

格式如下:
template <typename T1, typename T2,…,typename Tn>
返回值类型 函数名 参数列表

解释一下:第一行是用来说明下面的函数定义是通过模板定义的,其中T1、T2、…Tn是用来代表形参的数据类型;第二行是函数的声明,其中参数列表这块用上面模板说明中的T1、T2、…、Tn表示。
这里的typename也可以用class代替,实际上,在日常的使用中class是用的要更多一点(切记:不能用struct代替class)。

比如,上面的交换函数可以通过定义模板来完成。

//模板实现

template <class T>

void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

在这里插入图片描述
从上图可以看出,用模板可以对不同类型的数据类型的变量进行交换。

2、模板原理

那么模板的底层到底是什么呢?它是怎样产生不同的代码?

函数模板其实可以看作是一个图纸,本身并不是真正的函数,是编译器通过使用方式来产生具体类型函数的模具。也就是说,模板就相当于将本来应该我们自己做的重复的事情让编译器来做。

它的实现原理是这样的:
在这里插入图片描述
在编译器的编译阶段,通过在某一个地方调用函数模板传入实参,编译器会根据传入的实参来推演生成对应类型的函数。比如:当double类型使用函数模板时,编译器通过对实参的推演,将T确定为double类型,然后生成一份专门处理double类型的代码。

3、函数模板实例化

用不同的参数使用函数模板称为模板的实例化,其分为隐式实例化和显式实例化。
(1) 隐式实例化
隐式实例化就是让编译器根据实参来推演模板参数的实际类型,在调用时不专门给出。

template <class T>
T Add(const T left, const T right)
{
	return left + right;
}

int main()
{
	int a = 10, b = 20;
	double c = 2.3, d = 9.8;
	Add(a, b);
	Add(c, d);
	return 0;
}

在上面的代码中写了一个加法函数的模板。在这里插入图片描述
然后在调用的时候给定实参,但是不具体给定类型,让编译器自己推演。比如上面的两次调用编译器会把Add函数分别生成int类型和double类型。

但是这里要注意,如果在函数模板中给定两种不同类型的实参,编译是无法通过的。
在这里插入图片描述
因为在编译的时候,当编译器看到函数模板被实例化之后,会去推演其实参的类型,通过推演实参a将T确定为int类型,通过实参d将T确定为double类型,但是我们给定的参数列表中只有一个T,那么编译器在这里无法确定将T确定为int类型还是double类型。

那么如果我们一定要这样做,可以采用以下两种方式:
1、对其中一个类型进行强制类型转换,将其转换成和另一个实参相同的类型。
2、使用显式实例化。

(2)显式实例化
显式实例化顾名思义就是在调用函数模板时给定参数类型。在这里插入图片描述
指定a和b为int类型,指定的类型用<类型>来表示。
显式实例化在指定类型之后,无论实参是什么类型,都会实例化为指定的类型。

4、模板参数的匹配原则

(1) 一个非模板函数可以和同名模板函数同时存在。如果调用的函数的类型和非模板函数匹配,那么编译器就不需要调用函数模板。如果没有匹配的非模板函数,编译器就会调用函数模板。


//针对int类型的加法函数
int Add(int a, int b)
{
	cout << "int" << endl;
	return a + b;
}


//模板类加法函数
template <class T>

T Add(T a, T b)
{
	cout << "template" << endl;
	return a + b;
}

int main()
{
	Add(1, 2);
	Add (1.2, 2.6);
	return 0;
}

在这里插入图片描述
可以看到Add(1, 2)有现成的非模板函数,直接调用。而Add(1.2, 2.6)属于double类型,编译器会去调用函数模板生成一份double类型的代码。
(2) 对于非模板函数和同名函数模板,如果其他条件都相同,那么在调用时会优先调用非模板函数;但是如果模板能够匹配出一个更好的函数,那么编译器会调用模板。

//针对int类型的加法函数
int Add(int a, int b)
{
	cout << "int" << endl;
	return a + b;
}

//模板加法函数
template <class T1, class T2>
T1 Add(T1 a, T2 b)
{
	return a + b;
}

对于Add(1, 2),有对应的非模板函数,那么编译器就不会去用模板生成。
而对于Add(1, 2.0),函数模板能够匹配的更好,那么编译器就会调用函数模板。

(3) 模板函数不允许自动转换类型,但是普通函数可以进行自动类型转换。

类模板

类模板的定义格式

类模板的定义格式如下所示:

template<class T1, class T2, …, class Tn>
class 类模板名
{
类中的成员定义
};

简单的写一个顺序表的类模板来解释一下:

//动态顺序表

template <class T>
class Vector
{
public:
	Vector(int capacity  = 10)
		: _a(new T[capacity])
		, _size(0)
		, _capacity(capacity)
	{}
	
	~Vector();

	void Pushback(const T& data);
	void Popback();

	int Size()
	{
		return _size;
	}
private:
	T* _a;
	int _size;
	int _capacity;
};

类模板中,所有需要用到类型T的地方用T表示,也就是生成能够存储不同类型的顺序表。

那么我们如果想在类模板的外面定义,必须加上参数列表,否则在进行定义的时候编译器不知道T是干什么的。
在这里插入图片描述

参数模板的实例化

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

//Vector是类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值