一.泛型编程
编写一种和类型无关的代码,让编译器根据实际数据类型进行推导 模板是泛型编程的基础
二.模板
模板可以分为函数模板和类模板两种类型
1.函数模板
一种根据实际类型推导类型的函数家族
1) 函数模板的格式
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
示例:
template<class T>
T ADD(T a, T b)
{
return a + b;
}
2)函数模板的原理
函数模板本身并不是函数,是编译器根据实际使用方式产生特定具体类型函数的模具。在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
3)函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化 根据实例化方式的不同,可以分为隐式实例化和显示实例化两种类型 隐式实例化 让编译器根据实参推演模板参数的实际类型
隐式实例化示例:
int a = 1, b = 2;
int ans = ADD(a, b);
隐式实例化存在的问题: 对于类型不匹配时,编译器会报错,可以进行强制类型转换
int a = 1000;
double b = 24.0;
int ans = ADD(a, static_cast<int>(b));
错误:
显示实例化 在函数名后的<>中指定模板参数的实际类型
4)模板参数的匹配规则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
2.类模板
一个类中的某些数据并不是固定的,而是根据实际的需要使用特定的数据类型,这样的模板就是一个类模板
1)类模板的格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
};
示例:
template<class T>
class Stack
{
private:
T* _data;
int _capacity;
int curSz;
};
2) 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
示例:
Stack<int> st_Int;
Stack<TreeNode*> st_Tree;
三.模板参数
模板参数分为类型形参与非类型形参
1) 类型形参
出现在模板参数列表中,跟在class或者typename之后的参数类型名称
2) 非类型形参
用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
示例:
template<class T>
class Vector
{
public:
Vector(int sz)
{}
private:
T* _data;
int _size;
int _capacity;
};
3) 使用注意事项
浮点数、类对象以及字符串是不允许作为非类型模板参数的;
非类型的模板参数必须在编译期就能确认结果;
四.模板特化
有些场景下,需要对于特殊类型进行特殊化实现,这就是模板特化 模板特化分为函数模板特化和类模板特化
1. 函数模板特化
1)特化的步骤
- 必须要先有一个基础的函数模板,特化是在模板已经存在的前提下进行的;
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
示例:
struct Date
{
Date(int year = 2021, int month = 10, int day = 11)
:_year(year)
, _month(month)
, _day(day)
{}
int _year;
int _month;
int _day;
};
template<class T>
bool IsEqual(const T& a, const T& b)
{
return a == b;
}
template<>
bool IsEqual<Date>(const Date& d1, const Date& d2)
{
return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
void test()
{
bool ret = IsEqual(1, 2);
Date d1(2021, 1, 2);
Date d2;
ret = IsEqual(d1, d1);
}
2. 类模板特化
根据使用对象的不同分为全特化和偏特化两种类型 特化的前提是已经有一个模板
1) 全特化
模板参数列表中所有的参数都确定化
示例:
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template<>
class Data<int, char> {
public:
Data() { cout << "Data<int, char>" << endl; }
private:
int _d1;
char _d2;
};
void TestVector()
{
Data<int, int> d1;
Data<int, char> d2;
}
2) 偏特化
针对模板参数进一步进行条件限制
偏特化的两种表现方式: 部分特化 将模板参数类表中的一部分参数特化 参数更进一步的限制 针对模板参数进一步做出条件限制
根据模板进行两种偏特化示例:
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
template<class T2>
class Data<int, T2>
{
public:
Data() { cout << "Data<int, T2>" << endl; }
private:
int _d1;
T2 _d2;
};
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:
T1* _d1;
T2* _d2;
};
五.模板的分离编译
1.分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式
2.模板分离编译带来的问题
C++编译器不支持模板的分离编译 如果模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义,那么在编译期间程序会去源文件中找对应的实现,但是此时的模板只是一个模具,源文件中找不到实际的实现,因此编译器就会报错
解决办法: 将声明和定义放在一起
六.模板的优缺点
优点: 实现了代码复用,提高了开发效率; 增强了代码的灵活性; 缺点: 模板会导致代码膨胀问题,也会导致编译时间变长; 出现模板编译错误时,错误信息非常凌乱,不易定位错误;
|