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++库里的栈做一个演示。

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

相关推荐
Swift社区2 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht2 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht2 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20242 小时前
Swift 数组
开发语言
南东山人3 小时前
一文说清:C和C++混合编程
c语言·c++
stm 学习ing3 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc4 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe5 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin5 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python