⭐本篇重点:泛型编程,函数模板,类模板
⭐本篇代码:c++学习/07.函数模板 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
目录
一. 泛型编程
如何实现一个交换函数,这个函数可以交换任意两个相同类型的变量?我们可以使用函数重载来完成这个功能。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
void swap(int& a, int& b)
{
int t = a;
a = b;
b = t;
}
void swap(double& a, double& b)
{
double t = a;
a = b;
b = t;
}
void swap(char& a, char& b)
{
char t = a;
a = b;
b = t;
}
int main()
{
int a = 1, b = 2;
double c = 3.14, d = 1.44;
char c1 = 'a', c2 = 'b';
printf("交换前\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);
swap(a, b);
swap(c, d);
swap(c1, c2);
printf("\n\n交换后\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);
return 0;
}
运行结果如下:
函数重载虽然可以完成这个功能,但是有两个缺点:
1 如果有一个重载的函数出错,可能导致全部函数出问题
2 代码的复用性较低,每增加一个新的类型,都要新增一个重载函数
其实,在C++中也存在一种模具,根据填充的材料,获取不同的铸件。就是泛型编程
泛型编程:编写与类型无关的代码,而模板是泛型编程的基础
二. 函数模板
2.1 函数模板的格式
template<typename T1, typename T2, ... typename T3>
返回值类型 函数名(参数列表)
{函数体}
注意:typename是用来定义模板参数的关键字,可以使用class替代(不可使用struct替代)
2.2 函数模板的简单使用
将swap修改为函数模板后的代码如下:
//使用函数模板
template<class T>
void myswap(T& a, T& b)
{
T t = a;
a = b;
b = t;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
//使用函数模板
template<class T>
void myswap(T& a, T& b)
{
T t = a;
a = b;
b = t;
}
int main()
{
int a = 1, b = 2;
double c = 3.14, d = 1.44;
char c1 = 'a', c2 = 'b';
printf("交换前\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);
myswap(a, b);
myswap(c, d);
myswap(c1, c2);
printf("\n\n交换后\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);
return 0;
}
测试结果如下:
2.3 函数模板的原理
函数模板在编译器编译阶段,编译器根据传入的参数去推导生成对应参数类型的函数用来调用。比如我们传入int型参数,编译器通过推演,将T确定为int型,然后生成一份专门为int型调用的代码。
下图可以帮助我们理解函数模板
2.4 函数模板的实例化
用不同的参数去使用函数模板的时候,称为函数模板的实例化。函数模板的实例化有显示实例化和隐式实例化。
隐式实例化:让编译器根据实参去自动推演参数类型
显示实例化: 在调用函数时候,在函数名后面加上<>,在<>中指定参数的类型
上面的交换函数swao就是隐士实例化的例子
显示实例化:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
//使用函数模板
template<class T>
T add(const T& a, const T& b)
{
return a + b;
}
int main()
{
int a = 1, b = 2;
double c = 3.14, d = 1.44;
cout << "a + b = " << add<int>(a, b) << endl;
cout << "c + d = " << add<double>(c, d) << endl;
int ret = add<int>(a, c); //如果参数类型和指定类型不一样,编译器会转化
cout << "ret:" << ret << endl;
return 0;
}
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
2.5 模板参数的匹配原则
一个非模板函数可以和一个同名模板函数同时存在,并且这个模板函数可以被实例化为这个非模板函数
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
//非模板函数
int add(const int& a, const int& b)
{
return a + b;
}
//使用函数模板
template<class T>
T add(const T& a, const T& b)
{
return a + b;
}
int main()
{
int a = 1, b = 2;
double c = 3.14, d = 1.44;
int ret1 = add(a, b); //调用非模板函数
int ret2 = add<int>(a, b); //调用显示实例化的add函数
double ret3 = add(c, d); //调用double类型的add函数
cout << ret1 << endl << ret2 << endl << ret3;
return 0;
}
运行结果如下:
对于非模板函数和同名模板函数 ,在调用条件相同的情况下,编译器会优先去调用非模板函数。如果模板函数可以产生一个更匹配的函数,则会调用模板函数
模板函数不允许自动类型转化,普通函数支持自动类型转化
三. 类模板
与函数模板一样,我们也可以使用类模板。
3.1 类模板的格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
3.2 类模板的简单使用
这里使用一个模板数组(支持传入的类型)来举例:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
template<class T>
class arr
{
public:
arr()
:_a(nullptr)
, _size(0)
, _capacity(0)
{};
~arr()
{
//销毁开辟的空间
delete[] _a;
_size = 0;
_capacity = 0;
}
//插入函数
void PushBack(const T& x)
{
if (_size == _capacity)
{
size_t newcapacity = (_capacity == 0) ? 3 : _capacity * 2;
T* tmp = new T[newcapacity];
if (_a != nullptr)
{
memcpy(tmp, _a, sizeof(T) * _size);
delete[] _a; //注意要销毁旧的空间,防止内存泄漏!
_a = nullptr;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_size] = x;
_size++;
}
void print()
{
for (int i = 0; i < _size; i++)
{
cout << _a[i] << " ";
}
cout << endl;
}
private:
T* _a;
size_t _size;
size_t _capacity;
};
int main()
{
//定义一个int类型的数组
arr<int> nums;
for (int i = 0; i < 10; i++)
{
nums.PushBack(i);
}
nums.print();
//定义一个char类型的数组
arr<char> str;
for (int i = 0; i < 10; i++)
{
str.PushBack('a' + i);
}
str.print();
return 0;
}
运行结果如下:
3.3 类模板的实例化
类模板的实例化与函数模板的实例化不同,类模板的实例化需要在模板名后面加上<>,并且在<>指明这个类型。
就像上面的代码:arr是模板名字,arr<int>和arr<char>才是类名字