Chapter 8:Adventures in Functions

对齐第6版 Chapter 8:掌握 inline 函数、引用变量、默认参数、函数重载、重载解析与函数模板,并审计别名和实例化契约。

学习目标

  • 能比较 inline、引用变量、默认参数、函数重载和函数模板各自解决的问题、成本与失败边界
  • 能实现按值、可写引用、const 引用和指针参数,判断复制、别名、可空性、写权限与生命周期
  • 能推导重载候选、转换等级和模板参数推导,复现歧义、悬空引用与不满足模板操作的诊断

机制总览

Chapter 8:Adventures in Functions:机制路径

  1. 1

    为什么函数增强机制不能混成“少写代码”

    内联函数(inline function)处理定义可见性与调用开销优化机会,reference 改变参数是否别名,default argument 改变调用点可省略的实参,overload 让同一名字拥有多个签名,function template 从类型模式生成函数族。五者影响不同边界,组合前应分别写出契约。

  2. 2

    inline 函数允许头文件定义但不保证内联展开

    inline 允许相同函数定义出现在多个翻译单元,只要它们满足同一定义规则;编译器是否把调用替换为函数体,是优化决定。大函数或递归函数标 inline 也可能保留正常调用。

  3. 3

    引用变量是已有对象的别名

    int& alias = value; 绑定后不能改绑到另一个对象,通过 alias 写入就是修改 value。引用语法不提供空状态,仍可能因被引用对象结束生命周期而悬空。

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 8:Adventures in Functions:机制与证据

切换《Chapter 8:Adventures in Functions》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么函数增强机制不能混成“少写代码”

内联函数(inline function)处理定义可见性与调用开销优化机会,reference 改变参数是否别名,default argument 改变调用点可省略的实参,overload 让同一名字拥有多个签名,function template 从类型模式生成函数族。五者影响不同边界,组合前应分别写出契约。

可核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么函数增强机制不能混成“少写代码”」的状态变化。

学完《Chapter 8:Adventures in Functions》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 8:Adventures in Functions:失效与核验

为什么函数增强机制不能混成“少写代码”

典型失效

若只复述「为什么函数增强机制不能混成“少写代码”」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么函数增强机制不能混成“少写代码”」的状态变化。

inline 函数允许头文件定义但不保证内联展开

典型失效

若只复述「inline 函数允许头文件定义但不保证内联展开」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「inline 函数允许头文件定义但不保证内联展开」的状态变化。

引用变量是已有对象的别名

典型失效

若只复述「引用变量是已有对象的别名」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「引用变量是已有对象的别名」的状态变化。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么函数增强机制不能混成“少写代码”

内联函数(inline function)处理定义可见性与调用开销优化机会,reference 改变参数是否别名,default argument 改变调用点可省略的实参,overload 让同一名字拥有多个签名,function template 从类型模式生成函数族。五者影响不同边界,组合前应分别写出契约。

inline 函数允许头文件定义但不保证内联展开

inline 允许相同函数定义出现在多个翻译单元,只要它们满足同一定义规则;编译器是否把调用替换为函数体,是优化决定。大函数或递归函数标 inline 也可能保留正常调用。

inline int square(int value) {
    return value * value;
}

定义通常放入头文件,让每个使用翻译单元可见。若宏或条件编译使各处定义不同,会违反 ODR。先测量热路径,再讨论展开是否改善性能;不应为省一次调用复制大块代码。

引用变量是已有对象的别名

int& alias = value; 绑定后不能改绑到另一个对象,通过 alias 写入就是修改 value。引用语法不提供空状态,仍可能因被引用对象结束生命周期而悬空。

void swap_values(int& left, int& right) {
    int temporary{left};
    left = right;
    right = temporary;
}

可写引用表示调用者对象会被修改,应由函数名字和文档明确;const 引用适合读取大对象且可绑定某些临时值。指针适合可空、数组范围或显式间接语义,不能因引用语法更短就抹掉“可能没有对象”。

默认参数由调用点可见声明提供

默认参数允许调用者省略末尾实参,编译器在调用点补入表达式。默认值通常只在头文件声明一次;定义处重复会造成冲突。提供默认值后,其右侧参数通常也都需要默认值。

void report(std::string_view message, int repeat = 1);
 
report("ready");      // report("ready", 1)
report("retry", 3);

修改库中的默认值不会改变已编译调用点,调用者需重编。默认参数适合稳定、无歧义的常用选择;若参数组合有不同语义,命名函数或配置对象更清楚。

函数重载以参数列表区分候选

同一作用域可声明同名函数,只要参数类型/数量等形成不同签名。返回类型不能单独区分重载,因为调用点可能不使用返回值,编译器无法仅凭期望结果选择。

void show(int value);
void show(double value);
void show(std::string_view value);

解析先形成可行候选,再比较精确匹配、promotion、标准转换等等级。若一个候选对第一个实参更好、另一个对第二个更好,调用可能歧义;加更多 overload 不一定改善 API。

函数模板从类型模式生成函数族

函数模板把类型写成参数,调用时由实参推导 T 并实例化具体函数。模板体对 T 执行的每个操作都必须有效;“任意类型都能用”不是自动保证。

template <class T>
void swap_values(T& left, T& right) {
    T temporary{left};
    left = right;
    right = temporary;
}

该模板隐含 T 可复制构造、可赋值的要求。两个实参推导出的 T 必须一致;intdouble 不会自动共同推导一个 T。现代 C++ 可用 concepts 表达约束,但第6版的关键是看见模板体隐式契约。

overload 与 template 共同参与候选选择

非模板重载和模板实例可同时成为候选。精确的非模板函数通常优先于同等匹配模板;但实际规则还涉及特化程度和转换等级。先预测候选集,再用最小编译实验确认。

函数模板的错误常在实例化某个 T 时才出现。诊断应从“哪个表达式要求 T 支持什么”开始,不要只读最外层几十行模板回溯。为模板写 static_assert 或概念可以把失败移到接口边界。

引用与模板组合会放大生命周期问题

模板参数为 T& 时只能绑定匹配 lvalue,const T& 可接收更多值但只读;返回 T& 仍要求目标对象活得足够久。类型推导不会修复悬空引用,也不会自动区分 owner 与 borrower。

template <class T>
const T& larger(const T& left, const T& right) {
    return left < right ? right : left;
}

若调用 larger(1,2) 并把返回引用保存到调用结束之后,临时对象生命周期成为风险。安全接口可以按值返回小对象,或限制参数为调用者持有的 lvalue 并明确返回借用。

先预测四种候选结果

重载实验先预测:哪些声明可行、每个参数用什么转换、是否有唯一最佳候选、模板 T 如何推导。实际编译后比较诊断;若结果超出预期,先缩小候选集,不要再加一个 overload 掩盖。

三步选择函数增强机制

分步1 / 3

第一步:明确要改变哪个边界

区分定义布局、复制/别名、可省略实参、同名操作或跨类型生成,再选择 inline/reference/default/overload/template。

小结

  • inline 主要影响定义规则并给优化机会,不保证机器调用被展开
  • reference 表达非空别名但仍受生命周期约束,const 引用、可写引用与指针有不同契约
  • 默认参数在调用点补值并要求调用者重编,适合稳定尾部默认项
  • 函数 overload 按参数签名与转换等级选唯一最佳候选,返回类型不能单独区分
  • 函数 template 生成类型族,模板体操作就是隐式约束,推导和实例化不会自动保证语义安全

练习

  1. 问题 1:选择参数。 小 int、只读大 string、需要写回的计数和可选对象分别用什么形式?
  1. 问题 2:推导重载。 show(shortValue) 在 show(int) 与 show(double) 中为何通常选 int?
  1. 问题 3:审计模板。 通用 swap 模板对 T 隐含哪些要求,怎样让失败更靠近接口?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

函数增强机制
改变函数定义、绑定、调用、候选或类型生成边界的机制集合。
inline 函数
允许一致定义跨翻译单元并提供内联机会的函数。
引用变量
初始化后绑定已有对象且不能改绑的别名。
默认参数
调用省略尾部实参时由可见声明补入的表达式。
函数重载
同名不同参数签名并由实参选最佳候选的机制。
函数模板
按类型模式和模板参数生成具体函数的机制。

资料与写作方式声明

本章以C++ Primer Plus, Sixth Edition, Chapter 8 Adventures in Functions权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…