C++——初识模板

发布于:2024-04-23 ⋅ 阅读:(20) ⋅ 点赞:(0)

1. 概念

先看一个交换函数。

void swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

相信大家肯定可以信手拈来,但是这个函数只能用来交换两个int类型的数,如果这里有两个字符需要交换,就需要重新写一个交换函数。如下

void swap(char& x, char& y)
{
	char tmp = x;
	x = y;
	y = tmp;
}

可以看到这两个函数除了参数类型,其它部分完全一致。现在只是交换两种不同类型的数据,如果有很多不同类型的数据需要交换,那么需要依次实现,这太不可取了。由此,C++出现了模板。

模板分为函数模板类模板。先介绍函数模板。

概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型的版本。

函数模板格式为
template< typename T1,typename T2,typename T3…>
返回值类型 函数名(参数列表)

typename 是定义模板参数的关键字,可以用class替代,不能用struct
T1,T2,T3等是模板参数,名称随意,通常习惯用T(template首字母)。
参数列表的参数类型需要用模板参数替代。

比如上面swap函数的模板如下

template<typename T>
void Swap(T& x,T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

这样就可以完成任意类型的数据交换了。调用函数时,并不是调用这个模板,而是调用通过模板实例化出来的函数

template<typename T>
void Swap(T& x,T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 1, b = 2;
	char c = 'a', d = 'b';
	Swap(a, b);
	Swap(c, d);

	return 0;
}

调试这段代码,通过反汇编可以观察到调用的函数地址。
在这里插入图片描述
可以看到,两次调用的Swap函数的地址不同,说明调用的不是同一个函数。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于字符类型也是如此。所以函数的数量并没有减少,只是生成多份函数的工作交给了编译器。

2. 函数模板的实例化

实例化分为隐式实例化和显式实例化。

隐式实例化:让编译器通过实参类型去推导模板参数的实际类型。上面的Swap函数调用就是隐式实例化。

显式实例化:使用模板时,显式的传入模板参数的类型。

这里写一个add的函数模板进行讲解

template<class T>
T add(const T& x, const T& y)
{
	return x + y;
}

int main()
{
	int a = 1, b = 2;
	double c = 1.1, d = 2.2;
	cout << add(a, b) << endl;
	cout << add(c, d) << endl;

	return 0;
}

当写下这样的代码

add(a,d);//编译器会找不到匹配的函数模板,从而报错,注意使用模板时,编译器不会强制类型转换
解决方法为:
1.我们进行强制类型转换 -----> add( (double)a , b)或add ( a , ( int ) b );
2.显式实例化 ---->add< int >(a,b)

方法一我们自己去强制类型转换,这样就可以找到匹配的函数模板了。
方法二显式实例化,直接告诉编译器模板参数的类型。

运行结果如下。
在这里插入图片描述

3. 函数调用原则

  1. 对于普通函数和同名函数模板,如果其他条件都相同,在调动时会优先调用普通函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
  2. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

举个例子,还是add函数。

//专门计算int类型数据
int add(int x, int y)
{
	cout << "int add(int x, int y)" << endl;
	return x + y;
}

//模板
template<class T1,class T2>
auto add(const T1& x, const T2& y)
{
	cout << "auto add(const T1& x, const T2& y)" << endl;
	return x + y;
}

int main()
{
	int a = 1, b = 2;
	double c = 1.1, d = 2.2;
    add(a, b);
	add(c, d);
	add(a, d);
	return 0;
}

在这里插入图片描述

第一个add会调用专门计算int类型数据的add,更匹配;
第二个add会用模板生成一个更匹配的计算double数据的add函数
第三个用模板生成的更匹配。

总结:调用时,一定会选择最匹配的那个,没有最匹配的在选择其他的。

4. 类模板

之前实现的数据结构,比如栈,我们为了便于修改存储的数据类型,通常使用typedef来重定义数据类型,但是这样有一个缺陷,会导致我们定义的栈存储的都是一种数据,如果同时需要两个栈,一个存储int,一个存储自定义类型,那么typedef就不管用了。因此就有了类模板。

类模板和函数模板很相似,用法如下

template<class T1, class T2, …>
class 类模板名
{
// 类内成员定义
};

不同于函数模板的是在实例化时,类模板只能显式实例化
实例化方式为

类模板名 <类型>

这里用C++库里的栈做一个演示。
在这里插入图片描述

关于模板的初步介绍就到这了,现在只需要简单了解语法即可,后续会应用起来,就会有更深的理解。