Chapter 16:The string Class and the Standard Template Library

对齐第6版 Chapter 16:掌握 string class、smart pointer templates、STL containers、iterator、generic algorithms、functors 与 valarray。

学习目标

  • 能实现 std::string 的构造、拼接、查找、子串与输入边界,并解释 size/capacity 和 C 字符串桥接
  • 能比较 unique_ptrshared_ptrweak_ptr 与裸指针的 owner、复制、释放和借用契约
  • 能设计 STL container/iterator/algorithm/functor 管线,复现迭代器失效、排序前置条件和 valarray 逐元素运算

机制总览

Chapter 16:The string Class and the Standard Template Library:机制路径

  1. 1

    为什么标准库的价值是契约组合而不只是少写代码

    标准库把常见资源和算法装进具有复制、生命周期、范围和失败语义的类型。 string 不只是可增长字符数组;container 不只是数据盒;algorithm 也不只是现成循环。它们通过迭代器和 callable contract 组合,让同一算法作用于多种表示。

  2. 2

    string class 把字符、长度与资源绑成值

    string 类( std::string )拥有字符序列并保存长度,支持复制、移动和自动释放。它可以包含空字符,因此 size() 才是完整长度证据; c str() 提供以空字符结尾的临时观察指针,用于 C API,但修改 string 后指针可能失效。

  3. 3

    查找和子串要把 npos 当作显式分支

    find() 失败返回 std::string::npos ,不能把它当普通索引继续加一。

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

章级决策实验

Chapter 16:The string Class and the Standard Template Library:机制与证据

切换《Chapter 16:The string Class and the Standard Template Library》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么标准库的价值是契约组合而不只是少写代码

标准库把常见资源和算法装进具有复制、生命周期、范围和失败语义的类型。 string 不只是可增长字符数组;container 不只是数据盒;algorithm 也不只是现成循环。它们通过迭代器和 callable contract 组合,让同一算法作用于多种表示。

可核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么标准库的价值是契约组合而不只是少写代码」的状态变化。

学完《Chapter 16:The string Class and the Standard Template Library》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 16:The string Class and the Standard Template Library:失效与核验

为什么标准库的价值是契约组合而不只是少写代码

典型失效

若只复述「为什么标准库的价值是契约组合而不只是少写代码」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么标准库的价值是契约组合而不只是少写代码」的状态变化。

string class 把字符、长度与资源绑成值

典型失效

若只复述「string class 把字符、长度与资源绑成值」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「string class 把字符、长度与资源绑成值」的状态变化。

查找和子串要把 npos 当作显式分支

典型失效

若只复述「查找和子串要把 npos 当作显式分支」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「查找和子串要把 npos 当作显式分支」的状态变化。

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

为什么标准库的价值是契约组合而不只是少写代码

标准库把常见资源和算法装进具有复制、生命周期、范围和失败语义的类型。string 不只是可增长字符数组;container 不只是数据盒;algorithm 也不只是现成循环。它们通过迭代器和 callable contract 组合,让同一算法作用于多种表示。

string class 把字符、长度与资源绑成值

string 类(std::string)拥有字符序列并保存长度,支持复制、移动和自动释放。它可以包含空字符,因此 size() 才是完整长度证据;c_str() 提供以空字符结尾的临时观察指针,用于 C API,但修改 string 后指针可能失效。

#include <string>
 
std::string fullName(std::string first, std::string last) {
    std::string result = std::move(first);
    result += ' ';
    result += last;
    return result;
}

size() 是当前元素数,capacity() 是不重新分配可容纳的数量;capacity 不是可访问元素范围。reserve() 可减少已知增长的重分配,resize() 才改变 size 并建立/删除字符。

查找和子串要把 npos 当作显式分支

find() 失败返回 std::string::npos,不能把它当普通索引继续加一。substr(pos,count) 要求 pos 合法并返回新 string;compare 和关系运算按字典序,不等价于数字或本地化排序。

std::string_view value{"scheme://host/path"};
const std::size_t marker = value.find("://");
if (marker == std::string_view::npos) {
    throw std::invalid_argument{"missing scheme"};
}
const std::string_view scheme = value.substr(0, marker);

混用 operator>>getline 时,格式化提取会留下分隔换行;应决定是否 std::ws 跳过前导空白,或始终按行读取再解析。输入协议不是 string 自动替你选择的。

smart pointer templates 把 owner 写进类型

智能指针模板(smart pointer templates)用析构、复制和移动规则表达资源责任。unique_ptr<T> 是唯一 owner,移动转移;shared_ptr<T> 的强引用组共同延长生命周期;weak_ptr<T> 观察共享对象而不延长生命,使用前通过 lock() 获取临时强引用。

#include <memory>
 
auto owner = std::make_unique<Widget>(42);
Widget* borrowed = owner.get();
 
auto transferred = std::move(owner);
// owner is now empty; borrowed is valid only while transferred owns the Widget

auto_ptr 的复制会转移所有权,不能满足普通容器和值语义,现代 C++ 已移除;使用 unique_ptr 显式 move。shared_ptr 不是默认升级:循环引用需要 weak_ptr 打断,共享可变对象还需要同步和领域一致性。

STL containers 选择的是复杂度和稳定性契约

STL 容器(STL containers)管理元素范围。sequence container 如 vector/deque/list 强调位置顺序;associative container 如 set/map 按 key 组织;unordered container 以 hash 分桶;adapter 如 stack/queue 限制底层接口。

#include <map>
#include <vector>
 
std::vector<std::string> names{"Ada", "Bjarne", "Barbara"};
std::map<std::string, int> scoreByName;
for (const auto& name : names) {
    scoreByName.emplace(name, 0);
}

vector 连续、随机访问快,但增长重分配会使所有 pointer/reference/iterator 失效;list 插入稳定却不支持随机访问;map 的查找和顺序契约不同于 unordered_map。选择依据是访问、插入、顺序、内存局部性和失效规则,不是名字熟悉度。

iterator 把算法与容器表示解耦

迭代器(iterator)表示范围中的位置。STL 使用半开区间 [first,last):first 可指向首元素,last 是尾后位置不可解引用,空范围满足 first==last。不同算法要求 input、forward、bidirectional 或 random-access 能力。

auto it = names.begin();
while (it != names.end()) {
    if (it->empty()) {
        it = names.erase(it);  // use returned next iterator
    } else {
        ++it;
    }
}

修改容器后必须按该容器/操作的失效规则更新迭代器。缓存 end()、元素地址或索引也各有不同稳定性,不应泛化成“迭代器总会失效”或“list 永不失效”。

generic programming 复用满足操作要求的类型

泛型编程(generic programming)把算法写成类型/操作模式,而不是绑定具体类层次。STL algorithm 接收 iterator range,并通过函数对象(functor)、函数指针或 lambda 注入比较、谓词和变换策略。

struct ByLength {
    bool operator()(const std::string& a, const std::string& b) const {
        return a.size() < b.size();
    }
};
 
std::sort(names.begin(), names.end(), ByLength{});

sort 需要 random-access iterator,比较器必须形成 strict weak ordering。若比较器对同一元素不稳定、让 comp(a,a) 为真或产生环,算法前置条件被破坏,结果不可信。

algorithms 把遍历、输出和策略分开

算法(algorithm)通常不改变容器 size,只在给定范围读写;remove_if 只把保留元素移到前部并返回逻辑新尾,真正删除 vector 尾部还需 erase。输出算法要有足够空间或使用 inserter。

auto newEnd = std::remove_if(
    names.begin(), names.end(),
    [](const std::string& name) { return name.empty(); }
);
names.erase(newEnd, names.end());

for_each 适合副作用,transform 生成变换结果,accumulate 折叠范围;从意图选择算法能把边界循环交给库,但前置条件仍需测试。

valarray 提供逐元素数值表达式

std::valarray<T> 面向同类数值序列,算术运算逐元素执行,并提供 sum、min、max、shift、slice 等能力。它不是 vector 的通用替代:iterator/容器互操作历史上较弱,元素类型和操作集中于数值计算。

#include <valarray>
 
std::valarray<double> samples{1.0, 2.0, 3.0};
std::valarray<double> calibrated = samples * 2.0 + 1.0;
double total = calibrated.sum();

先预测 sort/filter/transform/valarray 四条管线需要的 iterator、输出和 callable 条件,再切换实验面板。记录输入、期望输出和失效点,不能只看模板是否编译。

三步验证标准库管线

分步1 / 3

第一步:画 STL 四方契约

写出 container range/invalidation、iterator category、algorithm precondition 与 functor 语义,再选择组合。

小结

  • string class 绑定字符、size 和资源;capacity 不是可访问范围,C 指针观察受修改失效约束
  • smart pointer templates 编码 owner 协议,但不自动解决借用、循环或共享可变状态
  • STL containers 通过复杂度、顺序和 invalidation contract 区分
  • iterator 以半开范围连接表示与 algorithms,算法还要求具体 iterator category
  • generic programming 用操作要求复用类型,functor 注入策略并必须保持比较/谓词语义
  • algorithms and valarray 分别提供范围操作与逐元素数值表达式

练习

  1. 问题 1:审计 string 指针。 保存 const char* p = text.c_str() 后 append,为什么 p 不能继续使用?
  1. 问题 2:选择智能指针。 树节点唯一拥有孩子,孩子需要观察父节点,应使用什么组合?
  1. 问题 3:修复算法管线。 对 list 调用 sort(first,last) 编译失败,应该复制到 vector 吗?

名词解释

名词解释

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

string 类
拥有字符序列、长度和资源生命周期的标准库值类型。
智能指针模板
通过类型语义表达动态对象 owner 与释放策略的 RAII 指针。
STL 容器
管理元素范围并声明复杂度和失效规则的模板类型。
迭代器
表示范围位置与遍历能力、连接容器和算法的对象。
泛型编程
围绕类型操作要求编写跨表示复用代码的方式。
函数对象
定义调用运算符并可携带状态的对象。
valarray
提供逐元素数值运算和归约的标准库模板。

讨论

评论区加载中…