模板
泛型编程,就是采用一种“通用”的类型来进行代码的编写,使程序独立于特定的数据类型。
此目的在于实现更快捷的代码重用,使程序设计者的主要精力集中在程序逻辑的实现,规避由于不同的数据类型给程序编写带来的麻烦。
函数模板
单类型模板
template <class T>
T Sum(T parameter1, T parameter2){
T sum = paratemer1 + paratemer2;
return sum;
}
int main(){
int a=1;
int b=2;
float c=1.1;
float d=2.2;
cout<<Sum(a,b)<<" "; //Sum<int>(a,b)
cout<<Sum(c,d)<<" "; //Sum<float>(c,d)
//完整写法
template
是模板的关键字,class
是标识模板中类型的关键字,替换成typename
也行
而T
就是类型替换符,当我们使用模板时,程序会根据传入的参数类型,自动替换T
多类型模板
#include <iostream>
using namespace std;
template<typename T1,typename T2>
void swap(T1& p1, T2& p2){
T1 tmp = p1;
p1 = (T1)p2;
p2 = (T2)tmp;
}
int main(){
int a = 10;
double b = 12.3;
::swap<int,double>(a,b);
cout<<a<<" "<<b; // 12 10.0
}
因为在标准命名空间中,已经有swap
这个函数名了,这里在自定义的函数swap
前加上::
,就表示是该swap
函数名,是本次程序自定义的,而不是系统std命名空间下的swap
重载
函数模板重载其实和普通的函数重载一样
T Sum(T p1, T p2, T p3){
T sum = p1+p2+p3;
return sum;
}
与前面T Sum(T paratemer1, T paratemer2)
重载
记得普通函数的重载条件
- 参数个数不同
- 参数类型不同
- 不同类型参数的顺序不同
本例是单类型模板的重载,那就去掉了第3种情况
而模板,本身就是服务于不同类型参数实现同一功能的函数,那第2点也就不需要考虑
所以这里也就只有第1点
而在多类型模板的重载中,就是1,3两点了
void swap(T1 &a, T2 &b);
void swap(T2 &a, T1 &b);
类模板
const int n = 10;
template <class T>
class Stack
{
T stk[n];
int top; //栈顶
public:
Stack(){top = -1;}
void push(T ob); //入栈
T pop(); //出栈
}
//类外定义成员函数
void Stack<T>::push(T ob){
if(top == n-1){
//栈满
return ;
}
stk[++top] = ob;
}
T Stack<T>::pop(){
if(top<0){
//栈空
return (0);
}
return stk[top--];
}
类模板的定义和函数模板的定义差不多,只是需要注意的细节多一些
类模板实例化
Stack<char> s1;
Stack<char> s2, *sp = &s2;
这不就和平常使用STL中vector
,queue
,unorder_map
一样吗
类模板的其他语法规则
typedef string type;
template <class type>
class example{
···
type n;
}
全局类型名和局部类型名均是type
,而类内的变量n
的类型也是type
但n
根据“局部优先”规则,类型是在实例化过程中依据实参决定,而非是全局类型string
类型参数带缺省类型
template <class T1 = char, class T2 = int>
class example{
T1 n;
T2 m;
···
}
对带有缺省值的类模板进行实例化过程中,通常有三种方式
example<> e1
example<double> e2
,此处的类型double
按从左到右的顺序进行实例化,T1
是double
类型,T2
是缺省的int
类型example<int, bool> e3
其中需要注意的是,在定义带有缺省值的类模板时,只能从右到左开始缺省
template <class T1, class T2 = int>
template <class T1 = char, class T2>
前者正确,后者错误
上面三种方式中的第2点,类型参数实例化是从左往右进行的,如果定义类模板时,写成后者
结果就是,T1 = double
而T2
没有实例化
函数模板没有参数的类型缺省
普通函数,有参数缺省值,但没有类型缺省值吧
int Sum(int a = 1, int b = 2)
{
return a + b;
}
类模板组合
在类模板的定义中,内嵌自身或其他类模板的对象的定义
template <class U>
class A{
A<U> *p; //内嵌自身时,可以省去<U>
}
template <class T>
class B{
A<U> a; //不可省略<U>
}
类模板派生
熟悉类派生的话,这里就很简单了,
只需要在派生时,在基类后面加上基类的模板类型
template <class T>
class A{
T t
}
template <class U>
class B:public A<T> //派生时在基类后面加上基类的模板类型
{
···
}
模板特化
特化,分为部分特化和全特化
看名字都能知道什么意思吧
函数模板特化
函数只有全特化
template <class T>
bool isEqual(T a, T b)
{
if(a==b)
return true;
return false;
}
template<>
bool isEqual(char *a, char *b)
{
if(strcmp(a,b))
return true;
return false;
}
int main(){
char s1[] = "abc";
char s2[] = "abd";
cout<<isEqual(10,15)<<endl; // 0
cout<<isEqual(s1,s2)<<endl; // 0
}
全特化template<>
如果全特化函数模板前的
template<>
模板声明去掉,就变成了普通函数。
C++对重载函数的匹配调用规则,普通函数最优,全特化函数模板次之,基础函数模板最后
类模板特化
全特化类模板,和全特化函数模板一样,必须用template<>
声明
template<>
class A<char *>{
···
}
部分特化类模板
template <class T1, class T2>
class A{
T1 t1;
T2 t2;
···
}
template <class T>
class A<bool, T>{
bool t1;
T t2;
···
}
类的调用匹配优先级
全特化类模板 > 偏特化类模板 > 基础类模板
操作符重载
关键字operate
class complex{
double real;
double imag;
public:
complex(double a = 0,double b = 0)
{
real = a;
imag = b;
}
complex operate+(complex c){}
}
//类外定义成员函数 complex:: 声明该函数是complex类的成员函数
complex complex::operate+(complex c)
{
complex d;
d.real = real + c.real; //real 成员变量
d.imag = imag + d.imag; //imag 成员变量
return d;
}
int main(){
complex a(3,4),b(5,6),c;
c = a + b; // c = a.operate+(b); 所以二元运算符的重载,参数就是运算符后面的变量
}
一元运算符和二元运算符的重载有明显的差别。
因为一元运算符只有一个操作数,所以重载形式中不含参数
前置 ++ 的重载形式为operate++()
后置 ++ 的重载形式为operate++(int)
赋值号的重载
在用类的对象初始化另一个对象,可以定义时进行初始化,还可以直接使用=
赋值
而其实这里就暗含了=
的重载,一个对象的各种成员变量的值复制给另一个对象
通常来说并不需要我们自行重载=
,但类的对象之间赋值,有深拷贝和浅拷贝之分
所以,是否需要重载=
,得看情况