C++ 进阶知识

本章将深入探讨 C++ 的高级特性,包括模板编程、STL 标准库、异常处理、智能指针、Lambda 表达式和多线程编程。掌握这些内容将使你能够编写更通用、更高效、更健壮的 C++ 代码。

本页阅读量: 加载中...

模板编程

通俗类比

想象你去奶茶店点饮料。你可以点"大杯珍珠奶茶"、"中杯抹茶拿铁"——模板就像这个菜单模板,你不用为每一种杯型和口味单独写一份制作流程,只需要写一份"模板流程",然后填入不同的配料和杯型即可。

模板(Template)是 C++ 最强大的特性之一,它允许你编写与类型无关的通用代码。通过模板,你可以创建能够处理任意数据类型的函数和类,实现代码复用的最大化。

函数模板

函数模板让你编写一个通用的函数,可以处理多种数据类型:

C++
#include <iostream>
using namespace std;

// 函数模板定义
template <typename T>
T maxVal(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << maxVal(3, 5) << endl;        // T 被推导为 int
    cout << maxVal(3.5, 2.1) << endl;   // T 被推导为 double
    cout << maxVal('a', 'z') << endl;   // T 被推导为 char
    return 0;
}

类模板

类模板让你创建通用的数据结构:

C++
#include <iostream>
using namespace std;

// 通用的盒子类
template <typename T>
class Box {
private:
    T value;
public:
    Box(T v) : value(v) {}
    void set(T v) { value = v; }
    T get() { return value; }
};

int main() {
    Box<int> intBox(100);        // 整数盒子
    Box<double> doubleBox(3.14);  // 浮点数盒子
    Box<string> strBox("Hello");  // 字符串盒子
    
    cout << intBox.get() << endl;     // 100
    cout << doubleBox.get() << endl;  // 3.14
    cout << strBox.get() << endl;     // Hello
    return 0;
}

多个模板参数

C++
// 键值对类,两个类型参数
template <typename K, typename V>
class Pair {
public:
    K key;
    V value;
    Pair(K k, V v) : key(k), value(v) {}
};

// 使用
Pair<string, int> p1("age", 20);
Pair<int, double> p2(1, 98.5);
提示

模板在编译时实例化,不会产生运行时开销。现代 C++ 中,typenameclass 在模板参数中可以互换使用。

编程练习

编写模板函数 maxVal(T a, T b) 返回两个值中较大的一个。在 main 中分别用 int、double、char 三种类型测试该函数。

STL 容器

通俗类比

STL 就像一套乐高积木。vector 是可伸缩的收纳盒,list 是手拉手的小朋友队伍,map 是查字典——你不用从零制造这些容器,直接拿来用,把精力放在"搭什么建筑"(解决什么问题)上。

STL(Standard Template Library,标准模板库)是 C++ 标准库的核心组成部分,提供了一系列通用的数据结构和算法。STL 容器是用于存储和管理数据的类模板。

序列容器

容器头文件特点适用场景
vector<vector>动态数组,随机访问快频繁随机访问,尾部增删
deque<deque>双端队列,两端操作快头部尾部都需要增删
list<list>双向链表,插入删除快频繁中间插入删除
forward_list<forward_list>单向链表,内存占用小只需要单向遍历

vector(动态数组)

vector 是最常用的 STL 容器,它会自动管理内存,可以根据需要动态增长:

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 创建 vector
    vector<int> nums;           // 空 vector
    vector<int> nums2(5);      // 5 个元素,初始化为 0
    vector<int> nums3 = {1, 2, 3, 4, 5};  // 列表初始化
    
    // 添加元素
    nums.push_back(10);   // 在尾部添加
    nums.emplace_back(20); // C++11,更高效
    
    // 访问元素
    cout << nums3[0] << endl;     // 1(不检查越界)
    cout << nums3.at(0) << endl;  // 1(检查越界,安全)
    cout << nums3.front() << endl; // 第一个元素
    cout << nums3.back() << endl;  // 最后一个元素
    
    // 获取大小
    cout << nums3.size() << endl;     // 元素个数
    cout << nums3.empty() << endl;    // 是否为空
    
    // 删除元素
    nums3.pop_back();       // 删除最后一个
    nums3.clear();          // 清空所有
    
    // 遍历(C++11 范围 for)
    vector<int> v = {10, 20, 30};
    for (int x : v) {
        cout << x << " ";
    }
    
    return 0;
}

关联容器

容器头文件特点适用场景
set<set>有序集合,元素唯一需要去重和排序
map<map>有序键值对,按键排序需要按键查找
unordered_set<unordered_set>哈希集合,查找 O(1)快速查找,不关心顺序
unordered_map<unordered_map>哈希映射,查找 O(1)最快的键值查找

map(映射)

C++
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    // 创建 map:键是 string,值是 int
    map<string, int> scores;
    
    // 插入元素
    scores["张三"] = 90;
    scores["李四"] = 85
    scores.insert({"王五", 95});
    
    // 访问元素
    cout << scores["张三"] << endl;  // 90
    
    // 检查键是否存在
    if (scores.count("赵六") == 0) {
        cout << "赵六不存在" << endl;
    }
    
    // 遍历(按键的升序)
    for (const auto& pair : scores) {
        cout << pair.first << ": " << pair.second << endl;
    }
    
    return 0;
}
说明

auto 关键字(C++11)让编译器自动推导变量类型,简化代码。unordered_map 使用哈希表实现,平均查找时间复杂度为 O(1),比 map 的 O(log n) 更快。

编程练习

创建一个 vector<int>,存入用户输入的 5 个整数,然后使用 STL 算法完成以下操作并输出结果:

  1. 使用 sort 排序
  2. 使用 reverse 反转
  3. 使用 accumulate 求和
  4. 使用 count 统计某个值出现的次数

迭代器

通俗类比

迭代器就像一本书的书签。你可以把书签放在某一页(指向某个元素),翻页(++ 向后移动),回看(-- 向前移动),直接跳到某一章(随机访问)。没有书签,你就很难快速找到特定内容。

迭代器(Iterator)是 STL 中用于遍历容器元素的对象,它提供了一种统一的方式来访问不同容器中的数据,类似于指针。

迭代器的基本使用

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {10, 20, 30, 40, 50};
    
    // 使用迭代器遍历
    vector<int>::iterator it;
    for (it = v.begin(); it != v.end(); ++it) {
        cout << *it << " ";  // 解引用获取值
    }
    
    // 使用 auto 简化(C++11)
    for (auto it = v.begin(); it != v.end(); ++it) {
        cout << *it << " ";
    }
    
    // const_iterator:不允许修改
    for (auto it = v.cbegin(); it != v.cend(); ++it) {
        // *it = 100;  // 错误!不能修改
        cout << *it << " ";
    }
    
    return 0;
}

迭代器类型

迭代器类型能力支持的容器
输入迭代器只读,单次遍历istream
输出迭代器只写,单次遍历ostream
前向迭代器可读写,多遍遍历forward_list, unordered_*
双向迭代器可前进后退list, set, map
随机访问迭代器可随机跳转vector, deque, array

编程练习

定义一个 vector<string> 存储 {"apple", "banana", "cherry", "date"}。使用迭代器遍历容器,输出每个字符串及其长度,不使用范围 for 循环。

STL 算法

通俗类比

STL 算法就像厨房里的多功能料理机。sort 是自动排序机,find 是雷达搜索器,copy 是复印机。这些工具已经由顶级厨师(标准库开发者)调试好了,你直接选用,比自己从头造一台机器要可靠得多。

STL 提供了大量通用的算法函数,定义在 <algorithm> 头文件中。这些算法可以与各种容器配合使用。

常用算法

C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // 排序
    sort(v.begin(), v.end());              // 升序排序
    sort(v.begin(), v.end(), greater<int>());  // 降序排序
    
    // 查找
    auto it = find(v.begin(), v.end(), 5);
    if (it != v.end()) cout << "找到了: " << *it << endl;
    
    // 二分查找(需要先排序)
    bool exists = binary_search(v.begin(), v.end(), 5);
    
    // 统计
    int count = count(v.begin(), v.end(), 1);
    
    // 最值
    auto minIt = min_element(v.begin(), v.end());
    auto maxIt = max_element(v.begin(), v.end());
    
    // 反转
    reverse(v.begin(), v.end());
    
    // 去重(需要先排序)
    sort(v.begin(), v.end());
    auto last = unique(v.begin(), v.end());
    v.erase(last, v.end());
    
    return 0;
}

编程练习

定义一个 vector<int> 包含 {45, 12, 78, 23, 67, 89, 34}。使用 STL 算法完成:

  1. 排序并输出
  2. 查找值为 67 的元素位置
  3. 统计大于 50 的元素个数(使用 count_if
  4. 将所有元素乘以 2(使用 transform

异常处理

通俗类比

异常处理就像火灾报警系统。try 是"正常工作区",catch 是"消防员"——当某个地方出问题时(火灾),报警器会响(throw),消防员立即赶到处理(catch),而不会让整个大楼(程序)崩溃。

异常处理是一种处理程序运行时错误的机制。它允许程序在发生错误时"抛出"一个异常,然后在合适的地方"捕获"并处理它。

基本语法

C++
#include <iostream>
#include <stdexcept>
using namespace std;

// 可能抛出异常的函数
double divide(double a, double b) {
    if (b == 0) {
        throw runtime_error("除数不能为零!");  // 抛出异常
    }
    return a / b;
}

int main() {
    try {
        // 可能抛出异常的代码
        double result = divide(10, 0);
        cout << "结果: " << result << endl;
    }
    catch (const exception& e) {
        // 捕获并处理异常
        cout << "错误: " << e.what() << endl;
    }
    
    cout << "程序继续执行..." << endl;
    return 0;
}

多个 catch 块

C++
try {
    // 可能抛出多种异常的代码
}
catch (const invalid_argument& e) {
    cout << "参数错误: " << e.what() << endl;
}
catch (const runtime_error& e) {
    cout << "运行时错误: " << e.what() << endl;
}
catch (...) {
    // 捕获所有其他异常
    cout << "未知错误" << endl;
}
提示

异常处理不应该用于控制正常程序流程,只应用于真正的异常情况。过度使用异常会影响性能。C++ 提倡"异常安全"的编程方式。

编程练习

编写函数 double safeSqrt(double x),当 x 为负数时抛出 invalid_argument 异常。在 main 中捕获异常并输出友好提示信息。

智能指针

通俗类比

普通指针就像借出去的东西没有登记——容易忘记归还(内存泄漏)。智能指针就像图书馆的自动借还系统,当你不再使用这本书(离开作用域)时,系统会自动归还(释放内存),再也不怕"借书不还"了。

智能指针是 C++11 引入的自动内存管理工具,它们封装了原始指针,在对象不再使用时自动释放内存,有效防止内存泄漏。

三种智能指针

智能指针头文件特点
unique_ptr<memory>独占所有权,不能复制
shared_ptr<memory>共享所有权,引用计数
weak_ptr<memory>弱引用,不增加引用计数

unique_ptr

C++
#include <iostream>
#include <memory>
using namespace std;

int main() {
    // 创建 unique_ptr
    unique_ptr<int> ptr(new int(10));
    
    // 更推荐的创建方式(C++14)
    auto ptr2 = make_unique<int>(20);
    
    // 使用
    cout << *ptr << endl;   // 10
    cout << *ptr2 << endl;  // 20
    
    // 获取原始指针
    int* raw = ptr.get();
    
    // 释放所有权
    int* released = ptr.release();
    delete released;  // 需要手动释放
    
    // 重置(释放旧对象,指向新对象)
    ptr2.reset(new int(30));
    
    // 不能复制 unique_ptr!
    // auto ptr3 = ptr2;  // 编译错误
    
    // 可以移动所有权
    auto ptr3 = move(ptr2);
    
    return 0;  // ptr 和 ptr3 自动释放内存
}

shared_ptr

C++
#include <iostream>
#include <memory>
using namespace std;

int main() {
    // 创建 shared_ptr
    auto sp1 = make_shared<int>(100);
    
    {
        auto sp2 = sp1;  // 复制,引用计数 +1
        cout << sp1.use_count() << endl;  // 2
        
        auto sp3 = sp1;  // 引用计数 +1
        cout << sp1.use_count() << endl;  // 3
    }  // sp2 和 sp3 销毁,引用计数 -2
    
    cout << sp1.use_count() << endl;  // 1
    // sp1 销毁时,引用计数变为 0,内存自动释放
    
    return 0;
}
注意

避免循环引用问题:如果两个 shared_ptr 互相引用,引用计数永远不会变为 0,导致内存泄漏。此时应使用 weak_ptr 打破循环。

编程练习

定义一个 Resource 类,构造函数输出"获取资源",析构函数输出"释放资源"。在 main 中使用 unique_ptrshared_ptr 各管理一个 Resource 对象,观察构造和析构的输出顺序。

Lambda 表达式

通俗类比

Lambda 就像一次性便签纸。有时候你只需要写一句简单的指令(比如"把价格打八折"),不需要专门印一本手册(定义函数)。Lambda 让你快速写一条"用完即扔"的简短代码。

Lambda 表达式(C++11)是创建匿名函数对象的简洁方式。它让你可以在需要函数的地方直接定义函数,而不需要单独写一个命名函数。

基本语法

C++
// 语法:[捕获列表](参数列表) -> 返回类型 { 函数体 }

#include <iostream>
using namespace std;

int main() {
    // 最简单的 lambda
    auto sayHello = []() {
        cout << "Hello!" << endl;
    };
    sayHello();
    
    // 带参数的 lambda
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    cout << add(3, 5) << endl;  // 8
    
    // 返回类型可自动推导
    auto multiply = [](int a, int b) { return a * b; };
    
    return 0;
}

捕获列表

捕获列表决定了 lambda 可以访问哪些外部变量:

C++
int x = 10, y = 20;

// []      - 不捕获任何外部变量
// [=]     - 以值方式捕获所有变量
// [&]     - 以引用方式捕获所有变量
// [x]     - 只以值捕获 x
// [&x]    - 只以引用捕获 x
// [x, &y] - x 值捕获,y 引用捕获

auto lambda1 = [=]() { return x + y; };   // 值捕获,不能修改
auto lambda2 = [&]() { x++; y++; };        // 引用捕获,可以修改
auto lambda3 = [x, &y]() { y = x + 10; }; // 混合捕获

Lambda 与 STL 算法结合

C++
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    
    // 使用 lambda 作为回调函数
    for_each(v.begin(), v.end(), [](int n) {
        cout << n * n << " ";  // 输出平方
    });
    
    // 使用 lambda 作为排序条件
    vector<string> names = {"Bob", "Alice", "Charlie"};
    sort(names.begin(), names.end(), [](const string& a, const string& b) {
        return a.length() < b.length();  // 按长度排序
    });
    
    // 使用 lambda 查找
    auto it = find_if(v.begin(), v.end(), [](int n) {
        return n > 3;  // 第一个大于 3 的元素
    });
    
    return 0;
}
提示

Lambda 表达式本质上是一个匿名函数对象(functor),编译器会自动生成一个类。它们非常适合作为 STL 算法的回调函数,代码更加简洁直观。

编程练习

编写程序:定义 vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},使用 Lambda 表达式完成:

  1. 输出所有偶数(for_each + lambda)
  2. 统计大于 5 的数的个数(count_if + lambda)
  3. 将所有元素平方后存入新 vector(transform + lambda)

命名空间

通俗类比

命名空间就像不同班级的学生名册。一班有个"张伟",二班也有个"张伟"——命名空间(一班、二班)让同名的学生不会混淆。using namespace 就像"我只看一班的名册",不用每次都写"一班.张伟"。

命名空间(namespace)用于组织代码,避免命名冲突。在大型项目或使用多个第三方库时尤其重要。

基本用法

C++
#include <iostream>

// 定义命名空间
namespace Math {
    const double PI = 3.14159;
    
    double square(double x) { return x * x; }
    double circleArea(double r) { return PI * r * r; }
}

namespace Utils {
    void print(const std::string& msg) {
        std::cout << "[Utils] " << msg << std::endl;
    }
}

int main() {
    // 使用 :: 运算符访问
    std::cout << Math::PI << std::endl;
    std::cout << Math::circleArea(5.0) << std::endl;
    Utils::print("Hello");
    
    // using 声明
    using Math::PI;
    std::cout << PI << std::endl;  // 可以直接使用 PI
    
    // using namespace(谨慎使用)
    using namespace Utils;
    print("World");
    
    return 0;
}

嵌套命名空间(C++17)

C++
// C++17 简洁语法
namespace Game::Graphics::Rendering {
    class Shader { /* ... */ };
}

// 等价于
namespace Game {
    namespace Graphics {
        namespace Rendering {
            class Shader { /* ... */ };
        }
    }
}

匿名命名空间

C++
// 匿名命名空间中的内容只在当前文件可见
namespace {
    int internalCounter = 0;  // 只在当前文件可见
    void helperFunction() { /* ... */ }
}

// 等价于 C 语言中的 static

编程练习

创建两个命名空间 MathPhysics,各自定义一个 PI 常量(值不同)和一个 calculate() 函数。在 main 中分别调用两个命名空间的函数并输出结果。

预处理指令

通俗类比

预处理器就像剧本的准备工作。在正式开拍前,导演先把"#张三 替换为 主演"(宏替换),把"所有场景A的剧本粘贴进来"(文件包含)。这些都是在"开拍前"(编译前)完成的准备工作。

预处理器在编译之前处理源代码,以 # 开头的指令都是预处理指令。

常用预处理指令

C++
// 包含头文件
#include <iostream>     // 系统头文件
#include "myheader.h"   // 用户头文件

// 宏定义
#define PI 3.14159
#define SQUARE(x) ((x) * (x))

// 条件编译
#ifdef DEBUG
    cout << "调试模式" << endl;
#else
    cout << "发布模式" << endl;
#endif

// 防止头文件重复包含
#ifndef MY_HEADER_H
#define MY_HEADER_H
// 头文件内容
#endif

// 更现代的方式(pragma once)
#pragma once  // 放在头文件最开头

预定义宏

说明
__LINE__当前行号
__FILE__当前文件名
__DATE__编译日期
__TIME__编译时间
__func__当前函数名(C++11)
C++
#include <iostream>
using namespace std;

#define LOG(msg) cout << __FILE__ << ":" << __LINE__ \
                   << " [" << __func__ << "] " << msg << endl

void test() {
    LOG("函数被调用");  // 输出: main.cpp:10 [test] 函数被调用
}

int main() {
    cout << "编译日期: " << __DATE__ << endl;
    cout << "编译时间: " << __TIME__ << endl;
    test();
    return 0;
}

编程练习

使用宏定义和条件编译编写程序:定义宏 DEBUG 时输出调试信息,未定义时只输出常规信息。使用 #ifdef 控制不同编译行为。

多线程编程

通俗类比

单线程就像一个人 sequentially 做家务:先洗碗、再拖地、再晾衣服。多线程就像一家人分工合作:爸爸洗碗、妈妈拖地、孩子晾衣服——三个人同时干活,整体速度大大提升!

多线程允许程序同时执行多个任务。C++11 引入了标准的线程库 <thread>,让跨平台的多线程编程成为可能。

创建线程

C++
#include <iostream>
#include <thread>
using namespace std;

// 线程函数
void worker(int id) {
    cout << "线程 " << id << " 正在运行" << endl;
}

int main() {
    // 创建线程
    thread t1(worker, 1);
    thread t2(worker, 2);
    
    // 使用 lambda 创建线程
    thread t3([]() {
        cout << "Lambda 线程" << endl;
    });
    
    // 等待线程完成
    t1.join();
    t2.join();
    t3.join();
    
    cout << "所有线程已完成" << endl;
    return 0;
}

线程同步(互斥锁)

当多个线程访问共享数据时,需要使用互斥锁(mutex)来防止数据竞争:

C++
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;        // 互斥锁
int counter = 0;  // 共享数据

void increment() {
    for (int i = 0; i < 100000; i++) {
        mtx.lock();    // 加锁
        counter++;     // 临界区
        mtx.unlock();  // 解锁
    }
}

// 更安全的写法:lock_guard
void incrementSafe() {
    for (int i = 0; i < 100000; i++) {
        lock_guard<mutex> lock(mtx);  // RAII 自动管理锁
        counter++;
    }  // 离开作用域自动解锁
}

其他同步工具

工具头文件用途
mutex<mutex>基本的互斥锁
lock_guard<mutex>RAII 方式自动管理锁
unique_lock<mutex>更灵活的锁管理
condition_variable<condition_variable>条件变量,线程间通信
atomic<atomic>原子操作,无锁编程
future/promise<future>异步任务和结果获取
提示

多线程编程的核心挑战是数据竞争死锁。尽量使用高级同步原语(如 lock_guardatomic),避免手动管理锁。遵循"以数据为中心"的设计,减少共享状态。

进阶知识学习完成!

接下来可以学习 C++ 图形界面,使用 Qt 框架开发桌面应用程序。

编程练习

创建两个线程同时向一个 vector<int> 中添加元素(各添加 1000 个)。分别测试以下两种情况并观察结果差异:

  1. 不加互斥锁
  2. 使用 mutex 加锁保护

课后练习与项目实践

基础练习

作业 1:模板交换函数

编写一个模板函数 swapValues(T& a, T& b),可以交换任意两个同类型变量的值。在主函数中测试 int、double、char 三种类型。

参考实现

C++
#include <iostream>
using namespace std;

template <typename T>
void swapValues(T& a, T& b) {
    T temp = a; a = b; b = temp;
}

int main() {
    int x = 3, y = 7;
    swapValues(x, y);
    cout << "int: " << x << " " << y << endl;

    double a = 1.5, b = 2.5;
    swapValues(a, b);
    cout << "double: " << a << " " << b << endl;

    char c1 = 'A', c2 = 'Z';
    swapValues(c1, c2);
    cout << "char: " << c1 << " " << c2 << endl;
    return 0;
}

作业 2:vector 综合练习

创建一个 vector<int>,存入10个随机数(1-100),然后:
1. 输出所有元素
2. 用 sort 从小到大排序并输出
3. 删除所有偶数(使用 erase-remove 惯用法)
4. 输出剩余元素

参考实现

C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;

int main() {
    srand(time(0));
    vector<int> nums;
    for (int i = 0; i < 10; i++) nums.push_back(rand() % 100 + 1);

    cout << "原始: ";
    for (int n : nums) cout << n << " ";
    cout << endl;

    sort(nums.begin(), nums.end());
    cout << "排序: ";
    for (int n : nums) cout << n << " ";
    cout << endl;

    nums.erase(remove_if(nums.begin(), nums.end(),
        [](int x){ return x % 2 == 0; }), nums.end());

    cout << "去偶: ";
    for (int n : nums) cout << n << " ";
    cout << endl;
    return 0;
}

作业 3:异常安全除法

编写除法函数 safeDivide(double a, double b),当 b 为 0 时抛出异常。在主函数中用 try-catch 捕获并提示"不能除以零"。

参考实现

C++
#include <iostream>
#include <stdexcept>
using namespace std;

double safeDivide(double a, double b) {
    if (b == 0) throw runtime_error("不能除以零!");
    return a / b;
}

int main() {
    double a, b;
    cout << "输入两个数: "; cin >> a >> b;
    try {
        cout << "结果: " << safeDivide(a, b) << endl;
    } catch (const exception& e) {
        cout << "错误: " << e.what() << endl;
    }
    return 0;
}

进阶练习

作业 4:智能指针管理对象数组

定义一个 Book 类(书名、价格),使用 vector<shared_ptr<Book>> 管理一个图书馆的书目。实现添加书籍、显示所有书籍、计算总价功能。

参考实现

C++
#include <iostream>
#include <vector>
#include <memory>
#include <string>
using namespace std;

class Book {
public:
    string title;
    double price;
    Book(string t, double p) : title(t), price(p) {}
};

int main() {
    vector<shared_ptr<Book>> library;
    library.push_back(make_shared<Book>("C++ Primer", 89.0));
    library.push_back(make_shared<Book>("Effective C++", 65.0));
    library.push_back(make_shared<Book>("STL源码剖析", 79.0));

    double total = 0;
    for (auto& b : library) {
        cout << b->title << " ¥" << b->price << endl;
        total += b->price;
    }
    cout << "总价: ¥" << total << endl;
    return 0;
}

作业 5:多线程计数器

创建两个线程,一个线程将全局变量 count 从 0 加到 50000,另一个线程也从 0 加到 50000(共享同一个 count)。观察结果为什么不是 100000,并尝试用 mutex 解决。

参考实现

C++
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int count = 0;
mutex mtx;

void increment() {
    for (int i = 0; i < 50000; i++) {
        lock_guard<mutex> lock(mtx);  // 自动加锁解锁
        count++;
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join(); t2.join();
    cout << "最终结果: " << count << endl;  // 正确输出 100000
    return 0;
}

综合项目:单词频率统计器

项目目标

编写程序读取一段英文文本(从文件或直接写在程序里),统计每个单词出现的次数,按频率从高到低输出前10个单词。要求使用 map 存储,使用 sort 配合 Lambda 排序。

参考实现

C++
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <sstream>
#include <string>
using namespace std;

int main() {
    string text = "the quick brown fox jumps over the lazy dog the dog was very lazy";
    map<string, int> freq;

    stringstream ss(text);
    string word;
    while (ss >> word) freq[word]++;

    vector<pair<string, int>> items(freq.begin(), freq.end());
    sort(items.begin(), items.end(),
        [](auto& a, auto& b) { return a.second > b.second; });

    cout << "Top 10 单词:" << endl;
    for (int i = 0; i < min(10, (int)items.size()); i++) {
        cout << items[i].first << ": " << items[i].second << endl;
    }
    return 0;
}