20、模板初探

厨子大约 5 分钟C++C++基础编程程序厨

学C++的朋友应该都知道多态,多态分为运行时多态和编译时多态,其中模板可以粗浅理解为是编译时多态的其中一种方式。

模板是一种通用的代码框架,允许在编写代码时使用占位符(通常是类型或值),在实际使用时再指定具体的类型或值。使用模板进行泛型编程,可以认为是用一种无关于特定类型的方式来编写代码。

模板大体分为函数模板与类模板,其中还有些主要的特性,比如全特化、偏特化、SFINAE等。

模板的声明以关键字template开头,后跟模板参数列表。模板参数可以是类型参数(typenameclass)或非类型参数(如整数、指针等)。

这篇文章主要介绍下函数模板与类模板的使用。

函数模板

函数模板与类模板的声明比较类似,都是使用template关键字,还是直接看示例代码吧。

示例

template <typename T>
T Max(T const& a, T const& b) {
  return a > b ? a : b;
}
int main() {
  std::cout << Max(1, 2) << std::endl;
  std::cout << Max(1.2, 2.3) << std::endl;
  std::cout << Max(std::string("1.2"), std::string("2.3")) << std::endl;
}

T起到一个类似占位符的作用,使用时传入的是什么类型,它就会规定T就是这个类型。

template用于声明这是一个模板,typename用来表明T是一个泛型类型。也有很多模板的声明不是使用typename,而是使用的class,区别不大。

大体语法如下:

template<typename type>
ret-type funcname(parameter list) {
  // function
}

类模板

再举一个类模板的示例:

template <typename T>
class Queue {
 public:
  void Push(const T& t) { elems.emplace_back(t); }
  T& Front() { return elems.front(); }
  void PopFront() { elems.erase(elems.begin()); }
  size_t Size() { elems.size(); }
  bool Empty() { return elems.empty(); }
 private:
  std::vector<T> elems;
};
int main() {
  Queue<int> q;
  q.Push(1);
  q.Push(2);
  while (!q.Empty()) {
    std::cout << q.Front() << std::endl;
    q.PopFront();
  }
  std::cout << "=========== \n";
  Queue<float> q2;
  q2.Push(1.1);
  q2.Push(2.1);
  while (!q2.Empty()) {
    std::cout << q2.Front() << std::endl;
    q2.PopFront();
  }
}

觉得上面的示例复杂,可以看下面,这个示例可能更简单一些:

template <typename P, typename Q>
class A {
 public:
  A(P p, Q q) : p_(p), q_(q) {}
  void Print() { std::cout << "p " << p_ << " q " << q_ << std::endl; }
 private:
  P p_;
  Q q_;
};
int main() {
  A<int, double> a(1, 3.3);
  a.Print();
  A<int, std::string> b(2, "dsds");
  b.Print();
}

这里可以看到,类模板的格式类似于下面这样:

template <typename type>
class classname {};

再看下默认模板参数:

就像函数可以有默认参数一样,模板也可以有默认参数,比如:

#include <iostream>
#include <string>
template <typename P, typename Q = int>
class A {
public:
  A(P p, Q q) : p_(p), q_(q) {}
  void Print() { std::cout << "p " << p_ << " q " << q_ << std::endl; }
private:
  P p_;
  Q q_;
};
int main() {
  A<int, double> a(1, 3.3);
  a.Print();
  A<int, std::string> b(2, "dsds");
  b.Print();
  A<int> c(1, 2);
  c.Print();
}

模板实例化

模板的实例化是指编译器根据模板生成具体的函数或类的过程。模板实例化可以是隐式的(由编译器自动完成)或显式的(由程序员指定)。

隐式实例化

int a = max(3, 5); // 编译器隐式实例化max<int>
double b = max(3.5, 2.5); // 编译器隐式实例化max<double>

显式实例化

template int max<int>(int, int); // 显式实例化max<int>
template double max<double>(double, double); // 显式实例化max<double>

模板特化

模板的特化是指为特定的类型或值提供特殊的实现。特化分为全特化和偏特化。

  • 全特化是指为模板的所有参数指定具体的类型或值。
  • 偏特化是指为模板的部分参数指定具体的类型或值。

看模板全特化和偏特化的示例代码。

函数模板全特化

template <typename T>
T Max(T const& a, T const& b) {
  std::cout << "Max a b \n";
  return a > b ? a : b;
}
template <>
int Max(int const& a, int const& b) {
  std::cout << "Max a b full specialization \n";
  return a > b ? a : b;
}

int main() {
  std::cout << Max(1, 2) << std::endl;
  std::cout << Max(1.2, 2.3) << std::endl;
  std::cout << Max(std::string("1.2"), std::string("2.3"));
}

输出:

Max a b full specialization
2
Max a b
2.3
Max a b
2.3

类模板全特化

template <>
class A<std::string, std::string> {
public:
  A(std::string p, std::string q) : p_(p), q_(q) {}
  void Print() { std::cout << "std::string p " << p_ << " q " << q_ << std::endl; }
private:
  std::string p_;
  std::string q_;
};

int main() {
  A<int, double> a(1, 3.3);
  a.Print();
  A<int, std::string> b(2, "dsds");
  b.Print();
  A<std::string, std::string> c("hello", "dsds");
  c.Print();
}

类模板偏特化

template <typename P>
class A<P, std::string> {
public:
  A(P p, std::string q) : p_(p), q_(q) {}
  void Print() { std::cout << "partial specialization p " << p_ << " q " << q_ << std::endl; }
private:
  P p_;
  std::string q_;
};

注意:

推荐阅读

参考链接

练习

  1. 实现一个通用的Pair类模板,包含两个成员变量firstsecond,并提供构造函数和访问方法。
  2. 实现一个通用的Stack类模板,支持pushpoptop操作。
  3. 实现一个通用的sort函数模板,支持对任意类型的数组进行排序。