重要的事情说三遍:
函数重载
在C++中,如果函数的参数不同,那么两个不同的函数可以具有相同的名称;要么是参数数量不同,要么是参数的类型不同。例如:
// 函数重载
#include <iostream>
using namespace std;
int operate (int a, int b)
{
return (a*b);
}
double operate (double a, double b)
{
return (a/b);
}
int main ()
{
int x=5,y=2;
double n=5.0,m=2.0;
cout << operate (x,y) << '\n';
cout << operate (n,m) << '\n';
return 0;
}
在这个例子中,有两个名为 operate
的函数,但其中一个有两个 int
类型的参数,而另一个有两个 double
类型的参数。编译器通过检查函数调用时传递的参数类型来确定在每种情况下调用哪个函数。如果用两个 int
参数调用,它将调用具有两个 int
参数的函数;如果用两个 double
参数调用,它将调用具有两个 double
参数的函数。
在这个例子中,两个函数的行为完全不同,int
版本将其参数相乘,而 double
版本将其参数相除。通常这样做并不是一个好主意。一般来说,两个同名的函数至少应该具有相似的行为,但这个例子表明它们完全可能没有相似的行为。两个重载函数(即两个同名函数)有完全不同的定义;从任何意义上来说,它们都是不同的函数,只是碰巧有相同的名称。
注意,一个函数不能仅通过其返回类型重载。至少其参数中的一个必须具有不同的类型。
函数模板
重载函数可能具有相同的定义。例如:
// 函数重载
#include <iostream>
using namespace std;
int sum (int a, int b)
{
return a+b;
}
double sum (double a, double b)
{
return a+b;
}
int main ()
{
cout << sum (10,20) << '\n';
cout << sum (1.0,1.5) << '\n';
return 0;
}
在这里,sum
被重载为不同的参数类型,但具有完全相同的函数体。
函数 sum
可以为很多类型重载,并且它们的函数体可能对所有类型都有意义。对于这种情况,C++ 可以定义具有通用类型的函数,称为 函数模板。定义函数模板的语法与常规函数相同,不同之处在于它前面有一个 template
关键字和一系列用尖括号括起来的模板参数:
template <template-parameters> function-declaration
模板参数是一系列用逗号分隔的参数。这些参数可以通过指定 class
或 typename
关键字后跟标识符来成为通用模板类型。然后可以在函数声明中使用这个标识符,就像使用常规类型一样。例如,可以定义一个通用的 sum
函数:
template <class SomeType>
SomeType sum (SomeType a, SomeType b)
{
return a+b;
}
在模板参数列表中使用 class
关键字还是 typename
关键字没有区别(它们在模板声明中是100%的同义词)。
在上面的代码中,声明 SomeType
(模板参数列表中用尖括号括起来的通用类型)允许在函数定义中像使用任何其他类型一样使用 SomeType
;它可以用作参数类型、返回类型或声明新变量的类型。在所有情况下,它表示将在实例化模板时确定的通用类型。
实例化模板是应用模板使用特定类型或值作为其模板参数来创建函数。这是通过调用 函数模板 来完成的,语法与调用常规函数相同,但指定模板参数用尖括号括起来:
name <template-arguments> (function-arguments)
例如,可以用以下方式调用上述定义的 sum
函数模板:
x = sum<int>(10,20);
函数 sum<int>
只是函数模板 sum
的一种可能实例化。在这种情况下,通过在调用中使用 int
作为模板参数,编译器自动实例化一个版本的 sum
,其中每个出现的 SomeType
都被替换为 int
,就像它被定义为:
int sum (int a, int b)
{
return a+b;
}
让我们看一个实际的例子:
// 函数模板
#include <iostream>
using namespace std;
template <class T>
T sum (T a, T b)
{
T result;
result = a + b;
return result;
}
int main () {
int i=5, j=6, k;
double f=2.0, g=0.5, h;
k=sum<int>(i,j);
h=sum<double>(f,g);
cout << k << '\n';
cout << h << '\n';
return 0;
}
在这个例子中,我们使用 T
作为模板参数名,而不是 SomeType
。这没有区别,T
实际上是一个非常常见的通用类型模板参数名。
在上面的例子中,我们使用了函数模板 sum
两次。第一次使用 int
类型的参数,第二次使用 double
类型的参数。编译器已经实例化并在每次调用时调用了适当版本的函数。
注意如何使用 T
来声明 sum
内的这种(通用)类型的局部变量:
T result;
因此,result
将是与参数 a
和 b
类型相同的变量,也是函数返回的类型。
在这种特定情况下,T
作为 sum
参数的模板参数,编译器甚至能够自动推导数据类型而不必显式指定尖括号内的类型参数。因此,可以将:
k = sum<int> (i,j);
h = sum<double> (f,g);
简单地写成:
k = sum (i,j);
h = sum (f,g);
而不需要尖括号中的类型。当然,这样做的前提是类型必须是明确的。如果用不同类型的参数调用 sum
,编译器可能无法自动推导 T
的类型。
模板是一个强大且灵活的功能。它们可以有多个模板参数,并且函数仍然可以使用常规的非模板类型。例如:
// 函数模板
#include <iostream>
using namespace std;
template <class T, class U>
bool are_equal (T a, U b)
{
return (a==b);
}
int main ()
{
if (are_equal(10,10.0))
cout << "x 和 y 相等\n";
else
cout << "x 和 y 不相等\n";
return 0;
}
注意这个例子在调用 are_equal
时使用了自动模板参数推导:
are_equal(10,10.0)
相当于:
are_equal<int,double>(10,10.0)
没有可能的歧义,因为数字字面量总是具有特定的类型:除非用后缀另有规定,否则整数字面量总是生成 int
类型的值,浮点字面量总是生成 double
类型的值。因此 10
总是 int
类型,10.0
总是 double
类型。
非类型模板参数
模板参数不仅可以包括由 class
或 typename
引入的类型,还可以包括特定类型的表达式:
// 模板参数
#include <iostream>
using namespace std;
template <class T, int N>
T fixed_multiply (T val)
{
return val * N;
}
int main() {
std::cout << fixed_multiply<int,2>(10) << '\n';
std::cout << fixed_multiply<int,3>(10) << '\n';
}
fixed_multiply
函数模板的第二个参数是 int
类型。它看起来就像一个普通的函数参数,并且实际上可以像使用普通函数参数一样使用它。
但存在一个主要区别:模板参数的值在编译时确定,以生成 fixed_multiply
函数的不同实例,因此该参数的值在运行时从未传递:在 main
中的两个对 fixed_multiply
的调用本质上调用了两个版本的函数:一个始终乘以二,一个始终乘以三。因此,第二个模板参数需要是常量表达式(不能传递变量)。