泛型编程
泛型编程,泛型即是指具有在多种数据类型上皆可操作的含义,其实就是能够帮助开发者编写完全一般化并可重复使用的算法,同样的工作不需要做多次,同样的算法针对不同的类型也不应该写多次,所以需要通过某种途径,来使一个容器或者算法,能够兼容所有的类型,这就是泛型编程。
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;
}
void Swap(char& left, char& right) {
char temp = left;
left = right;
right = temp;
}
就比如非常常用的swap函数,虽然c++支持重载,但是如果对于这种函数我们每一个都要自己去实现,是一件很麻烦并且多余的工作,因为我们将重复逻辑的东西多次实现。
class Stack
{
private:
int capacity;
int size;
int* arr;
};
又比如一个数据结构,如果我们实现了一个栈,如果他此时要存储其他的数据类型,那我们就必须还要重新修改一个,这无疑是多余的。
所以,c++基于重载这一项机制,实现了模板编程,模板作为类或者函数的蓝图,可以针对不同类型,来实现对应操作。
函数模板
函数模板
函数模板其实代表的是一个函数家族,,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定
类型版本。
而模板函数则时通过函数模板来实例化的具体函数
用法
template<typename T1, typename T2,......,typename Tn>
//typename即类型名,也可以用class替代,这里的class指的是类型,不能用struct替换
只需要在对应的函数前面加上这一句即可,然后将所有需要替换类型的参数改为上面的T1,T2即可
template<class T>
void Swap(T& left, T& right) {
T temp = left;
left = right;
right = temp;
}
int main()
{
int i = 3, j = 4;
double a = 3.4, b = 5.6;
char x = 'x', y = 'y';
Swap(i, j);
Swap(a, b);
Swap(x, y);
cout << i << ' ' << j << endl;
cout << a << ' ' << b << endl;
cout << x << ' ' << y << endl;
}
可以看到,这样就可以适用于多重类型。
函数模板的原理
下面来讨论一下,当我们调用这个模板函数的时候,是通过这个模板函数来实现对应的功能?还是它可能会重载成其他函数,再调用其他的函数呢?
这是只需要看看汇编代码就可以了
可以看到,它调用的是三个不同的函数,这时候就可以推论出来。
模板函数本身并不是一个函数,而是一个蓝图,通过识别我们传入的参数,然后在底层生成一个该类型的模板函数,并调用该函数。
并且这个阶段是在预处理的时候就进行了,因为我们仔细思考一下,如果它是在编译阶段才进行函数的生成,那肯定是无法通过语法的检查的,所以这一阶段只能在预处理进行。
函数模板的实例化
模板的实例化有两种方法,一种是显式实例化,一种是隐式实例化
隐式实例化
template<class T> T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int i = 3, j = 4;
double a = 3.4, b = 5.6;
Add(i, j);
Add(a, b);
/*
这时编译器就会报错,因为需要通过参数推演出对应类型,但是此时就无法
推演出到底是将T推演成double还是将T推演成int
Add(i, a);
*/
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
Add(i, (int)a);
return 0;
}
显式实例化
int main() {
int i = 3;
double x = 3.4;
// 显式实例化
Add<int>(i, x);
return 0;
}
这种则是用尖括号来显式的声明,STL中的容器等就是采用这种显式的实例化来明确参数的类型。
函数模板的匹配规则(引用)
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数,这时如果使用隐式实例化,则会调用非模板函数,如果使用显式实例化,则调用模板函数
//非模板函数
int Add(int left, int right)
{
return left + right;
}
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
Add(1, 2); // 调用非模板函数
Add<int>(1, 2); // 调用模板函数
}
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
// 专门处理int的加法函数
int Add(int left, int right) {
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right) {
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函 数
}
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
类模板
用法
类模板的用法和函数模板其实一样
template<class T>
class Stack
{
private:
int capacity;
int size;
T* arr;
};
这里的Stack不是一个具体的类,他是类模板,也就是一个蓝图,通过这个模板来识别参数的类型并生成对应的模板类。所以可以理解为类模板是一个类家族,模板类是通过类模板实例化的具体类
如果类中的成员函数需要在类外定义的话,需要每一个函数前都要声明一次类模板的参数列表
template<class T>
class Stack
{
public:
void push();
void pop();
private:
int capacity;
int size;
T* arr;
};
template<class T>
void Stack<T>::push()
{
}
template<class T>
void Stack<T>::pop()
{
}
还有一个需要注意的地方就是,类模板的所有成员函数都是模板函数
类模板的实例化
类模板只能够通过显式实例化
如STL中的几个容器都是通过类模板实现的
stack<int> s1;
stack<double> s2;
stack<int>并不是类名,而是类型,stack才是类名