Skip to content

C++ 模板 (第 16 节课)

主要内容:

1. 模板 - 实现泛型编程

用途: 模板为我们提供了泛型编程的能力,允许我们编写可以处理多种数据类型的代码,而无需为每种类型都写一遍代码。利用模板,我们可以定义一个通用的算法或数据结构,然后在编译时根据实际需要,将模板中的类型参数替换成具体的类型,从而生成针对特定类型的代码。

关键点:

  • 类型参数: 使用 typenameclass 关键字声明类型参数,例如 typename T
  • 模板实例化: 编译器在遇到使用模板的代码时,会根据实际传入的类型参数生成具体的代码,这个过程称为模板实例化。
  • 函数查找优先级: 编译器在寻找可调用函数时,会优先寻找完全匹配的函数,如果没有找到,才会尝试使用模板生成匹配的函数。

代码示例:

cpp
#include <iostream>

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

int main() {
  int a = 5, b = 10;
  std::cout << "Max of " << a << " and " << b << " is: " << max(a, b) << std::endl;

  double c = 3.14, d = 2.71;
  std::cout << "Max of " << c << " and " << d << " is: " << max(c, d) << std::endl;
  return 0;
}

讲解: 这个例子定义了一个通用的 max 函数模板,可以比较任意两种类型的值并返回较大的值。在 main 函数中,我们分别使用 intdouble 类型调用 max 函数,编译器会自动生成两个不同版本的 max 函数,分别处理 intdouble 类型。

2. 函数模板

格式:

cpp
template<typename T1, typename T2, ..., typename Tn> 
函数返回类型 函数名称 (参数列表) {
  // 函数执行代码
  // 模板类型可以用在函数的任何位置,比如参数列表中的参数类型,返回值类型,执行部分
}

关键点:

  • 声明: 使用 template 关键字和尖括号 <> 声明模板参数列表。
  • 参数列表: 可以有多个模板参数,用逗号分隔。
  • 实例化: 编译器根据函数调用时的参数类型,自动进行模板实例化。

代码示例:

cpp
#include <iostream>

template <typename T>
T add(T a, T b) {
  return a + b;
}

int main() {
  int x = 5, y = 10;
  std::cout << "Sum of " << x << " and " << y << " is: " << add(x, y) << std::endl;

  double p = 3.14, q = 2.71;
  std::cout << "Sum of " << p << " and " << q << " is: " << add(p, q) << std::endl;
  return 0;
}

讲解: 这个例子定义了一个通用的 add 函数模板,可以将任意两种相同类型的数值相加。编译器会根据调用 add 函数时传入的参数类型,自动生成相应的函数实例。

3. 类模板

格式:

cpp
template<typename T1, typename T2, ..., typename Tn>
class 自定义类型名 {
  // 成员列表 (成员变量, 成员函数)
};

关键点:

  • 声明: 与函数模板类似,使用 template 关键字和尖括号 <> 声明模板参数列表。
  • 定义: 类模板的成员函数一般需要在类外定义,并在定义时也要带上模板参数列表。
  • 实例化: 通过指定具体的类型参数来实例化类模板,例如 MyClass<int> myObject;

代码示例:

cpp
#include <iostream>

template <typename T>
class MyArray {
private:
  T* data;
  int size;

public:
  MyArray(int size) : size(size) {
    data = new T[size];
  }

  ~MyArray() {
    delete[] data;
  }

  void set(int index, T value) {
    data[index] = value;
  }

  T get(int index) {
    return data[index];
  }
};

int main() {
  MyArray<int> intArray(5);
  intArray.set(0, 10);
  std::cout << "Value at index 0: " << intArray.get(0) << std::endl;

  MyArray<double> doubleArray(3);
  doubleArray.set(1, 3.14);
  std::cout << "Value at index 1: " << doubleArray.get(1) << std::endl;
  return 0;
}

讲解: 这个例子定义了一个 MyArray 类模板,可以创建一个存储任意类型的数组。在 main 函数中,我们分别实例化了 MyArray<int>MyArray<double>,创建了存储 int 类型和 double 类型的数组。

4. 成员函数模板

关键点:

  • 定义: 类模板的成员函数也可以是模板函数,这意味着一个成员函数可以处理多种类型的参数。
  • 声明: 在类模板内部声明成员函数模板时,需要再次使用 template 关键字和尖括号 <> 声明模板参数列表。
  • 实例化: 在调用成员函数模板时,编译器会根据传入的参数类型自动进行实例化。

代码示例:

cpp
#include <iostream>

template <typename T>
class MyContainer {
private:
  T value;

public:
  MyContainer(T value) : value(value) {}

  template <typename U>
  void printWith(U prefix) {
    std::cout << prefix << ": " << value << std::endl;
  }
};

int main() {
  MyContainer<int> intContainer(10);
  intContainer.printWith("Integer");

  MyContainer<double> doubleContainer(3.14);
  doubleContainer.printWith("Double");
  doubleContainer.printWith(123); // 可以传入不同类型的参数
  return 0;
}

讲解: 这个例子定义了一个 MyContainer 类模板,其中包含一个成员函数模板 printWith。这个函数可以接受任意类型的参数 prefix,并将其与容器的值一起打印出来。在 main 函数中,我们分别使用字符串和整数作为 prefix 参数调用 printWith 函数,展示了成员函数模板的灵活性。

作业:

  1. 定义一个模板函数sum,使得它可以对于任何数组类型求和 (int, double, std::string),并在 main 函数中调用。

  2. 再写相同功能的非模板函数,各自都运行 1000000 次调用,观察两者的用时表现

    int add(int a, int b)