泛型编程
一、函数模板
函数模板适用于具有相似功能的函数,函数的参数个数相同、函数名相同的情况。
1、函数模板的定义
template <类型形参列表> //模板头
返回值类型 函数名(函数形参列表)
{
函数体;
}
例如:
template <typename T>
T add(T a, T b)
{
return a+b;
}
2、类型形参列表
例如:<typename T1, typename T2>
说明:typename是用来声明一种类型;T1,T2是类型形参名,可以作为函数的返回值类型,形参参数类型、
局部变量的类型。
3、函数模板的使用
模板的使用又叫做模板的实例化,调用函数模板时需要传入类型实参给模板头的类型形参列表。
1)一般用法:
函数名<类型实参列表>(数据实参列表);
例:
函数模板的定义:
template <typename T>
T add(T a, T b)
{
return a+b;
}
函数模板的使用:
int c = add<int>(1,2);//调用函数模板时给出类型实参
cout << c << endl;
2)函数模板类型形参默认值
在声明函数模板时可以指定类型形参的默认值。
例:
具有类型形参默认值的函数模板的定义
template <typename T = int>
T add(T a, T b)
{
return a+b;
}
具有默认类型形参的函数模板的使用
int c = add<>(1,2);//类型形参默认值,则不需要给出类型实参,但是<>不可以省略
cout << c << endl;
3)类型形参的自动推导
add<int>(1,3) <===> add(1,3);//因为参数1,3很明显是整数类型,可以进行自动推导,得到类型形参。
4、函数模板的特化定义
特化定义是用来解决某些特殊类型作为类型形参时的函数模板的定义问题,它是对函数模板通用定义的补
充。因为函数模板的通用定义是针对一般类型如:int、char、string等类型作为类型形参时的,当需要使用
const char *等复合类型、指针类型、引用类型作为函数模板的类型形参时它是解决不了的,因此需要用到
函数模板的特化定义。
函数模板的特化定义:
template <>
返回值类型 函数名<类型实参列表>(数据形参列表)
{
函数体;
}
**全局函数、函数模板的通用定义、函数模板的特化定义**
1)调用顺序:函数比函数模板优先调用;函数模板的特化定义比函数模板的通用定义优先调用。
2)全局函数与函数模板之间构成了重载关系
二、类模板
函数模板与类模板有两点需要进行区别:
1)函数模板存在自动类型推导,而类模板没有。
2)类模板具有特化定义与偏特化定义之分;而函数模板只有特化定义,没有偏特化定义。
1、类模板的定义
template <typname T1, typename T2> //类模板的模板头
class Demo{
public:
公有成员的定义;
private:
私有成员的定义;
};
2、类模板的使用
1、一般用法:
类名<类型实参列表> 对象名;
2、类型形参默认值:
1)在定义类模板时,可以指定类型形参的默认值。
template <typename T1, typename T2 = int>
class Demo{
public:
公有成员的定义;
private:
私有成员的定义;
}
2)在调用具有类型形参默认值的类模板时,可以不用给出类型实参
例:Demo<string, int> demo1 <===> Demo<string> demo1;
3)类型形参自动推导:
类模板不支持类型的自动推导,必须指定类型。即使指定了类型形参默认值,“<>”也不可以省略。
using namespace std;
#include <iostream>
//带有形参默认值的函数模板的定义
template <typename T1, typename T2 = string>
class Demo{
public:
Demo(T1 a, T1 b):a(a),b(b){}
void show(T2 data)
{
cout << data << endl;
}
int compare()
{
int ret = 0;
if(a != b)
ret = a > b? 1:0;
return ret;
}
private:
T a;
T b;
};
int main()
{
Demo<int,int> demo1(1,3);//函数模板的实例化
demo.show(666);
cout << d.compare() << endl;
Demo<int> demo2(1,2);//调用类型形参默认值的类模板
demo2.show(string("Hello World"));
//Demo d3(1,2); //error类模板不支持自动类型推导
//Demo<> demo4(1,2); //假设类型形参全部指定了默认值,<>也不可以省略。
return 0;
}
例题:在类的通用模板中对成员函数做声明,在类外部进行定义。
using namespace std;
#include <iostream>
template <typename T>
class Demo{
public:
void show();
private:
T val;
};
//类模板中成员函数的定义
template <typename T>
void Demo<T>::show()
{
cout << "show()一下" << endl;
}
int main()
{
Demo<int> demo1;
demo1.show();
return 0;
}
3、类模板的全特化定义
与函数模板的全特化定义类似,类模板的全特化定义时针对特殊的类型的定义,是对通用定义的补充。
类模板的全特化定义:
template<>
class Demo<类型实参列表>{
public:
公有成员;
private:
私有成员;
};
例如:
template <>
class Demo<const char*, int>{
public:
公有成员;
private:
私有成员;
}
注意
在一个源文件中不能只有模板的全特化定义或者偏特化定义,必须还有模板的通用定义,否则源程序无法
正常编译。因为特化定义和偏特化定义本来就是对通用定义的补充,如果没有通用定义则特化定义和偏特化定
义则不合法。
例题:
1、错误示范,只有特化定义,没有通用定义:
using namespace std;
#include <iostream>
//类模板的特化定义
template<>
class Compare<float>{
public:
bool equal(float a, float b);//在类模板的内部对成员函数进行声明
};
//在类模板的外部对成员函数进行定义
bool Compare<float>::equal(float a, float b)
{
return a == b;
}
int main()
{
Compare<flaot> c1;
cout << c1.equal(1.01, 1.02) << endl;
return 0;
}
//运行结果:编译出错误,无法通过。编译器检查到Compare不是一个类模板
2、正确示范:在特化定义的前面先进行通用的定义。
using namespace std;
#include <iostream>
//类模板的通用定义
template<typename T>
class Compare{
public:
bool equal(T a, T b);//类模板的内部进行成员函数的声明
};
//在类模板的外边进行定义
template <typename T>
bool Compare<T>::equal(T a, T b)
{
return a == b;
}
//类模板的特化定义
template<>
class Compare<float>{
public:
bool equal(float a, float b);//在类模板的内部对成员函数进行声明
};
//在类模板的外部对成员函数进行定义
bool Compare<float>::equal(float a, float b)
{
return a == b;
}
int main()
{
Compare<flaot> c1;
cout << c1.equal(1.01, 1.02) << endl;
return 0;
}
//运行结果:结果正确,符合预期结果。
4、类模板的偏特化定义
如果类模板有多个类型参数,进行特化定义是只需要特化部分类型参数,则成为偏特化定义。如果需要特
化全部的类型参数就称为全特化定义。
类模板的偏特化定义形式:
template <typename T>
class 类名<特化类型,T>{
public:
公有成员;
private:
私有成员;
};
template <typename T>
class Demo<const char*, T>{
public:
公有成员;
private:
私有成员;
};
模板调用时的匹配规则:
1)全特化类模板优先于偏特化类模板
2)偏特化类模板优先于通用类模板
三、pair<T1, T2>类模板
1)标准库中的pair是一个类模板,需要包含头文件<utility>。
2)pair可以将任意类型的两个数据组合成一个数据对。
3)pair包含两个公有的数据成员first和second
pair<T1, T2>的用法:
1)pair<T1, T2> p1;
调用无参构造函数定义一个空的pair对象,pair对象的两个成员变量的类型分别是T1和T2,没有值。
2)pair<T1, T2> p2(v1, v2);
调用有参构造函数,定义了一个pair对象,first成员变量的类型为T1,值为v1;second成员变量的类型
是T2,值为v2。
3)p1 == p2
如果p1和p2的两个数据成员变量first和second都相等则p1与p2相等。
4)p1 < p2
遵循字典次序:第一种情况,p1.first < p2.first;第二种情况,p1.first = p2.first 且p1.second < p2.second
例1:利用运算符的重载,使其可以输出pair类对象
using namespace std;
#include <iostream>
#include <utility>
//运算符重载函数
ostream& operator<<(ostream& out, pair<int, string>& p)
{
out << "p.first:" << p.first << endl;
out << "p.second:" << p.second;
return out;
}
int main()
{
pair<int, string> p(1001, string("lilei"));
cout << p << endl;
return 0;
}
例2:在上述例题的基础上使用函数模板,使输出运算符可以输出多种类型的pair对象
using namespace std;
#include <iostream>
#include <utility>
template <typename T1, typename T2>
ostream& operator<<(ostream& out, const pair<T1, T2>& p)
{
out << "p.first:" << p.first << endl;
out << "p.second:" << p.second;
reutrn out;
}
int main()
{
pair<int, int> p1(1,3);
cout << p1 << endl;
pair<int, string> p2(1001, string("lilei"));
cout << p2 << endl;
return 0;
}