第一章:函数模板

2020-12-20  本文已影响0人  找不到工作

函数模板是指被参数化的模板,可以代表一类函数。

1.1 初识函数模板

函数模板和普通函数看起来很相似,但是部分元素是未确定的。

1.1.1 定义模板

下面定义了从两个值中返回最大值的函数。

template<typename T>
T max(T a, T b)
{
    // if b < a then yield a else yield b 
    return b < a ? a : b;
}

我们使用

template<typename T>

声明了模板化的参数是T

由于历史原因,你可以用 classtypename 定义类型参数,但是 struct 不行。推荐使用 typename 避免歧义。

1.1.2 使用模板

通过以下程序来使用模板:

#include "max1.hpp"
#include <iostream>
#include <string>

int main()
{
    int i = 42;
    std::cout << "max(7,i):      " << ::max(7,i) << '\n';

    double f1 = 3.4; double f2 = -6.7;
    std::cout << "max(f1,f2):    " << ::max(f1,f2) << '\n';

    std::string s1 = "mathematics"; std::string s2 = "math";
    std::cout << "max(s1,s2):    " << ::max(s1,s2) << '\n';
}

我们可以看到分别对 int, float, std::string 三个类型使用了模板。输出为:

max(7,i):      42
max(f1,f2):    3.4
max(s1,s2):    mathematics

此外,我们通过使用 ::max 指明了 max 是在全局 namespace 的max,而非 std::max

模板并非被编译成一种能够处理所有类型的函数,而是对每个使用模板的类型都生成一个函数。因此,对于 int 的调用,在编译时将 T 替换为 int,编译了如下函数:

int max (int a, int b)
{
    return b < a ? a : b;
}

类似的,其他两个调用相当于用 floatstd::string 替换了 T

这个过程称为模板的“实例化”。

1.1.3 两步翻译

如果实例化一个模板不支持的类型,会引起编译错误。模板是通过两个步骤编译的:

  1. “定义”阶段,无视模板参数 T,检查模板语法
  1. “实例化”阶段,再次检查,包括依赖 T 的部分

例如:

template<typename T>
void foo(T t)
{
   undeclared();        // first-phase compile-time error if undeclared() unknown
   undeclared(t);        // second-phase compile-time error if undeclared(T) unknown
   static_assert(sizeof(int) > 10,          // always fails if sizeof(int)<=10
                 "int too small");
   static_assert(sizeof(T) > 10,            //fails if instantiated for T with size <=10
                 "T too small");
}

编译和链接

两步翻译引出一个处理模板时的难题:当使用的函数模板需要实例化时,编译器需要看到模板的实现。

对于普通的函数,编译和链接是分开的。只需要声明函数就足够编译了,而函数模板则需要看到实现。我们会在第九章讨论如何解决这个问题,为了简便,我们教程中都会将模板的实现放在头文件里

1.2 模板类型推断

当我们调用函数模板时,T 是由我们传入的参数类型决定的。

类型推断中的类型转换

自动类型转换在类型推断中的限制:

例如:

template<typename T>
T max (T a, T b);
…
int const c = 42;
max(i, c);            // OK: T is deduced as int
max(c, c);            // OK: T is deduced as int
int& ir = i;
max(i, ir);          // OK: T is deduced as int
int arr[4];
foo(&i, arr);        // OK: T is deduced as int*

然而,如果需要类型转换,例如 intfloat,就失败。例如:

max(4, 7.2);        // ERROR: T can be deduced as int or double
std::string s;
foo("hello", s);    //ERROR: T can be deduced as char const[6] or std::string

我们需要提前处理好类型再调用。

1.3 多个模板参数

如果我们确实需要比较不同类型的参数,那我们可能需要写如下代码

template<typename T1, typename T2>
T1 max (T1 a, T2 b)
{
    return b < a ? a : b; }
…
auto m = ::max(4, 7.2);        // OK, but type of first argument defines 

但是这个返回值显然很奇怪,以上的例子会返回 7(int) 而非 7.2(float)。

从 C++14 开始,我们可以让编译器推断返回类型,于是可以更优美地实现:

template<typename T1, typename T2>
auto max (T1 a, T2 b)
{
  return b < a ? a : b;
}

有时我们希望有同样类型的返回值,这时可以使用 std::common_type<>::type 返回多个类型中最 general 的类型。例如:

#include <type_traits>

template<typename T1, typename T2>
std::common_type_t<T1,T2> max (T1 a, T2 b)
{
  return b < a ? a : b;
}

(吐槽:所以为什么不一开始就转成一个类型,搞这么复杂)

1.4 默认模板参数

不常用,略过

1.5 模板函数重载

就像普通函数,模板函数也能被 overload。

// maximum of two int values:
int max (int a, int b)
{
  return b < a ? a : b;
}

// maximum of two values of any type:
template<typename T>
T max (T a, T b)
{
  return b < a ? a : b;
}

int main()
{
  ::max(7, 42);                // calls the nontemplate for two ints 
  ::max(7.0, 42.0);            // calls max<double> (by argument deduction)
  ::max(’a’, ’b’);              //calls max<char> (by argument deduction) 
  ::max<>(7, 42);              // calls max<int> (by argument deduction) 
  ::max<double>(7, 42);        // calls max<double> (no argument deduction)
  ::max(’a’, 42.7);             //calls the nontemplate for two ints
}

可以看到,规则有:

我们可以利用这个原则,优化对于指针类型或者const char* 类型的比较。

#include   <cstring>
#include   <string>

// maximum of two values of any type:
template<typename    T>
T max (T a, T b)
{
   return      b < a ? a : b;
}

// maximum of two pointers:
template<typename    T>
T* max (T* a, T* b)
{
   return       *b < *a   ? a : b;
}

// maximum of two C-strings:
char const* max (char   const* a,   char const* b)
{
   return       std::strcmp(b,a) < 0   ? a : b;
}

int   main ()
{
   int a = 7;
   int b = 42;
   auto m1 = ::max(a,b);         // max() for two values of type int

   std::string s1 =   "hey"; "
   std::string s2 =   "you"; "
   auto m2 = ::max(s1,s2);       // max() for two values of type std::string

   int* p1 = &b;
   int* p2 = &a;
   auto m3 = ::max(p1,p2);       // max() for two pointers

   char const* x =  hello"; "
   char const* y =  "world"; "
   auto m4 = ::max(x,y);         // max() for two C-strings
 }
上一篇下一篇

猜你喜欢

热点阅读