重载和模板(Overloads and templates)

重要的事情说三遍:

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

函数重载

在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

模板参数是一系列用逗号分隔的参数。这些参数可以通过指定 classtypename 关键字后跟标识符来成为通用模板类型。然后可以在函数声明中使用这个标识符,就像使用常规类型一样。例如,可以定义一个通用的 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 将是与参数 ab 类型相同的变量,也是函数返回的类型。

在这种特定情况下,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 类型。

非类型模板参数

模板参数不仅可以包括由 classtypename 引入的类型,还可以包括特定类型的表达式:

// 模板参数
#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 的调用本质上调用了两个版本的函数:一个始终乘以二,一个始终乘以三。因此,第二个模板参数需要是常量表达式(不能传递变量)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值