面向对象的继承和多态机制有效提高了程序的可重用性和可扩充性。在程序的可重用性方面,程序员还希望得到更多支持,这时候模板应运而生。
编译器会根据 函数模板自动生成多个函数,用以交换不同类型变量的值。
在 C++ 中,模板分为函数模板和类模板两种。函数模板是用于生成函数的,类模板则是用于生成类的。
16.1 函数模板
函数模板的写法如下:
template <class 类型参数1, class 类型参数2, ...>
返回值类型 模板名(形参表)
{
函数体
}
其中的 class 关键字也可以用 typename 关键字替换,例如:
template <typename 类型参数1, typename 类型参数2, ...>
函数模板看上去就像一个函数。
例如:
template <class T>
void Swap(T & x, T & y)
{
T tmp = x;
x = y;
y = tmp;
}
T 是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。
#include <iostream>
using namespace std;
template<class T>
void Swap(T & x, T & y)
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int n = 1, m = 2;
Swap(n, m); //编译器自动生成 void Swap (int &, int &)函数
double f = 1.2, g = 2.3;
Swap(f, g); //编译器自动生成 void Swap (double &, double &)函数
return 0;
}
编译器在编译到Swap(n, m);时找不到函数 Swap 的定义,但是发现实参 n、m 都是 int 类型的,用 int 类型替换 Swap 模板中的 T 能得到下面的函数:
void Swap (int & x, int & y)
{
int tmp = x;
x = y;
y = tmp;
}
编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。在某些编译器中,模板只有在被实例化时,编译器才会检查其语法正确性。如果程序中写了一个模板却没有用到,那么编译器不会报告这个模板中的语法错误。
编译器对模板进行实例化时,并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。可以
模板名<实际类型参数1, 实际类型参数2, ...>
的方式告诉编译器应该如何实例化模板函数。例如:
#include <iostream>
using namespace std;
template <class T>
T Inc(int n)
{
return 1 + n;
}
int main()
{
cout << Inc<double>(4) / 2;
return 0;
}
Inc<double>(4)指明了此处实例化的模板函数原型应为:
double Inc(double);
编译器不会因为实参 4 是 int 类型,就生成原型为 int Inc(int) 的函数。因此,上面程序输出的结果是 2.5 而非 2。
函数模板中可以有不止一个类型参数。
下面这个函数模板的写法是合法的:
template <class Tl, class T2>
T2 print(T1 argl, T2 arg2)
{
cout << arg1 << " " << arg2 << endl;
return arg2;
}
练习:
设计一个分数类 CFraction,再设计一个名为 MaxElement 的函数模板,能够求数组中最大的元素,并用该模板求一个 CFmction 数组中的最大元素。
#include <iostream>
using namespace std;
template <class T>
T MaxElement(T a[], int size) //size是数组元素个数
{
T tmpMax = a[0];
for (int i = 1; i < size; ++i)
if (tmpMax < a[i])
tmpMax = a[i];
return tmpMax;
}
class CFraction //分数类
{
int numerator; //分子
int denominator; //分母
public:
CFraction(int n, int d) :numerator(n), denominator(d) { };
bool operator <(const CFraction & f) const
{//为避免除法产生的浮点误差,用乘法判断两个分数的大小关系
if (denominator * f.denominator > 0){
return numerator * f.denominator < denominator * f.numerator;
}
else{
return numerator * f.denominator > denominator * f.numerator;
}
}
friend ostream & operator <<(ostream & o, const CFraction & f);
};
ostream & operator <<(ostream & o, const CFraction & f)
{//重载 << 使得分数对象可以通过cout输出
o << f.numerator << "/" << f.denominator; //输出"分子/分母" 形式
return o;
}
int main()
{
int a[5] = { 1,5,12,3,24 };
CFraction f[4] = { CFraction(8,6),CFraction(-8,4),
CFraction(3,2), CFraction(5,6) };
cout << MaxElement(a, 5) << endl;
cout << MaxElement(f, 4) << endl;
return 0;
}
16.2 普通函数或函数模板的匹配顺序
在有多个函数和函数模板名字相同的情况下,一条函数调用语句到底应该被匹配成对哪个函数或哪个模板的调用呢? C++ 编译器遵循以下先后顺序:
- 先找参数完全匹配的普通函数(非由模板实例化得到的函数)。
- 再找参数完全匹配的模板函数。
- 再找实参经过自动类型转换后能够匹配的普通函数。
- 如果上面的都找不到,则报错。
例如:
#include <iostream>
using namespace std;
template <class T>
T Max(T a, T b)
{
cout << "模板1" << endl;
return 0;
}
template<class T, class T2>
T Max(T a, T2 b)
{
cout << "模板2" << endl;
return 0;
}
double Max(double a, double b) {
cout << "普通函数" << endl;
return 0;
}
int main() {
int i = 4, j = 5;
Max(1.2, 3.4); //调用 Max 函数
Max(i, j); //调用第一个Max模板生成的函数
Max(1.2, 3); //调用第二个Max模板生成的函数
return 0;
}
image.png
网友评论