1,泛型编程
看如下代码
int Sum(int a, int b)
{
return a + b;
}
double Sum(double a, double b)
{
return a + b;
}
char Sum(char a, char b)
{
return a + b;
}
int main()
{
cout << Sum(10,20) << endl;
cout << Sum(10.1,20.2) << endl;
cout << Sum(10,20) << endl;
return 0;
}
虽然可以用函数重载完成不同类型的值相加,但是也有几个不好的地方:
1,函数重载仅仅是类型不同,代码复用率低,只要有新类型出现,用户需要增加对应的函数
2,代码的可维护性低,一个出错可能导致全部出错
为了解决上面的问题,C++也推出了一个摸具,通过给这个摸具填充不同类型,来获得不同类型的代码。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
二,函数模板
2.1函数模板定义
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型的不同产生对应的特定的类型版本。
格式为template<typename T1,typename T2 ......,typename Tn>
template<typename T>
T Sum(T a,T b)//完成不同类型的相加
{
return a + b;
}
int main()
{
cout << Sum(10,20) << endl;
cout << Sum(10.1,20.2) << endl;
cout << Sum(10,20) << endl;
return 0;
}
2.2模板原理
模板本质是一个蓝图,本身不是函数,是编译器产生的特定具体类型的摸具。简单来说模板就是把很多重复繁琐的事情交给了编译器来做。
在编译器编译阶段,对于函数模板,编译器更具传入的实参类型来生成对应类型的函数以供调用
2.3模板实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。
2.3.1隐式实例化
让编译器根据实参推演模板参数的实例类型
template<typename T>
T Sum(T a,T b)//编译器自动推演类型
{
return a + b;
}
int main()
{
cout << Sum(10,20) << endl;
cout << Sum(10.1,20.2) << endl;
cout << Sum(10,20) << endl;
return 0;
}
2.3.2显式实例化
int main()
{
int a = 10;
double b = 20.0;
cout<<Sum<int>(a,b)<<endl;
return 0;
}
在函数名后的<>中指定模板参数的实际类型
2.4函数模板的匹配原则
1,一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
2,对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板
//专门处理int的加法函数
int Sum(int a, int b)
{
return a + b;
}
//通用同类型加法函数
template<typename T>
T Sum(T a, T b)
{
return a + b;
}
//不同类型的Sum版本
template<typename T1,typename T2>
T1 Sum(T1 a, T2 b)
{
return a + b;
}
int main()
{
cout<<Sum(1,2)<<endl;//与非模板函数匹配,编译器不需要特化
cout<<Sum<int>(1,2)<<endl;//调用同类型的Sum版本
cout<<Sum(1,2.0)<<endl;//编译器根据实参调用不同类型的Sum版本
return 0;
}
三,类模板
3.1类模板定义
template<class T1,class T2 ...... class Tn>
template<typename T>
class Stack//注意此处Stack不是具体的类,时编译器根据实例化类型生成具体类的摸具,原因是T还没有被实例化
{
public:
Stack(size_t capacity = 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 = nullptr;
size_t _top = 0;
size_t _capacity = 0;
};
//注意,类模板中函数在类外进行定义时,需要加模板参数列表
template<class T>//模板在同一个文件中,是可以声明定义分离的
void Stack<T>::Push(const T& x)
{
if (_top == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
// 1、开新空间
// 2、拷贝数据
// 3、释放旧空间
T* tmp = new T[newCapacity];
if (_a)
{
memcpy(tmp, _a, sizeof(T)*_top);
delete[] _a;
}
_a = tmp;
_capacity = newCapacity;
}
_a[_top] = x;
++_top;
}
3.2类模板实例化
int main()
{
//Stack是类名,Stack<int>才是类型,s1是根据类模板实例化出的对象
Stack<int> s1;
Stack<double> s2;
return 0;
}
四,非类型模板参数
非类型形参,就是用一个常量作为类或函数模板的一个参数,在该类或模板中可以将该参数当成常量来使用。
template<class T,size_t N =10>
class Array
{
public:
void Print()
{
cout << N << endl;
}
};
int main()
{
Array<int,20> a;
a.Print();
return 0;
}
五,类模板的特化
5.1特化概念
先实现一个日期类
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(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;
}
}
int _year;
int _month;
int _day;
};
之后实例化几个对象并进行比较
template<class T>
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绝大多数情况下都可以正常比较,但是也有特例,比如上面的代码,并没有比较p1和p2所指向的d1和d2,而是直接比较p1和p2了,从而错误。
5.2特化
步骤:
1,必须要有一个基础的函数模板
2,template后面接一对空的<>
3,函数名后接一对尖括号,尖括号中指定需要特化的类型
4,函数形参表必须和模板函数的参数类型完全相同。
//函数模板 -- 参数匹配
template<class T>
bool Greater(T left, T right)
{
return left > right;
}
//对Greater特化
template<>
bool Greater<Date*>(Date* left, Date* right)
{
return *left > *right;
}
int main()
{
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
Date* p1 = &d1;
Date* p2 = &d2;
cout << Greater(p1, p2) << endl; // 可以比较,结果
}
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该特定类型的函数直接给出。
bool Less(Date* left, Date* right)
{
return *left < *right;
}
5.3全特化
将模板参数列表中的所有此参数都确定化
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;
};
int main()
{
Data<int, int> d1;
Data<int, char> d2;
return 0;
}
5.2偏特化
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 偏特化
//1,部分特化,将模板参数类表中的一部分参数特化
template <class T1>
class Data<T1, int>
{
public:
Data() { cout << "Data<T1, int>" << endl; }
private:
T1 _d1;
int _d2;
};
//2,参数进一步限制
//两个参数偏特化为指针类型
template<class T1, class T2>
class Data<T1*,T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
};
//两个参数偏特化为引用类型
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
Data() { cout << "Data<T1&, T2&>" << endl; }
};
int main()
{
Data<int, int> d0;
Data<double, int> d1;
cout << endl;
Data<int, char> d2;
cout << endl;
Data<double, double> d3;
Data<double*, double*> d4;
Data<int*, char*> d5;
Data<int*, char> d6;
cout << endl;
Data<int&, char&> d7;
Data<int&, double&> d8;
return 0;
}
六,分离编译
6.1什么是分离编译
一个程序,由若干个源文件共同实现,每个源文件编译生成目标文件后,将所有的目标文件链接起来形成一个统一的可执行文件的过程称为分离编译模式。
6.2模板的分离编译
//a.h
template<class T>
T Add(const T& left, const T& right);
//a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
//test.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
模板不支持分离编译,因为声明和定义分离时,编译器没有看到a.cpp中对Add模板函数的实例化,因此不会生成具体的加法函数,而test.cpp中的Add<int>与Add<double>,编译器在链接时才会找其地址,但是这两个函数都没有实例化出具体代码,因此链接时报错
解决方法:
1,将声明和定义放到一个文件“xxx.h”里面。
2,模板定义的位置显示实例化。一般用第一种方法。
七,模板总结
优点:
1,模板复用了代码,节省资源,C++的标准模板库(STL)因此产生。
2,增强了代码的灵活性。
缺点:
1,模板会导致代码膨胀问题,也会导致编译时间边长
2,出现模板编译错误时,错误信息非常凌乱,不易定位错误。