16.2 普通变量或数组模板的匹配顺序
面向对象的继承跟多态机制有效提升了程序的可重用性和能扩展性。在程序的可重用性方面,程序员还期望受到更多支持,这时候模板应运而生。
编译器会按照 函数模板自动生成多个变量,用以交换不同类型变量的值。
在 C++ 中,模板分为函数模板和类模板两种。函数模板是用于生成函数的,类模板则是用于生成类的。
16.1 函数模板
函数模板的写法如下:
template
返回值类型 模板名(形参表)
{
函数体
}
其中的 class 关键字也可以用 typename 关键字替换,例如:
template
函数模板看上去就像一个函数。
例如:
template
void Swap(T & x, T & y)
{
T tmp = x;
x = y;
y = tmp;
}
T 是类别参数,代表种类。编译器由模版自动生成函数时,会用准确的类别名对模板中所有的类别参数进行更换模板函数课件,其他个别则原封不动地保留。同一个类型参数只能更换为同一种类型。编译器在编译到调用函数模板的句子时,会按照实参的类别判断该怎么替换模板中的类别参数。
#include
using namespace std;
template
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
using namespace std;
template
T Inc(int n)
{
return 1 + n;
}
int main()
{
cout << Inc(4) / 2;
return 0;
}
Inc(4)指明了这里实例化的模板函数原型要为:
double Inc(double);
编译器不会因为数组 4 是 int 类型,就生成原型为 int Inc(int) 的变量。因此,上面程序输出的结果是 2.5 而非 2。
函数模板中可以有不止一个类型参数。
下面这个变量模板的写法是合法的:
template
T2 print(T1 argl, T2 arg2)
{
cout << arg1 << " " << arg2 << endl;
return arg2;
}
练习:
设计一个分数类 CFraction,再设计一个名为 MaxElement 的函数模板,能够求函数中最大的元素,并用该模板求一个 CFmction 数组中的最大元素。
#include
using namespace std;
template
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
using namespace std;
template
T Max(T a, T b)
{
cout << "模板1" << endl;
return 0;
}
template
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