转载自:https://blog.csdn.net/qq_61939403/article/details/125470395
仅作学习记录
一、 泛型编程
在C语言中,我们实现一个交换函数通常就只能交换特定的类型(int、double等),若需要其它类型也可以使用同样的函数进行交换,我们就需要实现不同名的其它函数,不能通用的使用一个函数。那我们如何实现一个通用的交换函数呢?
void Swap(int& p1, int& p2)
{
int tmp = p1;
p1 = p2;
p2 = tmp;
}
void Swap(double& p1, double& p2)
{
double tmp = p1;
p1 = p2;
p2 = tmp;
}
void Swap(char& p1, char& p2)
{
char tmp = p1;
p1 = p2;
p2 = tmp;
}
测试实例:
使用函数重载虽然可以实现通用的交换函数,但有一些缺点:
- 重载的函数仅仅是类型不同,代码的复用率比较低,写起来非常麻烦。只要有新类型的出现,就需要增加对应的函数。
- 代码的可维护性很低。
那么在c++中是否存在相对应的函数模板呢?
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
模板
是泛型编程的一种重要思想,STL
(Standard Template Library,标准模板库)是采用模板实现的一个实例。泛型编程以一种独立于任何特定类型的方式编写代码。
模板
是创建泛型类
或函数
的蓝图公式,可以使用模板来定义函数和类。
二、函数模板
函数模板
可以用来创建一个通用的函数,以支持不同的形参
,避免重载函数的函数体重复设计。函数模板代表了一个函数家族,该函数模板与类型无关
,在使用时被参数化,根据实参类型产生函数的特定类型版本
。
1. 函数模板声明格式
template<typename T1, typename T2, ... , typename Tn>
//返回值类型 函数名(参数列表){}
示例:
//定义模板参数T可以用typename,也可以用class
template<typename T>
void Swap(T& n1, T& n2)
{
T tmp = n1;
n1 = n2;
n2 = tmp;
}
template<class T>
T Add(const T& x1, const T& x2)
{
return x1 + x2;
}
❗️typename是用来定义模板参数的关键字,也可以使用class
。
在编译器编译时,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
2. 模板函数实例化
C++中模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程。
- 对于函数模板而言,模板实例化之后,会生成一个真正的函数。
- 而类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始化。
模板的实例化分为隐式实例化
和显式实例化
。
隐式实例化
函数模板隐式实例化 指的是
在发生函数调用的时候,如果没有发现相匹配的函数存在,编译器就会寻找同名的函数模板,如果可以成功进行参数类型推演,就对函数模板进行实例化。
显式实例化
在函数名后的
<>
中指定模板参数的实际类型。
❗️如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
在有多个函数和函数模板名字相同的情况下,一条函数调用语句到底应该被匹配成哪个函数或者哪个模板调用呢?
1️⃣ 一个非模板函数可以和一个同名的函数同时存在,而且该模板函数还可以被实例化为这个非模板函数。
//处理int的加法函数
int Add(int x, int y)
{
return x + y;
}
// 通用加法函数
template<class T>
T Add(T x, T y)
{
return x + y;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要实例化
Add<int>(1, 2); // 调用编译器实例化的Add版本
}
2️⃣ 对于非模板函数和同名函数模板,如果其它条件相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以实例出一个更好更匹配的函数,那么将选择模板。
//处理int的加法函数
int Add(int x, int y)
{
return x + y;
}
// 通用加法函数
template<class T>
T Add(T x, T y)
{
return x + y;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
3️⃣ 模板函数不能自动类型转换,但普通函数可以进行自动类型转换
三、类模板
c++除了支持函数模板,还支持
类模板
(Class
Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
1. 类模板定义的格式
类模板和函数模板都是以template
开头(也可以用class开头),后跟类型参数,类型参数不能为空,多个类型参数用逗号分隔开。
类模板的定义格式:
template<class T1,class T2,...,class Tn>
class xxx //类模板名
{
//类成员定义
};
一旦声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。即原来使用int、float、char等内置类型的地方,都可以用类型参数来替代。
//加命名空间是因为当你定义的类名和c++库里面的类名冲突时,将其隔离起来,否则编译器无法识别是库里面的还是自己写的
namespace hy
{
//vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
class vector
{
public:
vector()
:_a(nullptr)
, _size(0)
, _capacity(0)
{}
~vector()
{
delete[] _a;
_a = nullptr;
_size = _capacity = 0;
}
void push_back(const T& x)
{
if (_size == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
T* tmp = new T[newcapacity];
if (_a)
{
memcpy(tmp, _a, sizeof(T) * _size);
delete[] _a;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_size] = x;
++_size;
}
//在类中声明,在类外定义
T& operator[](size_t pos);
size_t size();
private:
T* _a;
size_t _size;
size_t _capacity;
};
//在类外面定义
//注意:类模板中函数在类外面进行定义时,需要加模板参数列表
template<class T>
T& vector<T>::operator[](size_t pos)
{
assert(pos < _size);
return _a[pos];
}
template<class T>
size_t vector<T>::size()
{
return _size;
}
}
2. 类模板实例化
类模板的实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟
<>
,然后将实例化的类型放在<>中即可
,类模板名字不是真正的类,实例化出的才是真正的类。
hy::vector <int> v1; //int
hy::vector<double> v2; //double