cs106l-lecture-4-2-templates-and-functions

Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming


🎮 CS106L Lecture 4.2 — Templates and Functions

🏛 课程:Stanford CS106L — Standard C++ Programming Language

📍 主题:模板与函数(Templates and Functions)

🎯 目标:理解模板(Template)的工作原理、类型推导机制、谓词与 lambda 的使用方式,以及 C++20 Concepts 的改进思想。


在上一讲中我们学习了:

“算法是独立于容器的逻辑。

迭代器是算法与容器的桥梁。”

但——

算法如何做到对“任意类型”都能工作?

答案就是 模板(Templates)

📘 模板是 C++ 泛型编程的基础。

它让函数或类在编译期“生成”适合的类型版本。


Template|模板

Type Deduction|类型推导

Predicate|谓词(函数式条件)

Lambda|匿名函数

Concept|概念约束

Compile-time Polymorphism|编译期多态


“模板让 C++ 成为会‘变形’的语言。

你写一次逻辑,它在编译期为每种类型生成最优代码。”



假设我们想写一个函数来返回两个数中较大的一个:

📦 示例 1:类型不泛化

int maxInt(int a, int b) {
    return (a > b) ? a : b;
}
double maxDouble(double a, double b) {
    return (a > b) ? a : b;
}

问题:重复!

📦 改进:使用模板

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

🧩 讲解:

  • template <typename T> 定义了类型参数

  • 编译器在调用时自动推导 T 类型

  • 即:myMax(3, 7) → 生成 int 版本;myMax(2.5, 9.1) → 生成 double 版本

    📤 输出: 7, 9.1


C++ 会在调用时根据参数自动决定模板类型。

📦 示例 2:类型推导

std::cout << myMax(10, 42) << "\n";      // 推导为 int
std::cout << myMax(3.14, 2.71) << "\n";  // 推导为 double
std::cout << myMax('A', 'B') << "\n";    // 推导为 char

⚠️ 若类型不一致,会报错:

myMax(10, 3.14); // ❌ 类型不匹配

📦 解决方案:显式指定模板类型

myMax<double>(10, 3.14); // ✅ 强制使用 double

🧠 总结:

  • 编译期生成类型安全版本
  • 无运行时开销
  • 提升可复用性

📦 示例 3:函数模板与普通函数共存

template <typename T>
void printType(T x) {
    std::cout << "Template version\n";
}
void printType(int x) {
    std::cout << "Non-template version\n";
}

printType(5);     // 调用非模板版本
printType(3.14);  // 调用模板版本

🧩 讲解:

  • 若普通函数与模板都匹配 → 优先选择普通函数。

    📤 输出:

Non-template version
Template version

谓词是“返回布尔值的函数”。

常用于算法中的条件筛选。

📦 示例 4:谓词函数

bool isEven(int x) {
    return x % 2 == 0;
}

std::vector<int> v = {1, 2, 3, 4, 5, 6};
int cnt = std::count_if(v.begin(), v.end(), isEven);
std::cout << cnt;

📤 输出: 3

🧩 讲解:

  • count_if 第三个参数是一个函数指针
  • 函数模板允许将“逻辑条件”以函数形式传入算法
  • 泛化行为的核心体现

C++11 引入 lambda 表达式,可直接在调用点定义小函数。

📦 示例 5:Lambda 替代谓词

int cnt = std::count_if(v.begin(), v.end(),
                        [](int x){ return x % 2 == 0; });
std::cout << cnt;

📤 输出: 3

🧩 讲解:

  • [] → 捕获列表
  • () → 参数
  • {} → 函数体
  • 可在局部作用域中定义内联函数

📦 Lambda 捕获示例

int threshold = 4;
std::for_each(v.begin(), v.end(),
              [threshold](int x){
                  if (x > threshold) std::cout << x << " ";
              });

📤 输出: 5 6

🧩 捕获机制说明:

  • [=] 捕获所有外部变量(按值)
  • [&] 捕获所有外部变量(按引用)
  • [this] 捕获当前类实例

📦 示例 6:模板算法 + Lambda

template <typename T, typename Func>
void forEach(const std::vector<T>& v, Func fn) {
    for (auto& elem : v) fn(elem);
}

std::vector<int> nums = {1, 2, 3};
forEach(nums, [](int n){ std::cout << n * n << " "; });

📤 输出: 1 4 9

🧩 讲解:

模板不仅能接收类型参数,也能接收“函数行为”。

函数式泛型编程(Functional Generic Programming)


模板报错通常非常长,因为:

  • 错误在“实例化阶段”才出现;
  • 编译器要展开模板定义。

📦 错误示例:

template <typename T>
void printVector(const T& container) {
    for (auto x : container) std::cout << x << " ";
}

printVector(42); // ❌ 不是容器

🧩 错误信息会类似:

“no matching begin()/end() for int”

💡 解决思路:

  • 明确模板期望的接口(如 begin() / end()
  • 在错误提示中关注“instantiation”关键字

C++20 引入 Concepts,让模板函数可以明确指定参数能力。

📦 示例 7:Concepts 限定

#include <concepts>

template <std::integral T>
T add(T a, T b) {
    return a + b;
}

std::cout << add(3, 4);      // ✅ OK
std::cout << add(3.14, 2.7); // ❌ 编译错误

🧩 讲解:

  • std::integral 限定参数必须是整数类型

  • 错误信息更清晰:

    “template constraint not satisfied: requires integral type.”

📦 自定义 Concept

template <typename T>
concept Addable = requires(T a, T b) { a + b; };

template <Addable T>
T myAdd(T a, T b) { return a + b; }

🧩 Concept 使模板函数接口显式化,减少调试成本。


“C++ 的模板不是语法糖,而是编译期多态。”

区别于 OOP 的运行时多态(virtual functions),

模板实现的是编译期多态:

  • 编译时生成类型安全版本
  • 无运行时开销
  • 更高性能

“模板生万象,编译期多态强;

Lambda 当谓词,概念显接口。”


练习 1:泛型 Max 函数

实现 myMax,能对任意类型比较返回最大值。

练习 2:Lambda 过滤器

std::copy_if + lambda 筛选偶数到新容器中。

练习 3:Concept 限定模板

写一个只接受浮点类型的 average() 函数。

练习 4:函数模板 + 谓词组合

用模板写一个 filterVector(),接受任意容器与谓词函数。


  • 模板代码应放在 .h 文件中(编译期展开)。
  • 避免模板嵌套过深(调试困难)。
  • 使用 concepts 明确接口需求。
  • Lambda 捕获外部变量时要注意作用域与生命周期。
  • 模板实例化过多会增加编译时间,可用 explicit instantiation 优化。

主题要点示例
模板函数编译期生成特化版本template<typename T>
类型推导自动确定模板类型myMax(3,4)
谓词与算法逻辑条件函数指针count_if(v, pred)
Lambda局部匿名函数[](int x){return x>0;}
Concepts模板参数约束template<std::integral T>

“模板让 C++ 在性能与抽象之间找到平衡,

既能表达思想,又能贴近机器。”