目录
1.泛型编程
2.函数模板
2.1 概念
2.2 函数模板格式
2.3 函数模板原理
2.4 函数模板的实例化
2.5 函数模板的匹配原则
3.类模板
4.非类型模板参数
5.模板的特化
5.1 概念
5.2 函数模板特化
5.3 类模板特化
6.模板分离编译
6.1 分离编译模式
6.2 模板的分离编译
6.3 解决方法
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
int main()
{
int i = i, j = 2;
double x = 1.1, y = 2.2;
Swap(i, j);
Swap(x, y);
return 0;
}
相比C语言,C++的引用、函数重载等等已经为代码的书写提供了很大遍历,但仍然存在问题:
如上文代码,函数重载面对新类型的对象需要不断创建同名且逻辑类似的函数,这是非常重复且低效的。
故而C++引入泛型编程:
编写与类型无关的通用代码,是代码复用的一种手段。而模板是泛型编程的基础。
模板又分为函数模板和类模板;
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
template
返回值类型 函数名(参数列表){}
template
//template
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
int main()
{
int i = 1, j = 2;
double x = 1.1, y = 2.2;
char m = 'A', n = 'B';
Swap(i, j);
Swap(x, y);
Swap(m, n);
cout << "i=" << i << ",j=" << j << endl;
cout << "x=" << x << ",y=" << y << endl;
cout << "m=" << m << ",n=" << n<< endl;
return 0;
}
说明: ① 模板参数类似于函数参数,只是模板参数是模板类型,函数参数是参数对象;
② typename后的T自由定义,通常用单词首字母大写;
③ T表示一个模板类型(虚拟类型);
④ 此处template
以上文代码为例,基于先前所学知识,我们知道在函数体内创建的临时变量tmp基于T的不同,字节也不同,而函数调用时建立栈帧也会因此不同,因此调用的三个函数必然是不同的函数;
其实函数模板就是把程序员要做的事交给了编译器:
在编译阶段:编译器会根据传递的实参类型来推演生成对应类型的函数以供调用:
用不同类型的参数使用函数模板时,称为函数模板的实例化。
模板实例化分为显式实例化和隐式实例化。
2.4.1 隐式实例化:编译器根据实参类型推演模板参数的实际类型
template
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
Add(1, 2);
Add(1.1,2);
return 0;
}
//实参类型不同导致的实例化链接错误
如果实参为多种类型,仍然想创建函数模板,可以进行如下操作:
(1)强转
template
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
Add(1, 2);
//强转
Add((int)1.1, 2); //输出结果为3
Add(1.1,(double)2); //输出结果为3.1
return 0;
}
//此种方法可能造成精度丢失,可能得不到预期输出
(2)多参数:
template
T1 Add(const T1& left, const T2& right)
{
return left + right;
}
int main()
{
cout << Add(1.2, 2) << endl;
return 0;
}
//返回值较麻烦,不建议使用
(3)进行显式实例化:
2.4.2 显式实例化:
template
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
cout<(1.1, 2) << endl; //输出结果为3
cout << Add(1.1, 2) << endl; //输出结果为3.1
return 0;
}
一个非模板函数和一个同名的模板函数同时存在,该函数模板还可以被实例化为这个非模板函数:
//函数模板
template
T Add(const T& left, const T& right)
{
return left + right;
}
//专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
int main()
{
cout<
并且,编译器匹配参数类型时会先调用参数类型匹配的函数,
当不存在参数类型匹配的函数时,才会调用函数模板进行实例化;
以栈为例:
template
class Stack
{
public:
Stack(size_t capacity = 0)
:_a(nullptr)
,_capacity(0)
,_top(0)
{
if (capacity > 0)
{
_a = new T[capacity];
_capacity = capacity;
_top = 0;
}
}
~Stack()
{
delete[] _a;
_a = nullptr;
_capacity = _top = 0;
}
void Push(const T& x);
void Pop()
{
assert(_top > 0);
--_top;
}
bool Empty()
{
return _top == 0;
}
const T& Top() //防止栈中数据被修改
{
assert(_top > 0);
return _a[_top-1];
}
private:
T* _a;
size_t _top;
size_t _capacity;
};
template
void Stack::Push(const T& x)
{
if (_top == _capacity)
{
//1.开新空间
//2.拷贝数据
//3.释放旧空间
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
T* tmp = new T[newcapacity];
if (_a)
{
memcpy(tmp, _a, sizeof(T) * _top);
delete[] _a;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top] = x;
++_top;
}
//模板不支持分离编译,即不支持声明放在.h文件中,定义放在.cpp文件中;
//但在同一个文件中,模板支持声明与定义分离;
int main()
{
try
{
类模板都是显式实例化
//Stack st1; //char
//Stack st2; //int
虽然使用了一个类模板,但是是不同的类型
Stackst1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
st1.Push(5);
while (!st1.Empty())
{
cout << st1.Top() << " ";
st1.Pop();
}
cout << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
模板参数分为类型形参和非类型形参:
类型形参:出现在模板参数列表中,跟在class或typename之类的参数类型名称;
非类型形参:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用;
以静态数组类为例:
template //可提供缺省值
//template
//N是非类型模板参数,是一个常量
class array
{
private:
T _a[N];
};
int main()
{
array a0;
array a2; //100
array a2; //1000
return 0;
}
PS:(1)浮点数、类对象以及字符串是不允许作为非类型模板参数的,一般情况下费类型模板参数都是整型;
(2)非类型的模板参数必须在编译期就能确认结果;
对于array
对于a2来说,C++对静态数组的检查是数组长度后的某些特定位置进行抽查,而a2元素的访问是指针解引用,一般情况下,类似于a2[10]这样的越界读是不会被抽查到的,a2[10]=0这样的越界可能会被抽查出来;
而对于array
模板的特化就是在原有模板的基础上,针对某些类型进行特殊化处理;
struct Date
{
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
int _year;
int _month;
int _day;
bool operator>(const Date& d)const
{
if ((_year > d._year)
|| (_year == d._year && _month > d._month)
|| (_year == d._year && _month == d._month && _day > d._day))
{
return true;
}
else
return false;
}
};
template
bool Greater(T left, T right)
{
return left > right;
}
int main()
{
cout << Greater(1, 2) << endl;
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Greater(d1, d2) << endl;
Date* p1 = &d1;
Date* p2 = &d2;
cout << Greater(p1, p2) << endl;
}
对上文代码,当实参为指针时,会进行实参地址大小的比较,并无意义,我们希望当传递欲比较对象的地址时,仍然可以进行对象本身大小的比较,故而需要对Greater函数进行特例化,当形参为对象指针时,也进行对象的大小比较而非对象指针大小的比较;
基于上文代码,进行Greater函数的特化:
struct Date
{
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
int _year;
int _month;
int _day;
bool operator>(const Date& d)const
{
if ((_year > d._year)
|| (_year == d._year && _month > d._month)
|| (_year == d._year && _month == d._month && _day > d._day))
{
return true;
}
else
return false;
}
};
template
bool Greater(T left, T right)
{
return left > right;
}
template<>
bool Greater(Date* left, Date* right)
{
return *left > *right;
}
int main()
{
cout << Greater(1, 2) << endl;
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Greater(d1, d2) << endl;
Date* p1 = &d1;
Date* p2 = &d2;
cout << Greater(p1, p2) << endl;
}
增加特化函数后,当实参为对象指针时,自动调用参数为指针的函数,比较对象本身的大小;
基于以上代码:
#include
#include
#include
struct Date
{
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
int _year;
int _month;
int _day;
bool operator<(const Date& d)const
{
if ((_year < d._year)
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day))
{
return true;
}
else
return false;
}
bool operator>(const Date& d)const
{
if ((_year > d._year)
|| (_year == d._year && _month > d._month)
|| (_year == d._year && _month == d._month && _day> d._day))
{
return true;
}
else
return false;
}
};
namespace bit
{
template
struct less
{
bool operator()( const T& x1, const T& x2)const
{
return x1 < x2;
}
};
//特化
template<>
struct less
{
bool operator()( Date* x1, Date* x2)const
{
return *x1 < *x2;
}
};
}
int main()
{
bit::lesslessFunc1;
cout << lessFunc1(d1, d2) << endl;
bit::lesslessFunc2;
cout << lessFunc2(p1, p2) << endl;
std::priority_queue, bit::less>dq1;
dq1.push(Date(2022, 9, 27));
dq1.push(Date(2022, 9, 25));
dq1.push(Date(2022, 9, 28));
dq1.push(Date(2022, 9, 29));
while (!dq1.empty())
{
Date top = dq1.top();
cout << top._year << "/" << top._month << "/" << top._day << endl;
dq1.pop();
}
cout << endl;
std::priority_queue, bit::less>dq2;
dq2.push(new Date(2022, 9, 27));
dq2.push(new Date(2022, 9, 25));
dq2.push(new Date(2022, 9, 28));
dq2.push(new Date(2022, 9, 29));
while (!dq2.empty())
{
Date* top = dq2.top();
cout << top->_year << "/" << top->_month << "/" << top->_day << endl;
dq2.pop();
}
return 0;
}
PS:(1)特化不能单独存在;
5.3.1 全特化
全特化:将模板参数列表中所有参数都确定化:
template
class Data
{
public:
Data() { cout << "Data" << endl; }
private:
T1 _d1;
T2 _d2;
};
//全特化
template<>
class Data
{
public:
Data() { cout << "Data" << endl; }
private:
int _d1;
char _d2;
};
int main()
{
Datad1; //走模板类
Datad2; //走全特化类
}
5.3.2 偏特化
偏特化:任何针对模板参数进一步进行条件限制设计的特化版本:
(1)模板参数是普通类型:
template
class Data
{
public:
Data() { cout << "Data" << endl; }
private:
T1 _d1;
T2 _d2;
};
//偏特化
template
class Data
{
public:
Data() { cout << "Data" << endl; }
private:
T1 _d1;
int _d2;
};
int main()
{
Datad1; //走偏特化类
Datad2; //走模板类
}
(2)模板类型是指针:
template
class Data
{
public:
Data() {cout<<"Data"<
class Data
{
public:
Data(){cout<<"Data"<d1; //走指针偏特化
Datad2; //走模板特化
Datad3; //走指针偏特化
return 0;
}
(3)模板类型是引用:
template
class Data
{
public:
Data(){cout<<"Data"<
class Data
{
public:
Data(){cout<<"Datad1;
Datad2;
return 0;
}
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一可执行文件的过程称为分离编译模式;
一般情况下,模板不支持分离编译;
(1)将声明和定义放在同一文件“xxx.hpp”或“xxx.h”中;
(2)模板定义的位置显式实例化(换一次类型则显式实例化一次,不推荐);