泛型编程
C语言中交换两个变量数据的内容一般是这样实现的
#include<iostream>using namespace std;void swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
int main()
{int x = 5;int y = 7;swap(&x,&y);cout << "x=" << x << " ";cout << "y=" << y << " ";}
但自从学了C++引用以后 就不用再传地址和用指针接受实参地址了,直接使用引用 既高效又不开辟栈帧空间。
#include<iostream>using namespace std;void swap(int &xx, int&yy)
{int tmp = xx;xx = yy;yy = tmp;
}
int main()
{int x = 5;int y = 7;swap(x,y);cout << "x=" << x << " ";cout << "y=" << y << " ";}
结果是一样的。
但如果要实现多种不同类型数据之间的交换呢?
C++可以使用函数重载
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;
}
虽然函数重载可以实现 但有几个不好的地方
模板
函数模板
#include<iostream>using namespace std;template<class T>
void swap(const T&x, const T&y)
{T tmp = x;x = y;y = x;
}
int main()
{int x = 5;int y = 7;swap(x,y);cout << "x=" << x << " ";cout << "y=" << y << " ";}
函数模板的格式
template<typename T1,typename T2,.......typename Tn>
返回值类型 函数名(参数)
typename是用来定义模板参数的关键字,也可以用class。(一般是用class,但不能以struct替代class)
#include<iostream>using namespace std;template<class T>
void swap(const T&x, const T&y)
{T tmp = x;x = y;y = x;
}
int main()
{int x = 5;int y = 7;double a = 1.34;double b = 2.34;swap(x,y);swap(a, b);cout << "x=" << x << endl;cout << "y=" << y << endl;cout << "a=" << a << endl;cout << "b=" << b << endl;}
模板会根据你传的实参类型自动推演生成对应数据类型的函数。
但它们俩调用的却不是同一个函数 通过反汇编就可以看出来
编译器自动完成的事情。
函数模板的原理
要清楚模板是在编译阶段就调用完成的。
#include<iostream>using namespace std;template<class T,class Y>
T add(const T& x, const Y& y)
{return x + y;
}int main()
{int a = 4;double b = 3.14;int ret=add(a, b);cout << ret << endl;
}
函数模板的实例化
模板参数语法其实很类似函数参数,函数参数定义的形参对象,模板参数定义的是类型
用不同类型的参数使用函数模板时,是函数模板的实例化。
而实例化又分为隐式实例化和显示实例化
隐式实例化:让编译器根据实参类型推演模板参数实际类型的过程
显式实例化: 在函数名后的<>中指定模板参数的实际类型
#include<iostream>using namespace std;template<class T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int a = 5;int b = 11;double c = 3.14;double d = 4.14;int ret = Add(a, b);double ret1 = Add(c, d);int ret2 =Add(a, d);error该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅所以为了解决这个问题 1.要么用户自己强转 2.要么显式实例化int ret3 = Add(a, (int)d);cout << ret << endl;cout << ret1 << endl;cout << ret3 << endl;显式实例化cout << Add<int>(a, c) << endl;cout << Add<double>(a, b) << endl;
}
一般这种时候就要显式实例化
否则编译器也不知道f是返回值是什么?
模板参数的匹配原则
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
#include<iostream>using namespace std;普通函数
int Add(const int x, const int y)
{cout << "(const int x, const int y)" << endl;return x + y;
}函数模板
template<class T>
T Add(const T& x, const T& y)
{cout << "(const T& x, const T& y)" << endl;return x + y;
}int main()
{Add(1, 2);Add(1.1,1.2);
}
当我把函数模板屏蔽后再运行
double到int可以进行隐式类型转换 所以去调用了普通函数。
由此我们可以发现模板参数匹配调用原则:
1.有现成的吃现成的 (第一个int类型去调用普通函数 第二个double类型去调用函数模板)
2.有适合的吃适合的,哪怕自己要现做
3.没有就将就吃 (函数模板屏蔽后,去调用普通函数)
类模板
类模板的格式
template<class T1,class T2class Tn>
class 类模板名
{
// 类内成员定义
}
#include<iostream>using namespace std;template<class T>class stack
{
public:nstack(int n = 4){cout << "stack(int n = 4)" << endl;_a = new T[n];_top = 0;_capacity = n;}~stack(){cout << "~stack()" << endl;delete _a;_a = nullptr;_top = _capacity = 0;}private:T* _a;int _top;int _capacity;
};如果想声明和定义分离这样写
但模板一般声明和定义都是在一个文件写的 不会分开写 因为会产生链接错误
暂且先不讲 我在模板进阶再详细说
template<class T>
Stack<T>::Stack(int n)
{cout << "Stack(int n = 4)" << endl;_a = new T[n];_top = 0;_capacity = n;
}int main()
{显式实例化stack<int>st;stack<double>st;
}
拿栈来做例子,C语言里面如果栈里面要存int和double类型数据 还要typedef 成intdatatype 或者doubledatatype 还要把类型名改成stackint 或者stackdouble
而有了类模板以后就避免了这种低效用法,直接显式实例化要存的类型数据,类模板参数直接推演出对应存储数据类型。
类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
#include<iostream>
#include<vector>
#include<stack>
using namespace std;int main()
{vector是类名 vector<int>才是类型vector<int>s;同理stack是类名 stack<double>才是类型stack<double>st;return 0;
}
总结:普通类----类名是类型 类模板----类名<数据类型>才是整个类的类型。