C++——模板初阶与泛型编程
文章目录
- 💐专栏导读
- 💐文章导读
- 🌷引例
- 🌷函数模板
- 🌺函数模板的概念
- 🌺函数模板的格式
- 🌷函数模板的原理
- 🌷函数模板的实例化
- 🌺隐式实例化
- 🌺显式实例化
- 🌺一个疑问
- 🌷模板参数的匹配原则
- 🌷类模板
- 🌺类模板的定义格式
- 🌺类模板的实例化
💐专栏导读
🌸作者简介:花想云,在读本科生一枚,致力于 C/C++、Linux 学习。
🌸本文收录于 C++系列,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新!
🌸相关专栏推荐:C语言初阶系列 、C语言进阶系列 、数据结构与算法
💐文章导读
本章我们将学习模板
,与泛型编程
初次见面。C++相比于C语言有这么多丰富的接口与类型,都源于泛型编程。本章的内容为模板初阶知识
,为接下来的STL
学习打下坚实的基础。
🌷引例
如何实现一个通用的交换函数(swap
)呢?我们已经学过函数重载
的使用,函数重载在这种场景下貌似作用很大。
void Swap(int& a, int& b)
{
auto tmp = a;
a = b;
b = tmp;
}
void Swap(double& a, double& b)
{
auto tmp = a;
a = b;
b = tmp;
}
void Swap(char& a, char& b)
{
auto tmp = a;
a = b;
b = tmp;
}
//...
虽然函数重载
可以实现,但是有点吃力。最糟心的事儿是明明这些函数几乎都长一个样子,仅仅是参数类型不同而已,但是每多一个类型就得写一个对应的重载函数。
那么有什么办法能做到一劳永逸
呢?能否做到告诉编译器:我给你一个模板,你来帮我实现每个函数,只是换一下参数类型就OK了?
这就是我们今天要讲到的模板
——模板是泛型编程的基础
。模板不仅适用于函数
,也适用于类
。
🌷函数模板
🌺函数模板的概念
函数模板
代表了一个函数家族,该函数模板
与类型无关,在使用时被参数化
,根据实参类型产生函数的特定类型版本。
🌺函数模板的格式
template<typename T1, typename T2,......,typename TN>
返回值类型 函数名(参数列表)
{
//...函数体
}
🌼示例1
template<typename T>
void Swap(T& a,T& b)
{
auto tmp = a;
a = b;
b = tmp;
}
🍁注意事项
typename
后面的内容不一定是T
,可自由指定;typename
是用来定义模板参数关键字
,也可以使用class
(切记:不能使用struct
代替class
);
🌼示例2
template<class N>
void Swap(N& a, N& b)
{
auto tmp = a;
a = b;
b = tmp;
}
🌷函数模板的原理
函数模板
是一个描述的过程
、一幅设计图
、一个模板
,并不是真正的函数。编译器
会根据模板使用特定方式
产生具体类型函数。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
去做(想想印刷术的原理)。
- 在编译器编译阶段,编译器需要根据传入的
实参类型
来推演
生成对应类型的函数以供调用。
比如:当用double
类型使用函数模板时,编译器通过对实参类型的推演
,将T
确定为double
类型,然后产生一份专门处理double
类型的代码,对于字符类型也是如此。
🌷函数模板的实例化
用不同类型的参数使用函数模板
时,称为函数模板的实例化
。模板参数实例化分为:隐式实例化
和显式实例化
。
🌺隐式实例化
让编译器
根据实参推演
模板参数的实际类型。
🌼示例1
template<class T>
T Add(T a, T b)
{
return a + b;
}
void Test()
{
int a = 10;
int b = 100;
cout << Add(a, b) << endl;
}
🌺显式实例化
在函数名后的<>
中指定模板参数的实际类型
。
🌼错误示例
template<class T>
T Add(T a, T b)
{
return a + b;
}
void Test()
{
int a = 10;
double d = 3.14;
cout << Add(a, d) << endl;
}
🍁错误原因
因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a
将T
推演为int
,通过实参d
将T
推演为double
类型,但模板参数列表中只有一个T
,编译器无法确定此处到底该将T
确定为int
或者 double
类型而报错。
- 在模板中,编译器一般不会进行
类型转换
操作。
🌼正确的做法1
显式实例化
:就是在告诉编译器:不用你推演了,我已经指定这个类型了。
template<class T>
T Add(T a, T b)
{
return a + b;
}
void Test()
{
int a = 10;
double d = 3.14;
cout << Add<int>(a, d) << endl;
}
🌼正确的做法2
使用多个模板参数
。
template<class T,class Y>
Y Add(T a, Y b)
{
return a + b;
}
void Test()
{
int a = 10;
double d = 3.14;
cout << Add<int>(a, d) << endl;
}
🌺一个疑问
很多小伙伴初次理解模板都会有一个疑问——请问如下情况中,两次调用的swap
函数是一个函数吗?或者说,两次调用的函数在同一个地址空间
吗?
template<class N>
void Swap(N& a, N& b)
{
auto tmp = a;
a = b;
b = tmp;
}
void Test()
{
int a = 10;
int b = 100;
Swap(a,b);
char c1 = 'a';
char c2 = 'b';
Swap(c1, c2);
}
我们不妨先来看看汇编代码吧。如下图所示,很显然两次调用的函数并不是一个函数(一个类型为Swap< int >
,一个为Swap< char >
)。事实上,虽然我们看不见这两份函数的代码,但是它们实实在在的产生了。
🌷模板参数的匹配原则
- 一个
非模板函数
可以和一个同名的函数模板
同时存在,而且该函数模板还可以被实例化为这个非模板函数
。
🌼示例1
template<class T>
T Add(T a, T b)
{
return a + b;
}
int Add(int a, int b)
{
return a + b;
}
void Test()
{
int a = 10;
int b = 100;
cout << Add<int>(a, b) << endl;
}
- 对于
非模板函数
和同名函数模板
,如果其他条件都相同,在调动时会优先调用非模板函数
而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
。
🌼示例2
template<class T>
T Add(T a, T b)
{
cout << "模板" << endl;
return a + b;
}
int Add(int a, int b)
{
cout << "非模板" << endl;
return a + b;
}
void Test()
{
int a = 10;
int b = 100;
double d = 3.14;
cout << Add(a, b) << endl;//使用非模板函数
cout << Add(a, d) << endl;//使用模板
}
- 模板函数不允许
自动类型转换
,但普通函数可以进行自动类型转换。
🌷类模板
同样的,对于类
也有同样的套路。
🌺类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
🌼示例
template<class T>
class Vector
{
public:
//...
private:
T* _a;
size_t _size;
size_t _capacity;
};
🌺类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>
,然后将实例化的类型
放在<>
中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
🌼示例
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2
点击下方个人名片,可添加博主的个人QQ,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓