c++模板FAQ

2019-04-25  本文已影响0人  zevolv

c++模板编程可能会遇到的一些问题,挺折腾的,这里看到一个很不错的网址。本文只是转译的,如果有任何问题,请及时联系我删除。
原文地址:https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl

模板

模板提出来的意图是什么?

模板就像饼干模具一样,可以制作出差不多形状的饼干(不管这些饼干是什么面团做的,都可以被制作的差不多样子)。同理,模板类就是制作一系列基本功能一致的类,模板函数就是制作一系列功能一致的函数。

模板类通常用于创建一些安全的容器(这个用途仅仅是模板的一点点皮毛)

类模板的语言是怎么样的?

看下下面是这个元素为int类型的Array类

// This would go into a header file such as "Array.h"
class Array {
public:
  Array(int len=10)                  : len_(len), data_(new int[len]) { }
 ~Array()                            { delete[] data_; }
  int len() const                    { return len_;     }
  const int& operator[](int i) const { return data_[check(i)]; }  // Subscript operators often come in pairs
  int&       operator[](int i)       { return data_[check(i)]; }  // Subscript operators often come in pairs
  Array(const Array&);
  Array& operator= (const Array&);
private:
  int  len_;
  int* data_;
  int  check(int i) const
    {
      if (i < 0 || i >= len_)
        throw BoundsViol("Array", i, len_);
      return i;
    }
};

重复写出元素类型为float,double,string...等等的Array类会让人觉得无聊和枯燥。所以我们在定义Array前面添加 template<typename T>(这个 T 可以作为任何的类型,但是在下面的例子的使用却是通用的,)。所以我们就用了T来取代了int,float,或者class ,Array<T> 就是一个指向Array<int> , Array<float> 等的模板

// This would go into a header file such as "Array.h"
template<typename T>
class Array {
public:
  Array(int len=10)                : len_(len), data_(new T[len]) { }
 ~Array()                          { delete[] data_; }
  int len() const                  { return len_;     }
  const T& operator[](int i) const { return data_[check(i)]; }
  T&       operator[](int i)       { return data_[check(i)]; }
  Array(const Array<T>&);
  Array(Array<T>&&);
  Array<T>& operator= (const Array<T>&);
  Array<T>& operator= (Array<T>&&);
private:
  int len_;
  T*  data_;
  int check(int i) const {
    assert(i >= 0 && i < len_);
    return i;
  }
};

模板类和正常的类一样,你可以在类的外面写函数

template<typename T>
class Array {
public:
  int len() const;
  // ...
};

template<typename T>
inline      // See below if you want to make this non-inline
int Array<T>::len() const
{
  // ...
}

模板类需要在实例化的时候显式的声明类型,模板函数则不用。

int main()
{
  Array<int>           ai;
  Array<float>         af;
  Array<char*>         ac;
  Array<std::string>   as;
  Array<Array<int>>    aai;
  // ...
}

上面例子的写法需要注意一下,在c++11 以前的版本中,'>>'这个双大于号的写法是需要最少一个空格的,没有空格的话就会被当成右移操作。现在你应该庆幸出了c++11版本。

函数的模板是怎么样的

看下面的有两个参数的交换函数的例子

void swap(int& x, int& y)
{
  int tmp = x;
  x = y;
  y = tmp;
}

如果我们需要交换float,long,String,Set等等,我们需要把这些都写上实现函数的重载。为了让电脑来做这些重复无聊的事情,因此有了函数模板。

template<typename T>
void swap(T& x, T& y)
{
  T tmp = x;
  x = y;
  y = tmp;
}

每次我们用swap函数的时候都会给一对同类型的参数,编译器会直接跳到定义上面的模板函数然后实例化一个“模板函数”出来,模板函数由于已经实例化了所以不需要显式的声明类型,编译器会自动寻找到合适的函数并执行。

int main()
{
  int         i,j;  /*...*/  swap(i,j);  // Instantiates a swap for int
  float       a,b;  /*...*/  swap(a,b);  // Instantiates a swap for float
  char        c,d;  /*...*/  swap(c,d);  // Instantiates a swap for char
  std::string s,t;  /*...*/  swap(s,t);  // Instantiates a swap for std::string
  // ...
}

注意:一个“模板函数”是一个函数模板的实例化。
但有些时候,你也需要显式的声明你要使用的变量。如下。

在模板中我需要怎么选择我需要调用的函数?

当你调用模板函数的时候,编译器会尝试着推出你的模板类型,大多时候都能推出来,但是有时候你可能需要做一些工作去帮助编译器推导出正确的类型,因为有时候编译器推导出的类型会有问题。
举个例子,你调用了一个没有参数的模板函数,这个时候编译器就没办法帮你选一个正确的模板了,因为你没有参数,就像野指针一样,没得内存,也没得对象。在类似的例子中你可能需要告诉编译器去实例化什么样的函数给你调用。

这里有个简单的例子,模板参数没有出现在参数列表里面,这个例子里面,编译器就不能决定要调用什么样的模板。

template<typename T>
void f()
{
  // ...
}

调用的时候你可以这样

#include <string>

void sample()
{
  f<int>();          // type T will be int in this call
  f<std::string>();  // type T will be std::string in this call
}

这里给了一个有参数的函数模板,编译器就可以正常的识别出正确的参数和选择正确的函数。

template<typename T>
void g(T x)
{
  // ...
}

如果你想强制使用一个参数让编译器去正确识别的话可以用上面这个例子。如果你调用g(42),你实际调用了g<int>(42),如果你想把42当做long的类型,你需要这样使用 g<long>(42).你也可以显式的声明下你的参数比如 g(long(42)) 或者g(42L) 但是这样就不是我们例子的初衷了。

如果你调用 g("xyz") 你就会实际调用了g<char>(char),但是你想调用std::string 作为参数的时候,你需要显式调用g<std::string>("xyz")。类似的你也可以调用g(std::string("xyz")),但是这个也不是这里例子的初衷。

有时候你可能在一个含有两个同类型的参数的模板函数中传入了不同的参数类型,

template<typename T>
void g(T x, T y);

int m = 0;
long n = 1;
g(m, n);

由于m和n有不同的类型,所以编译器不能识别出正确的类型,需要我们告诉编译器

template<typename T>
void g(T x, T y);

int m = 0;
long n = 1;
g<int>(m, n);

什么是“参数化的类型”

另外的说法就是模板。
一个参数化的类型就是一个类型里面包含了另外一个参数或者类型,就像List<int>就是List里面包含了int类型。

啥是泛型

也可以说是模板类型。
注意不要和通用类型搞混了,泛型就是模板类型。

我的模板类型在传入不同类型需要做不同的事情,我应该怎么去实现它?

在展示这个之前,让我们看看你有没有搬石头砸过自己的脚。函数的行为在coder面前有区别吗?函数在本质上有很明显的区别吗?如果你觉得有,那么你就有些问题,甚至没有理解到模板,你可能需要用不同的函数名和不同的函数来实现你的功能,而不是模板和重载。举个例子,如果把int放入到容器里面并且排序,对std::string则拿出容器,并且不排序,这两个事情的行为方式明显不一样的才需要有不同的名字和函数。

然后函数行为方式总会有一些区别的,我们需要处理这些却别。下面的例子只是一个概念,不是c++

template<typename T>
void foo(const T& x)
{
  switch (typeof(T)) {  // Conceptual only; not C++
    case int:
      // ...implementation details when T is int
      break;

    case std::string:
      // ...implementation details when T is std::string
      break;

    default:
      // ...implementation details when T is neither int nor std::string
      break;
  }
}

一旦你用了上面的模板,switch函数会把代码分为几个函数,一旦你是用第一次之后,不管你之后的模板传入类型是什么,都将是你使用第一次的函数。

template<typename T>
void foo(const T& x)
{
  // ...implementation details when T is neither int nor std::string
}

下面是两个分开的例子

template<>
void foo<int>(const int& x)
{
  // ...implementation details when T is int
}
template<>
void foo<std::string>(const std::string& x)
{
  // ...implementation details when T is std::string
}

这个分开的定义编译器才会帮你选择正确的类型。

未完待续.....

有问题请发email 到 13546467573@qq.com或者评论。如果发现问题欢迎指出。

上一篇下一篇

猜你喜欢

热点阅读