C++学习笔记:函数模版

在编写程序中经常遇到的情况是为了实现大致相同的功能而不得不编写多个函数,而这些函数只是返回类型和形参类型不同,要怎么解决这种问题呢?那就是使用泛型编程。

有时我们想分别为整形和浮点型编写重载函数,而两者的实现本质又相同,这时我们就可以利用函数模版来实现,这里以绝对值函数为例:

template

T absval(T x)
{
  if (x < 0)
    return -x;
  else
    return x;
}

关键就在第一行。关键字template意为以下是一个模版,这里它是一个函数模版定义。尖括号包含一个由逗号分隔的模版形参列表。函数模版就是一个能根据形参类型生成函数的模式。在函数模版定义中,T表示一个类型,它将来可能为任意类型。absval函数的调用者会确定用哪个来替换T。

编译器遇到函数模版定义时会记下该模版,但不会生成任何代码,而直到使用该函数模版时才生成一个真正的函数。流程可以理解为:编译器先读取模版源代码,并用int等模版实参替换模版形参T,然后编译器再编译生成的代码。

这里要注意的是模版实参与模版形参的概念,他们有函数实参与函数形参的概念不同。

在上例中,模版形参是T,而模版实参必须为某个类型。不能把类型作为函数实参传递,但模版不同,实际上程序没有“传递”任何东西。模版的神奇之处在于编译时刻:编译器读取模版定义,当它随后发现对函数模版的函数调用时,它检查函数实参类型,然后根据该函数的实参类型判定模版的实参。随后编译器使用该模版实参替换T,从而生成一个新的函数实例。

模版形参:

有时形参的名字可能比T更加具体,如果模版有多个形参,则各个参数的名字不能相同,因此代码中会出现T之外的名字。如,copy算法是有两个形参的函数模版:一个是输入迭代器,一个是输出迭代器,代码如下:

template
OutputIterator copy(InputIterator start, InputIterator end, OutputIterator result)
{
  for ( ; start != end; ++start, ++result)
    *result = *start;
  return result;
}

模版实参:

模版最让人舒服的地方在于编译器能够自动从函数实参推导出模版实参,不过编译器可不是任何时候都有这个能力,因此有时还要显示的告知编译器。如下面的算法:

template
T min(T a, T b)
{
  if (a < b)
    return a;
  else
    return b;
}

如果两个实参类型相同编译器能够正确推导出需要的类型并正确工作。

int x(10), y(20), z(std::min(x, y));

但如果形参类型不同,则编译器就不知道应该用哪个类型作为实参了。

int x(10);

long y(20);

std::cout << std::min(x, y); //报错

这是因为编译器在处理模版时不会进行自动类型转换,这与我们手动编写函数的情况不同(会自动转换)。

所以我们要指明模版形参类型来知道编译器。将第三行改为

std::cout << std::min(x, y);

还有一种情况:先看下例:

#include
#include
#include

template
U input_sum(std::istream& in)
{
  T x;
  U sum(0);
  while (in >> x)
    sum += x;
  return sum;
}

int main()
{
  long sum(input_sum(std::cin));
  std::cout << sum << '\n';
}

此例中函数实参并未使用数据和累加操作符类型,所以编译器不能推导出模版形参,这也需要显示声明(加粗处)。

关于声明与定义:

我们知道在编写函数的时候,我们一般将函数的声明放到一个.hpp文件中,而将函数的定义放到一个同名的.cpp文件中,但在使用一个模版函数之前,编译器不仅需要函数模版声明,通常还需要完整的函数模版定义。也就是说,如果在一个头文件中定义了一个模版,那么该文件必须包含该函数模版的内容。当然也有其他技术可以实现将模版的声明与定义放到不同的文件中,但要想正确使用还有点困难。


你可能感兴趣的:(c\c++)