函数模板
我们经常会遇到一种情况:用相同的方法处理不同的数据。对于是函数,我们可以用函数重载来解决。虽然重载可以解决这种情况,但还是很繁琐。如果函数重载10次,有一天你突然发现有新的需求,函数需要修改,那你只能把这10个函数依次修改了,麻烦的要死!所以函数模板他来了。
函数模板代表了一类函数,函数模板与参数类型无关,在使用时被实例化,根据参数类型产生特定类型的函数版本。
函数模板格式
template<typename T1, ... ,typename Tn>
函数返回值 函数名 (参数列表)
{
函数体;
}
注:typename可以用class替换,二者基本没有区别
T1 ... Tn表示参数名,可以自己随便命名
Swap函数模板:
template<class T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
函数模板原理
函数模板不是函数,在编译器编译阶段,编译器会根据传入的实参类型推演生成对应的函数以供调用。所以我们调用的不是模板函数而是由模板函数生成的函数。
函数模板实例化
当我们使用函数模板时,生成函数供我们使用,这个过程就是函数的实例化。函数实例化分两种方式:隐式实例化和显式实例化。
隐式实例化
编译器自己推演模板参数类型
template<typename T>
T Add(T& a, T& b)
{
return a + b;
}
int main()
{
int a = 5;
int b = 10;
cout << Add(a, b) << endl;//15
}
显式实例化
如果加法函数的两个参数是不同的类型,隐式实例化会报错,因为模板参数T只能是一个类型。
解决这种情况有三种办法:
方法一:强制类型转换
template<typename T>
T Add(T& a, T& b)
{
return a + b;
}
int main()
{
int a = 5;
double b = 10;
cout << Add((double)a, b) << endl;
cout << Add(a, (int)b) << endl;
}
方法二:使用两个模板参数
template<typename T1, typename T2>
T2 Add(T1 a, T2 b)
{
return a + b;
}
int main()
{
int a = 5;
double b = 10;
cout << Add(a, b) << endl;
}
方法三:显式实例化
在函数名后<>指定模板参数的类型
template<typename T>
T Add(T a, T b)
{
return a + b;
}
int main()
{
int a = 5;
double b = 10;
cout << Add<int>(a, b) << endl;
cout << Add<double>(a, b) << endl;
}
如果指定类型与实际类型不匹配,编译器会自动进行类型转换,无法转换就会报错
注意:
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
类模板
类模板和函数模板类似
对于C++中用来存储数据的container,例如vector,string,list,stack等等,都是类模板。container可以存储各种不同的数据,不管是自定义类型还是内置类型。我们使用这些container存储具体的数据就是一种类模板的实例化。
//类模板定义
template<class T1,...,Tn>
class classname
{
...
}
//类模板实例化
void test()
{
//type是任意的数据类型
classname<type> c;
...
}
typename和class的区别
二者在大部分情况下没有区别,但是这种情况下会不同:
当模板参数是容器内部定义的类型参数,并且在后面也使用了该参数。那么在使用该参数时必须加typename前缀
template<class T, class container=vector<T>>
//在后面的类中使用了 容器中的类型 的模板参数
class it
{
private:
container c;
public:
void print()
{
//必须使用typename前缀
typename container::iterator _it = c.begin();
while (_it != c.end())
{
cout << *_it << ' ';
_it++;
}
}
void pushback(const T& x)
{
c.push_back(x);
}
};
原因:
当我们用::使用容器中的类型时,编译器无法判断它是不是一个类型,它也可能是容器中的一个静态成员,typename就是告诉编译器这是一个类型,编译才可以过。
非类型模板参数
在模板中有两种参数,类型形参和非类型形参。模板中的非类型参数可以当作常量使用,这种参数多用于静态容器,即不需要扩容,使用时指定容器大小的情况。
但是有几点要注意:
1.非类型参数必须是整形,浮点数和类对象以及字符串不允许
2.非类型参数必须在编译器就能确定结果
下面是用非类型模板参数模拟实现静态数组的代码:
template<class T, size_t num = 10>
class arr
{
private:
T _a[num];
size_t _size;
public:
T& operator[](size_t index)
{
return _a[index];
}
const T& operator[](size_t index)const
{
return _a[index];
}
size_t size()const
{
return _size;
}
bool empty()const
{
return _size == 0;
}
};
模板的特化
有的时候模板实例化后在大部分情况下是正确的,但是个别情况下无法正常使用或者需求不同,这就需要我们对个别情况下的模板进行特化,即特殊实例化。
函数模板特化(不支持偏特化)
template<class T1, class T2>
T1 add(const T1 x1, const T2 x2)
{
return x1 + x2;
};
//特化
template<>
int add<int, double>(const int x1, const double x2)
{
return x1 - x2;
};
void test3()
{
cout << add(4, 2.1) << endl;
cout << add(4, 1) << endl;
}
类模板特化
template <class T1, class T2>
class Data
{
private:
T1 d1;
T2 d2;
public:
void print()
{
cout << "Data(T1, T2)" << endl;
}
};
//全特化
template<>
class Data<int, int>
{
private:
int d1;
int d2;
public:
void print()
{
cout << "Data(int, int)" << endl;
}
};
//偏特化,对部分模板参数特化
template<class T2>
class Data<int, T2>
{
private:
int d1;
T2 d2;
public:
void print()
{
cout << "Data(int, T2)" << endl;
}
};
//偏特化,对模板参数进行进一步限制
template<class T1, class T2>
class Data<T1*, T2>
{
private:
T1* d1;
T2 d2;
public:
void print()
{
cout << "Data(T1*, T2)" << endl;
}
};
void test4()
{
Data<double, double> d1;
d1.print();
Data<int, double> d2;
d2.print();
Data<int, int> d3;
d3.print();
Data<int*, double> d4;
d4.print();
}
模板的分离编译
一个程序由许多源文件共同构成,每个源文件分别编译成目标文件,最后链接到一起形成一个可执行程序,这是分离编译模式。
下面的模板分离编译会有什么问题?
com.h
//声明
template <class T>
bool compare(const T& left, const T& right);
com.cpp
//定义
template <class T>
bool compare(const T& left, const T& right)
{
return left < right;
}
test.cpp
#include"com.h"
int main()
{
compare(1, 2);
compare(3, 2);
return 0;
}
com.cpp编译时,函数未实例化,不会生成compare函数,也就没有函数地址;因为调用了compare<int>函数,test.cpp在链接时要寻址,但是找不到compare函数的地址,就会报错。
解决方法:
将函数声明和定义放到一个xxx.hpp或xxx.h文件中即可。