在 C++ 中,如果参数不同,两个不同的函数可以具有相同的名称;
要么是因为它们具有不同数量的参数,要么是因为它们的任意一个参数属于不同类型。例如:
// overloading functions
#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;
}
|
10
2.5 |
在本例中,有两个同名函数operation,但其中一个具有两个int类型的形参,而另一个具有double类型的形参。
当函数被调用时,编译器通过检查参数传递过来的类型来判断调用哪个.
如果调用时传来两个int类型参数,它会调用有两个int形参的函数,
如果它被两个double类型调用,它就调用有两个double类型的那个.
在这个例子中,两个函数具有不同的作用,int版本将参数相乘,而double版本将参数相除。
一般不这样用。两个具有相同名称的函数通常应该至少具有类似的作用,
但是这个示例是为了说明它们完全可以不具有类似的作用。
两个重载函数(即两个同名函数)有完全不同的定义;
它们都是不同的函数,只是碰巧有相同的名称。
请注意,函数不能由其返回值类型判定重载。函数重载的参数中至少有一个必须是不同的类型。
重载函数可能具有相同的定义。例如:
// overloaded functions
#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;
}
|
30
2.5 |
这里,sum函数用不同的参数类型重载,但函数体完全相同。
函数sum可以重载很多类型,并且所有类型都有相同的函数体是有意义的。
对于这种情况,c++能够用泛型类型定义函数,称为函数模板。
定义函数模板遵循与普通函数相同的语法,但是它前面有template关键字和一系列用尖括号<>括起来的模板形参:
template <template-parameters> function-declaration
模板参数是用逗号分隔的一系列参数。通过定义class或typename关键字后面的标识符,
这些标识符就是泛型模板类型。然后可以在函数声明中使用该标识符,
就像它是常规类型一样。例如,一个泛型和函数可以定义为:
template <class SomeType>
SomeType sum (SomeType a, SomeType b)
{
return a+b;
}
|
在模板参数列表中使用关键字class或关键字typename指定泛型类型没有什么区别(它们在模板声明中是100%同义词)。
在上面的代码中,声明SomeType(尖括号括起来的模板形参中的泛型类型)
允许SomeType在函数定义的任何地方使用,就像其他类型一样;
它可以用作参数的类型、返回值类型或声明此类型的新变量。
在这些情况下,它都表示一个泛型类型,该泛型类型将在模板实例化时确定是那种类型。
实例化一个模板就是应用该模板来创建一个使用特定类型或模板参数值的函数。
这可以通过调用函数模板来实现,其语法与调用普通函数相同,
只是需要指定用尖括号括起来的模板参数:
name <template-arguments> (function-arguments)
例如,上面定义的sum函数模板可以用以下方式调用:
函数sum<int>只是函数模板sum的可能实例化之一。
在这种情况下,通过在调用中使用int作为模板实参,编译器自动实例化sum的一个版本,
其中SomeType的每次出现都被int替换,就像它被定义为:
int sum (int a, int b)
{
return a+b;
} |
让我们来看一个实际的例子:
// function template
#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;
}
|
11
2.5 |
在本例中,我们使用T作为模板参数名,而不是SomeType。
这没有什么区别,T实际上是泛型类型的一个相当常见的模板参数名。
在上面的例子中,我们使用了两次函数模板sum。第一次是int型实参,第二次是double型实参。
编译器实例化并调用了适当版本的函数。
请注意如何使用T在sum中声明该(泛型)类型的局部变量:
因此,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的类型。
模板是一个功能强大且通用的特性。它们可以有多个模板参数,
并且该函数仍然可以使用常规的非模板类型。例如:
// function templates
#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 and y are equal\n";
else
cout << "x and y are not equal\n";
return 0;
}
|
1x and y are equal |
注意,这个例子在调用are_equal时使用了自动的模板参数推断:
等于
are_equal<int,double>(10,10.0) |
这没有歧义,因为数字字面值总是特定类型的:
除非带有后缀加以指定,整数字面值是int类型的值,而浮点字面值是double类型的值。
因此,10总是int类型,而10.0总是double类型。
模板形参不仅可以使用由class或typename定义的类型,还可以使用特定类型的表达式:
// template arguments
#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';
}
|
20
30 |
fixed_multiply函数模板的第二个实参是int类型。
它看起来就像一个普通的函数参数,实际也可以同样使用。
但存在着很大的差异:模板参数的值是在编译时确定的,以生成fixed_multiply不同的函数实例化,
因此,在运行时,该参数的值永远不会传递:
在main函数中对fixed_multiply的两个调用实际上调用了函数的两个版本:一个总是乘以2,
一个总是乘以三个。因此,第二个模板参数需要的是常量表达式(不能传递变量)。