目录
1、泛型编程
泛型编程是一种编程风格,它可以让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。泛型编程的代表作就是STL。
int Add1(int a, int b)
{
return a + b;
}
double Add2(double a, double b)
{
return a + b;
}
int main()
{
double a = 12.5, b = 13.5;
cout << Add2(a, b) << endl;
return 0;
}
上面的Add函数只有当参数匹配能得出正确结果,那么如果有多种类型都要使用Add函数时就必须多写几个Add函数,这样的代码复用率低,可维护性低,所以就需要用到泛型编程思想。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
模板就相当于一个模具,这个模具可以做出很多类似的产品,C++中通过函数模板可以解决上面Add函数要写多个的问题。
2、函数模板
概念
函数模板代表相似功能的函数,这个函数模板与类型无关;在使用时会转换成特定类型版本,类似于之前提到过的auto。
格式
template <typename T1,typename T2...>
//参数类型相同
template<class T>
T Add3(T a, T b)
{
return a + b;
}
//参数类型不同
//要求返回值按T2参数化后的类型返回
template<class T1,class T2>
T2 Add4(T1 a, T2 b)
{
return a + b;
}
int main()
{
double a = 12.5, b = 13.5;
cout << Add3(a, b) << endl;
int c = 10;
cout << Add4(a, c) << endl;
return 0;
}
注意:typename是用来定义模板参数的关键字,也可以用class(不能用struct),通常我们都是用class
函数模板原理
函数模板本质就是一个模具,本身并不是函数,当我们传给它数据后,它会根据传入的实参类型推导出对应类型的函数;例如上面的Add4,我们传了一个double和int类型的参数,它会自动推导出T1就是double,T2是int,返回值为int,然后运行函数。
如上就是通过函数模板实例化出的两个不同函数,两个char类型或者short等都可以使用该模板。
函数模板的实例化
对于不同类型的参数使用函数模板时,这个过程就叫做函数模板的实例化。函数模板的实例化分为显示实例化和隐式实例化。
隐式实例化
template<class T>
T Add(T a, T b)
{
return a + b;
}
int main()
{
double a = 12.5, b = 13.5;
cout << Add(a, b) << endl;
return 0;
}
上面就是函数模板的隐式实例化,我们不指明要实例化成什么类型,让系统根据实参类型自己去推导。
显式实例化
template<class T>
T Add3(T a, T b)
{
return a + b;
}
int main()
{
double a = 12.5;
int b = 13;
//指定实例化成int类型
cout << Add<int>(a, b) << endl;
return 0;
}
在函数名后<>内指定参数模板的实际类型。
如果参数不匹配,编译器会进行隐式类型转换,如果无法转换的话就会报错。
函数模板的匹配原则
函数模板可以和同名的非函数模板函数同时存在。
template<class T>
void Add(T a, T b)
{
cout << "void Add1(T a, T b):" << a + b << endl;
}
void Add(int a, int b)
{
cout << "void Add2(int a, int b):" << a + b << endl;
}
int main()
{
int a = 10, b = 10;
Add(a, b);
double c = 10.5, d = 10.5;
Add(c, d);
return 0;
}
可以看到,如果有现成的int类型的Add函数,会去优先选择现成的,而不会去调用函数模板实例化。当没有现成的函数使用时,就回去调用函数模板实例化一个。
3、类模板
类模板的格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
使用实例:
template<class T>
class A
{
public:
A(size_t capacity = 4)
: _p(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
// 析构函数类外定义示例
~A();
private:
T* _p;
int _size;
int _capacity;
};
// 类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
A<T>::~A()
{
if (_p)
delete[] _p;
_size = _capacity = 0;
}
类模板的实例化
类模板的实例化需要在类模板名字后加上<>,在<>里写上实例化的类型,类模板的名字并不是真正的类,实例化出来的结果才算是真正的类。
// A是类名,A<int>才是类型
A<int> a1;
A<double> a2;
4、非类型模板参数
模板参数有两种:类型形参和非类型形参
类型形参:就是在class和typename后加参数类型名,就像上面例子中的 class T 一样。
非类型模板参数:用常数作为模板参数,在类模板和函数模板中可以把非类型模板参数当做常量使用。
template<class T,size_t N = 4>
class A
{
private:
//定义一个静态数组,大小为N
T _p[N];
size_t _size;
};
说明:
1、非类型模板参数只能是常量整型值(包括枚举),指向对象、函数、成员的指针,指向对象或者函数的左值引用。
2、浮点数和类类型对象不能作为非类型模板参数。
3、非类型的模板参数必须在编译期就能确认结果。
5、模板的特化
类模板的特化
在有些情况下,针对特殊的类型,需要对模板进行特化,例如当我们需要一个bool型的时候,实际上bool类型用1个bit位就能标识了,这是如果用模板就会浪费空间。
//正常
template <class T>
class A {};
//特化
template < >
class A<bool> {};
template<>就相当于告诉了编译器这是一个模板的特化。
函数模板特化
template<class T>
void Add(T x, T y)
{
cout << x + y << endl;
}
template<>
void Add<int*>(int* p, int* q)
{
cout << *p + *q << endl;
}
int main()
{
int a = 5;
int b = 5;
Add(a, b);
double c = 5.5;
double d = 5.5;
Add(c, d);
//当两个指针相加时,会报错,无法直接相加
//针对这种情况,就需要写一个模板的特化
int* p1 = &a;
int* p2 = &b;
Add(p1, p2);//这里走的就是第二个的Add
return 0;
}
说明:
1、必须在template后加上一个空的<>
2、函数名后加<>,里面写上指定的类型
3、形参列表里的参数类型必须和特化的类型一致
模板的偏特化
部分特化:将模板参数一部分进行特化
template <class T1, class T2>
class A {};
template <class T>
//下面两种都可以,不分先后
//class A<T, bool> {};
class A<bool,T> {};
将模板参数偏特化成指针类型
//不能没有主模板,只有偏特化。
//所以必须写上主模板声明/定义,不写下面这句就会报错
template <class T1, class T2> class Data;
//将两个参数特化成指针类型
template<class T1, class T2>
class Data <T1*,T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:
T1 _d1;
T2 _d2;
};
int main()
{
//创建两个int*的类对象
Data<int*, int*> d;
return 0;
}
将模板参数偏特化成引用类型
template <class T1, class T2> class Data;
//将两个参数特化成指针类型
template<class T1, class T2>
class Data <T1&, T2&>
{
public:
Data() { cout << "Data<T1&, T2&>" << endl; }
private:
T1 _d1;
T2 _d2;
};
int main()
{
Data<int&, int&> d1;
Data<int&, double&> d2;
return 0;
}
模板特化的匹配规则
类模板的匹配方式
template <class T> class A {}; // 普通类型都能匹配
template <class T> class A<T*> {}; //指针类型优先选T*
template <class T> class A <T&> {}; //引用类型优先选T&
template <> class A <int*> {}; //int*类型优先选
函数模板的匹配方式
template <class T> void Func(T); //普通类型都可以
template <class T> void Func(int, T); //当第一个参数int时匹配
template <class T> void Func(T*); //优先指针类型
template <> void Func<int>(int); //参数只有一个int时匹配
void Func(double); //参数只有一个double类型时匹配
总结:当类型匹配时,优先选择已经存在的,尽量不去使用特化版本。