C++——初识模板

模板

  • [1. 概念](#1. 概念)
  • [2. 函数模板的实例化](#2. 函数模板的实例化)
  • [3. 函数调用原则](#3. 函数调用原则)
  • [4. 类模板](#4. 类模板)

1. 概念

先看一个交换函数。

cpp 复制代码
void swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

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

cpp 复制代码
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函数的模板如下

cpp 复制代码
template<typename T>
void Swap(T& x,T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

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

cpp 复制代码
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的函数模板进行讲解

cpp 复制代码
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函数。

cpp 复制代码
//专门计算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++库里的栈做一个演示。

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

相关推荐
小萌新上大分9 分钟前
SpringCloudGateWay
java·开发语言·后端·springcloud·springgateway·cloudalibaba·gateway网关
XYY3691 小时前
前缀和 一维差分和二维差分 差分&差分矩阵
数据结构·c++·算法·前缀和·差分
PacosonSWJTU1 小时前
python基础-13-处理excel电子表格
开发语言·python·excel
froginwe111 小时前
Perl 条件语句
开发语言
longlong int1 小时前
【每日算法】Day 16-1:跳表(Skip List)——Redis有序集合的核心实现原理(C++手写实现)
数据库·c++·redis·算法·缓存
24白菜头1 小时前
C和C++(list)的链表初步
c语言·数据结构·c++·笔记·算法·链表
啥都鼓捣的小yao2 小时前
利用C++编写操作OpenCV常用操作
开发语言·c++·opencv
灼华十一2 小时前
Golang系列 - 内存对齐
开发语言·后端·golang
程序媛学姐2 小时前
SpringRabbitMQ消息模型:交换机类型与绑定关系
java·开发语言·spring
努力努力再努力wz2 小时前
【c++深入系列】:类与对象详解(中)
java·c语言·开发语言·c++·redis