大学计算机专业 · 现代 C++ 课程(C++11/14/17/20)

C++ 程序设计:
面向过程到面向对象与现代 C++ 实战

本教程面向大学一年级学生,按"C++ 基础 → 面向对象 → STL 与模板 → 现代 C++ → 工程与综合项目"的路线展开。我们将以现代 C++(以 C++17/20 为主)的视角组织内容,突出类与对象、STL、智能指针、Lambda,让你从零走向能交付完整 C++ 项目。

1979Bjarne Stroustrup 在贝尔实验室诞生
C with ClassesC 的超集,加入 OOP 与泛型
C++11 / 17 / 20三个里程碑标准
系统级 + OOP + STL游戏/驱动/AI 基础设施
↓ 向下滚动,按章节系统学习并完成课堂练习
00

学习指南:为什么学、怎么学、学到什么

C++ 是一门"难学难精"的语言——它既保留了对硬件的直接控制,又加入了面向对象与泛型。本章先帮你建立全局认知。

0.1 什么是 C++

C++ 由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计,最初叫"C with Classes"。它是 C 的超集:

  • 兼容 C:几乎所有合法 C 程序也是合法 C++ 程序;
  • 面向对象:类、继承、多态、虚函数;
  • 泛型编程:模板与 STL;
  • 零成本抽象:高级特性不付出额外运行代价("what you don't use, you don't pay for")。

C++ 的主要应用领域

  • 操作系统:Windows、macOS、Linux 内核与系统组件;
  • 游戏开发:Unreal Engine、《原神》、王者荣耀等;
  • 浏览器:Chrome (Blink/V8)、Firefox (Gecko/SpiderMonkey);
  • 数据库:MySQL、PostgreSQL、MongoDB、Redis;
  • 嵌入式 / 物联网图形学 / CAD金融高频交易
  • AI 基础设施:PyTorch / TensorFlow 后端;
  • 编译器 / 虚拟机:LLVM、Clang、MSVC、C++ 自身。

一句话总结:C++ 是性能敏感系统的"主力语言",学完它你能理解从操作系统到游戏引擎的底层逻辑。

0.2 C++ 标准的发展:理解"现代 C++"

C++ 的每一次标准更新都带来"更现代、更安全、更高效"的写法。建议直接学现代 C++,而不是先学传统 C++ 再迁移

C++98第一个 ISO 标准 C++03补丁修订 C++11 ⭐auto · Lambda · 右值引用智能指针 / 范围 for C++14泛型 Lambda · 改进 C++17 ⭐std::optional · if constexprfilesystem · 结构化绑定 C++20 ⭐Concepts · Modules协程 · ranges C++23std::expected 等 C++26草案制定中
C++ 标准演进时间轴 — C++11/17/20 是三个里程碑
核心观点:现代 C++(C++11 起)改变了 C++ 的写法 —— auto、Lambda、范围 for、智能指针、std::optional 等让代码更简洁、更安全。本教程默认使用 C++17 风格。

0.3 学习路线(从大一到大二进阶)

C++ 基础第 01-04 章
控制 / 函数第 05-06 章
容器 / 指针引用第 07-08 章
类与对象 ⭐第 09 章
继承 / 多态 ⭐第 10 章
模板 / STL ⭐第 12-14 章
Lambda / 智能指针 ⭐第 15-16 章
工程 + 项目第 17-22 章
01

C++ 入门:第一个程序与基本结构

与 C 相比,C++ 使用 iostream + std::cout 输出,理解命名空间与流的概念。

1.1 Hello, World!

C++hello.cpp
#include <iostream>

int main()
{
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

逐行解释

  • #include <iostream>:包含标准输入输出流头文件;
  • std::coutstd 命名空间下的"标准输出流"对象;
  • <<:流插入运算符,把右侧数据"插入"到流里;
  • std::endl:输出换行并刷新缓冲区(比 "\n" 稍慢);
  • :::作用域运算符,访问 std 中的 cout

1.2 注释 & 命名空间

C++namespace.cpp
// 单行注释(C99/C++ 都支持)

/*
   多行注释
*/

#include <iostream>

// 方式一:每次写 std::  (推荐,更清晰)
int main() {
    std::cout << "A" << std::endl;
    return 0;
}

// 方式二:使用 using 声明简化(大作业慎用,避免命名冲突)
using std::cout;     // 只引入 cout
using std::endl;

// 方式三:using namespace std;  (教学代码常用,工业代码慎用)
风格建议:学生阶段可以 using namespace std;,但工业代码与面试代码推荐显式写 std:: —— 避免与第三方库的同名符号冲突。

1.3 C++ 程序的编译与运行

shellterminal
# 1. 用编辑器写 hello.cpp
$ vim hello.cpp

# 2. 用 g++ 编译:-o 指定输出文件名
$ g++ -std=c++17 hello.cpp -o hello

# 3. 运行
$ ./hello
Hello, World!

# 常用选项
$ g++ -Wall -Wextra -O2 -std=c++17 main.cpp -o main   # 警告 + 优化
$ g++ -g main.cpp -o main                            # 调试模式
$ g++ main.cpp -o main                               # 默认 C++ 标准(通常 C++14)
02

C++ 开发环境:编译器、IDE 与 CMake 入门

搭好工具链是开始的第一步 —— 编译器、IDE、构建系统三件套。

2.1 主流编译器

编译器平台特点
g++ (GCC)Linux/macOS/WindowsGNU 出品,最常用的开源 C++ 编译器,本课程默认使用
clang++macOS/Linux/WindowsApple 主力,错误提示友好,编译快
MSVC (cl.exe)WindowsVisual Studio 自带,Windows 平台原生
Intel oneAPI跨平台Intel CPU 优化最佳

2.2 推荐开发环境组合

方案 A:Linux/macOS + VS Code(推荐)

  • 编译器:g++ 或 clang++(macOS 自带);
  • VS Code 扩展:C/C++、CMake Tools、Code Runner;
  • 终端 g++ main.cpp -o main && ./main
  • 调试:gdb 或 lldb。

方案 B:Windows

  • Visual Studio:Windows 下 IDE 之王,自带 MSVC;
  • CLion:JetBrains 出品,C++ 专用智能 IDE;
  • MinGW + VS Code:轻量级,跨平台一致体验。

2.3 CMake 工程化基础

当项目不止一个源文件时,CMake 能帮你自动管理编译流程 —— 是 C++ 工程化的"行业标准"。

CMakeCMakeLists.txt
# CMakeLists.txt —— CMake 项目配置文件
cmake_minimum_required(VERSION 3.15)       # 最低版本要求
project(hello_cpp LANGUAGES CXX)           # 项目名 + 语言

set(CMAKE_CXX_STANDARD 17)              # 使用 C++17 标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(hello main.cpp)             # 生成可执行文件 hello
shellCMake 常用命令
$ mkdir build && cd build
$ cmake ..            # 生成 Makefile / 构建系统
$ cmake --build .     # 编译
$ ./hello             # 运行
03

基本语法:标识符、关键字与输入输出

本节聚焦 C++ 相对 C 的关键语法差异 —— 命名空间、流式 I/O、类型推断。

3.1 关键字(C++17 约 84 个)

C++ 关键字除了继承自 C 的全部,还增加了面向对象、模板与现代特性关键字:

类别关键字(部分)
类型bool char int double void auto decltype
面向对象class struct public private protected virtual override final
模板template typename
控制流if else switch for while do break continue return goto
异常try catch throw noexcept
存储static const constexpr mutable explicit inline volatile
命名空间namespace using
类型转换static_cast dynamic_cast const_cast reinterpret_cast
智能指针nullptr
现代enum class decltype(auto) requires concept co_await co_return module import

3.2 变量与 auto 类型推断 C++11

C++variable.cpp
#include <iostream>
#include <string>

int main() {
    // 传统写法:明确类型
    int    age   = 18;
    double score = 95.5;
    std::string name = "Alice";

    // C++11 起:auto 让编译器推断类型
    auto x  = 42;          // int
    auto y  = 3.14;        // double
    auto s  = "hello";     // const char* (注意不是 string)
    auto z  = name + "!";  // std::string

    // 常量:const / constexpr
    const int       MAX = 100;
    constexpr double PI = 3.14159;   // 编译期常量

    std::cout << name << ", age=" << age << ", PI=" << PI << std::endl;
}

3.3 输入输出:cin / cout

C++io.cpp
#include <iostream>
#include <string>

int main() {
    int          age;
    std::string name;

    std::cout << "请输入姓名和年龄:";
    std::cin >> name >> age;            // 自动跳过空白,不需要 &

    std::cout << "你好," << name << "!今年 "
              << age << " 岁。" << std::endl;

    // 读一行(含空格)
    std::string line;
    std::getline(std::cin, line);
}
关键差异:C++ 的 cin >> x 自动传引用、不需要写 &getline 用于读含空格的整行。
04

数据类型、类型转换与运算符

C++ 在 C 的基础上加入 bool、引用类型,以及更安全的 static_cast 系列转换。

4.1 基本数据类型

类型含义典型大小
bool布尔1 字节(true / false)
char字符1
wchar_t / char32_t宽字符2 / 4
short / int / long / long long整型2 / 4 / 4~8 / 8
float / double / long double浮点4 / 8 / 12 或 16

4.2 类型转换:4 个命名 cast

C++ 摒弃了 C 风格的强制转换 (int)x,推荐使用更安全、意图明确的命名转换:

C++cast.cpp
#include <iostream>

int main() {
    int    a = 10;
    double b = static_cast<double>(a) / 3;   // 编译期检查,常用

    const int x = 42;
    int* p = const_cast<int*>(&x);          // 去掉 const(慎用)

    // dynamic_cast 用于多态指针/引用的下行转换(见继承章节)
    // reinterpret_cast 是底层位重解释(极少使用)

    std::cout << b << std::endl;          // 3.333...
}
转换方式用途
static_cast编译期检查的常规类型转换(最常用)
dynamic_cast多态类型的下行转换(运行时检查,需 RTTI)
const_cast添加或去除 const / volatile
reinterpret_cast位级别的重新解释(危险,少用)

4.3 运算符

C++ 在 C 的运算符之外,新增了几个常用运算符:

  • 作用域 :::访问命名空间、类的成员;
  • 成员访问 -> / .:对象的指针与直接访问;
  • 条件 ?:逗号 ,sizeof:同 C;
  • 类型识别 typeid(RTTI);
  • throw:抛出异常。
05

程序控制结构

与 C 基本相同;C++11 起新增范围 for遍历,让循环更简洁。

5.1 范围 for:现代 C++ 的遍历方式 C++11

C++range_for.cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 拷贝每个元素到 x(只读遍历)
    for (int x : numbers) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    // 引用遍历(修改元素)
    for (auto& x : numbers) {
        x = x * x;            // 平方
    }

    // const 引用:避免拷贝
    for (const auto& x : numbers) {
        std::cout << x << " ";
    }
}

建议:遍历容器时优先用范围 for + const auto& —— 比传统下标循环更安全、更通用。

5.2 if / switch:基本控制流(与 C 类似)

C++control.cpp
if (score >= 90)        grade = 'A';
else if (score >= 80)   grade = 'B';
else if (score >= 60)   grade = 'C';
else                       grade = 'D';

switch (op) {
case '+': result = a + b; break;
case '-': result = a - b; break;
default: std::cerr << "未知操作符";
}
06

函数:声明、参数、返回、重载

C++ 在 C 函数基础上加入默认参数、函数重载、引用参数、内联函数 —— 让函数更强大、更易用。

6.1 默认参数

C++default_arg.cpp
void greet(const std::string& name = "World",
          int times = 1) {
    for (int i = 0; i < times; i++)
        std::cout << "Hello, " << name << "!\n";
}

int main() {
    greet();              // Hello, World!  (用默认值)
    greet("Alice");      // Hello, Alice!
    greet("Bob", 3);    // 打印 3 次
}

6.2 函数重载 ⭐

同一作用域内,同名但参数列表不同的函数构成重载。编译器根据实参类型选择调用哪个。

C++overload.cpp
int    add(int a, int b)              { return a + b; }
double add(double a, double b)     { return a + b; }
std::string add(const std::string& a,
                 const std::string& b) { return a + b; }

int main() {
    std::cout << add(1, 2)               << "\n"; // 3
    std::cout << add(1.5, 2.5)           << "\n"; // 4
    std::cout << add("Hello, ", "C++") << "\n"; // Hello, C++
}

6.3 三种参数传递方式

C++param.cpp
// 1. 值传递:拷贝一份,不影响原值
void by_value(int x) { x = 999; }

// 2. 引用传递:直接操作原变量
void by_ref(int& x)    { x = 999; }

// 3. const 引用:只读,避免拷贝(推荐用于大对象)
void print(const std::string& s) { std::cout << s; }

// 4. 指针传递:也可修改,但调用方需传地址
void by_ptr(int* x) { if (x) *x = 999; }
现代 C++ 建议:能用引用就引用(void f(T&)void f(const T&));裸指针更多留给"所有权"语义(如 new/malloc)。

6.4 内联函数 & 递归

C++inline_rec.cpp
// inline:建议编译器把函数体"内联"展开(避免函数调用开销)
inline int square(int x) { return x * x; }

// 递归:阶乘(注意终止条件)
long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
07

数组、字符串与 STL 容器

C++ 在 C 风格数组之上,提供了std::stringstd::vector —— 推荐优先使用。

7.1 std::string ⭐:真正的字符串类型

C++string.cpp
#include <string>
#include <iostream>

int main() {
    std::string s = "Hello";
    s += ", World!";                          // 拼接(+ 可用)
    std::cout << s << " 长度=" << s.size() << std::endl;

    // 子串、查找、替换
    std::string sub = s.substr(0, 5);              // "Hello"
    auto pos = s.find("World");                  // 7 或 npos
    s.replace(7, 5, "C++");                    // 替换 World → C++

    // 遍历
    for (char c : s) std::cout << c;            // 按字符遍历
}

7.2 std::vector ⭐:动态数组

C++vector.cpp
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3};
    v.push_back(4);                       // 末尾追加
    v.pop_back();                          // 末尾删除
    v.insert(v.begin() + 1, 99);          // 在下标 1 处插入

    std::cout << "size=" << v.size() << ", empty="
              << v.empty() << std::endl;

    // 二维 vector:矩阵
    std::vector<std::vector<int>> mat({{1,2},{3,4}});
}

记住:size()empty()push_backpop_backclearresize 是最高频操作。

7.3 std::array / 字符串与 C 字符串的转换

C++array.cpp
#include <array>
#include <string>
#include <cstring>      // C 字符串函数(strlen 等)

std::array<int, 5> a = {1,2,3,4,5};   // 固定长度数组,有 size()、at()

// string ↔ C 风格字符串
std::string s = "hi";
const char* c = s.c_str();              // string → C-string
std::string s2 = c;                         // C-string → string

指针、引用与内存

指针继承自 C;引用是 C++ 新增的"安全别名" —— 在 OOP 中频繁使用。

8.1 指针 vs 引用 ⭐

特性指针 (T*)引用 (T&)
是否可以为空可以(nullptr)不可以(必须绑定到对象)
能否重新绑定可以(p = &x)不可以(终身绑定到初始对象)
访问方式*p 解引用直接使用,与原变量同义
是否需要取地址需要 &不需要
使用建议表示"可能为空/可变的资源"表示"必然存在、不变的别名"
C++ref.cpp
int a = 10;
int& ref = a;       // 引用:ref 是 a 的别名
int* p   = &a;      // 指针:p 存储 a 的地址

ref = 20;             // a 现在是 20
*p  = 30;             // a 现在是 30

8.2 空指针:nullptr C++11

现代 C++ 用 nullptr 取代 C 的 NULL,类型更安全:

C++nullptr.cpp
int* p = nullptr;          // 推荐
// int* p = NULL;             // 不推荐(C 风格宏)

if (p == nullptr) { /* 安全检查 */ }
// 或更简洁(C++17)
if (!p) { /* ... */ }

8.3 const 引用:避免拷贝 + 保证只读

C++const_ref.cpp
// 1. 避免大对象拷贝
void print(const std::vector<int>& v) { /* 只读 */ }

// 2. 绑定到临时对象(右值)
std::string const& r = "literal" + "suffix";

// 3. const 引用延长临时对象生命周期
const int& x = 42;            // OK,普通引用不行
09

类与对象 ⭐⭐⭐ —— C++ 的核心

面向对象(OOP)让代码从"操作裸数据"升级到"操作有行为的数据" —— 是 C++ 区别于 C 的关键。

9.1 面向对象思想:对象 = 属性 + 行为

  • 对象(Object):现实世界事物的抽象(如学生、汽车);
  • 属性(Attribute):对象的状态(姓名、年龄);
  • 行为(Behavior):对象能做什么(study, drive);
  • (Class):对象的"模板",描述一类事物的共性。

OOP 三大特性:封装(数据 + 行为打包,隐藏细节) · 继承(子类复用父类) · 多态(同一接口,不同实现)。

9.2 定义一个 Student 类

C++Student.h
#include <string>

class Student {
public:                              // 公开:外部可访问
    Student();                              // 默认构造函数
    Student(const std::string& name, int age);
    ~Student();                             // 析构函数

    void study() const;
    void print() const;

    std::string getName() const { return name_; }   // 内联实现
    int         getAge()  const { return age_;  }

private:                             // 私有:外部不可访问(封装)
    std::string name_;
    int         age_;
};
C++Student.cpp
#include <iostream>
#include "Student.h"

Student::Student() : name_("Unknown"), age_(0) {}    // 初始化列表

Student::Student(const std::string& n, int a)
    : name_(n), age_(a) {}

Student::~Student() { std::cout << name_ << " 毕业\n"; }

void Student::study() const {
    std::cout << name_ << " 正在学习\n";
}

void Student::print() const {
    std::cout << "姓名:" << name_ << ",年龄 " << age_ << std::endl;
}
C++main.cpp
int main() {
    Student s1;                              // 默认构造
    Student s2("Alice", 20);              // 带参构造
    Student* p = new Student("Bob", 19); // 堆上构造
    s2.study();
    s2.print();
    delete p;
}

9.3 访问控制:public / private / protected

  • public:外部可见,常放"接口";
  • private:仅本类内部可见,常放"数据成员"(默认访问级别);
  • protected:本类与子类可见(用于继承)。
封装原则:数据成员尽量 private,对外暴露 public 的 getter/setter(必要时加约束)。

9.4 this 指针 & const 成员函数

C++this_const.cpp
class Box {
public:
    // this 指向当前对象的指针(类型 Box* const)
    Box& setW(int w) { this->w_ = w; return *this; }

    // const 成员函数:承诺不修改成员(可被 const 对象调用)
    int volume() const { return w_ * h_ * d_; }

private:
    int w_ = 1, h_ = 1, d_ = 1;
};
规则:不修改成员状态的成员函数都应该加 const,否则 const 对象无法调用它们。
10

继承与多态 ⭐⭐⭐

让类形成"父子层级" —— 子类复用父类代码,多态让同一接口表现不同行为。

10.1 继承语法

C++inherit.cpp
class Person {                       // 基类(父类)
public:
    void sayHi() { std::cout << "Hi!\n"; }
};

class Student : public Person {      // 派生类(子类)
public:
    void study() { std::cout << "学习\n"; }
};

Student s;
s.sayHi();   // 继承自 Person
s.study();   // 自己定义
继承方式基类 public 成员基类 protected 成员
public 继承仍为 public仍为 protected
protected 继承变为 protected仍为 protected
private 继承变为 private变为 private
经验法则:几乎所有场景都用 public 继承 —— 它表达"is-a"语义。其他方式罕用。

10.2 多态:虚函数与运行时绑定

通过基类指针/引用调用虚函数,实际执行的是派生类的版本 —— 这就是多态。

C++poly.cpp
#include <iostream>

class Animal {
public:
    virtual void speak() const {       // 虚函数
        std::cout << "...\n";
    }
    virtual ~Animal() = default;       // 虚析构(必须!)
};

class Dog : public Animal {
public:
    void speak() const override {         // override:编译期校验
        std::cout << "汪汪!\n";
    }
};

class Cat : public Animal {
public:
    void speak() const override {
        std::cout << "喵喵!\n";
    }
};

int main() {
    Animal* p = new Dog;
    p->speak();   // 汪汪!(运行时绑定到 Dog::speak)
    delete p;
}

10.3 抽象类与纯虚函数

C++abstract.cpp
class Shape {                       // 抽象类
public:
    virtual double area() const = 0;  // 纯虚函数
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double r_;
public:
    Circle(double r) : r_(r) {}
    double area() const override { return 3.14159 * r_ * r_; }
};

// Shape s;        // ❌ 不能实例化抽象类
Shape* p = new Circle(2);   // ✔ 通过派生类指针使用
铁律:凡是多态基类,必须有虚析构函数 virtual ~Base()。否则通过基类指针 delete 派生类对象时,派生类的析构函数不会被调用,资源会泄漏。
11

运算符重载:让自定义类型像内置类型一样自然

通过 operator+operator<< 等让自定义类支持熟悉的运算符。

11.1 重载算术与流运算符

C++overload.cpp
#include <iostream>

struct Vec2 {
    double x, y;

    // 成员函数版:+=
    Vec2& operator+=(const Vec2& rhs) {
        x += rhs.x; y += rhs.y; return *this;
    }
};

// 非成员函数版:+ 与 <<
Vec2 operator+(Vec2 a, const Vec2& b) {
    return {a.x + b.x, a.y + b.y};
}

std::ostream& operator<<(std::ostream& os, const Vec2& v) {
    return os << "(" << v.x << ", " << v.y << ")";
}

int main() {
    Vec2 a{1,2}, b{3,4};
    std::cout << (a + b) << std::endl;   // (4, 6)
}
规则:=()[]-> 只能作为成员函数重载;二元对称运算符(如 +<<)通常作为非成员函数
12

模板与泛型编程 ⭐⭐

"一次编写,多种数据类型使用" —— STL 的基石。

12.1 函数模板 & 类模板

C++template.cpp
#include <iostream>

// 函数模板:T 是类型参数
template <typename T>
T add(T a, T b) { return a + b; }

// 类模板:通用的"盒子"
template <typename T>
class Box {
    T value_;
public:
    Box(T v) : value_(v) {}
    T get() const { return value_; }
};

int main() {
    std::cout << add(3, 5)            << "\n";   // 8(int)
    std::cout << add(1.5, 2.5)        << "\n";   // 4(double)

    Box<int>    bi(42);
    Box<std::string> bs("hello");
}

12.2 C++20 Concepts C++20

Concepts 让模板参数有"明确的约束" —— 编译错误信息更友好:

C++concept.cpp (C++20)
#include <concepts>

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

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

STL 标准模板库 ⭐⭐⭐

STL = 容器(存数据)+ 算法(处理数据)+ 迭代器(连接两者)+ 函数对象 + 适配器

13.1 常用容器速查表

容器特点典型用途
vector动态数组,随机访问 O(1),末尾增删 O(1)默认首选序列容器
deque双端队列,两端增删 O(1)队列、滑动窗口
list双向链表,任意位置增删 O(1),无随机访问频繁中间插入/删除
array固定大小数组,无额外开销替代 C 风格数组
forward_list单向链表节省内存
stackLIFO 栈(基于 deque)DFS、表达式求值
queueFIFO 队列BFS、任务调度
priority_queue堆(默认最大堆)Top-K、Dijkstra
set / multiset有序集合(红黑树)去重 + 排序
map / multimap有序键值对字典、索引
unordered_set哈希集合,O(1) 平均快速查找
unordered_map 哈希键值对,O(1) 平均最常用 map

13.2 unordered_map:用例 ⭐

C++umap.cpp
#include <unordered_map>
#include <iostream>
#include <string>

int main() {
    std::unordered_map<std::string, int> scores;

    // 增
    scores["Alice"] = 95;
    scores["Bob"]   = 87;
    scores.insert({"Carol", 92});

    // 查
    auto it = scores.find("Alice");
    if (it != scores.end())
        std::cout << "Alice = " << it->second << std::endl;

    // 遍历(C++17 结构化绑定)
    for (const auto& [name, score] : scores)
        std::cout << name << ": " << score << std::endl;

    // 删
    scores.erase("Bob");
}
14

STL 算法:<algorithm> 与迭代器

STL 提供 ~100 个通用算法,配合迭代器对任意容器工作。

14.1 高频算法速查

C++algorithm.cpp
#include <algorithm>
#include <vector>
#include <numeric>     // accumulate
#include <iostream>

int main() {
    std::vector<int> v = {5, 2, 8, 1, 9, 3};

    std::sort(v.begin(), v.end());                 // 升序
    std::reverse(v.begin(), v.end());              // 反转

    auto it = std::find(v.begin(), v.end(), 8);   // 查找
    int  cnt = std::count(v.begin(), v.end(), 5); // 计数

    int sum = std::accumulate(v.begin(), v.end(), 0);

    bool found = std::binary_search(v.begin(), v.end(), 8);

    // 自定义比较器:降序
    std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
}

记住:STL 算法默认作用于"半开区间" [begin, end),传迭代器对,不依赖具体容器类型。

15

Lambda 表达式 ⭐

C++11 引入的匿名函数对象 —— 让 STL 算法、自定义回调变得极其简洁。

15.1 Lambda 语法

C++lambda.cpp
[捕获列表] (参数) -> 返回类型 { 函数体 }

// 最简形式
auto hello = []() { std::cout << "Hi\n"; };

// 带参数和返回值
auto add = [](int a, int b) -> int { return a + b; };

// 与 STL 算法配合
std::sort(v.begin(), v.end(),
      [](int a, int b) { return a > b; });   // 降序

15.2 捕获列表:访问外部变量

C++capture.cpp
int x = 10, y = 20;

[]            // 不捕获任何外部变量
[x, y]        // 值捕获(拷贝)
[&x, &y]     // 引用捕获(可修改)
[=]           // 值捕获所有用到的外部变量
[&]           // 引用捕获所有用到的外部变量
[=, &x]      // 默认值捕获,但 x 用引用
[this]        // 捕获当前类的 this 指针

// 例:计数器
int cnt = 0;
auto inc = [&]() { return ++cnt; };
inc(); inc();    // cnt 现在是 2
16

智能指针与 RAII ⭐⭐⭐

现代 C++ 的核心安全机制 —— 让"忘记 delete"成为历史。

16.1 传统 new/delete 的痛点

常见 bug:忘记 delete 导致内存泄漏;异常发生时跳过 delete;同一块内存 delete 两次。

16.2 三种智能指针

智能指针所有权典型场景
unique_ptr<T>独占所有权(不可复制,可移动)默认首选,替代裸 new/delete
shared_ptr<T>共享所有权(引用计数)资源被多处共享、生命周期不确定
weak_ptr<T>不增加引用计数,可查 shared_ptr 是否存活解决循环引用
C++smart.cpp
#include <memory>

// 1) unique_ptr:独占(推荐首选)
auto p1 = std::make_unique<int>(42);
// std::unique_ptr p2 = p1;       // ❌ 不可复制
auto p2 = std::move(p1);                // ✔ 可移动

// 2) shared_ptr:共享
auto sp1 = std::make_shared<std::string>("hi");
auto sp2 = sp1;                          // 引用计数=2

// 3) weak_ptr:观察但不拥有
std::weak_ptr<std::string> wp = sp1;
if (auto locked = wp.lock()) {       // 提升为 shared_ptr
    // 使用 *locked
}

16.3 RAII:现代 C++ 的核心思想

Resource Acquisition Is Initialization:资源在构造函数获取、在析构函数释放。让对象生命周期 = 资源生命周期。

C++raii.cpp
class File {
    FILE* fp_;
public:
    File(const char* path) : fp_(fopen(path, "r")) {}
    ~File() { if (fp_) fclose(fp_); }   // 自动关闭
    // 禁止拷贝或转移所有权
    File(const File&) = delete;
    File& operator=(const File&) = delete;
};

void read() {
    File f("data.txt");    // 打开;离开作用域自动关闭
    // 即使抛异常,析构也会执行
}
17

文件 I/O 与异常处理

C++ 用流式对象读写文件 —— 比 C 的 fopen 更类型安全。

17.1 文件流:ifstream / ofstream

C++file.cpp
#include <fstream>
#include <string>

int main() {
    // 写文本
    std::ofstream out("out.txt");
    if (!out) return 1;
    out << "Hello, " << 42 << std::endl;

    // 读文本(按行)
    std::ifstream in("out.txt");
    std::string line;
    while (std::getline(in, line))
        std::cout << line << "\n";

    // 二进制:reinterpret_cast + read/write
    int arr[3] = {1, 2, 3};
    std::ofstream bin("a.bin", std::ios::binary);
    bin.write(reinterpret_cast<const char*>(arr), sizeof arr);
}

17.2 异常处理:try / catch / throw

C++trycatch.cpp
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0)
        throw std::runtime_error("除数不能为零");
    return a / b;
}

int main() {
    try {
        std::cout << divide(10, 0);
    } catch (const std::runtime_error& e) {
        std::cerr << "错误:" << e.what() << std::endl;
    } catch (std::exception& e) {
        std::cerr << "其它异常:" << e.what() << std::endl;
    } catch (...) {
        std::cerr << "未知异常\n";
    }
}
建议:异常用于真正异常情况(不是正常控制流);构造函数失败应抛异常;析构函数应 noexcept
18

数据结构与 STL 实现

STL 不仅提供容器,还让我们能学到"如何用 C++ 实现数据结构"。

18.1 单链表实现

C++linkedlist.cpp
#include <iostream>

template <typename T>
struct Node {
    T     data;
    Node* next;
    Node(T d, Node* n = nullptr) : data(d), next(n) {}
};

template <typename T>
class LinkedList {
    Node<T>* head_ = nullptr;
public:
    ~LinkedList() { while (head_) { auto t = head_; head_ = head_->next; delete t; } }

    void push_front(T v) { head_ = new Node<T>(v, head_); }

    void print() const {
        for (auto p = head_; p; p = p->next)
            std::cout << p->data << " -> ";
        std::cout << "nullptr\n";
    }
};
19

算法设计基础

C++ 是练习算法的最佳语言 —— STL 让算法实现变得简洁。

19.1 二分查找(STL 版)

C++bs.cpp
#include <vector>
#include <algorithm>

std::vector<int> v = {1, 3, 5, 7, 9};

bool ok = std::binary_search(v.begin(), v.end(), 7);

auto lo = std::lower_bound(v.begin(), v.end(), 5);  // 第一个 ≥5
auto hi = std::upper_bound(v.begin(), v.end(), 5);  // 第一个 >5

19.2 快速排序(递归)

C++qsort.cpp
void quickSort(std::vector<int>& a, int lo, int hi) {
    if (lo >= hi) return;
    int pivot = a[(lo + hi) / 2];
    int i = lo, j = hi;
    while (i <= j) {
        while (a[i] < pivot) i++;
        while (a[j] > pivot) j--;
        if (i <= j) std::swap(a[i++], a[j--]);
    }
    quickSort(a, lo, j);
    quickSort(a, i, hi);
}

实战建议:手写快速排序理解原理;实战用 std::sort(经过精心优化的 introsort)。

20

C++ 工程化:多文件、CMake 与 Git

从"脚本式编程"升级到"工程化开发" —— 学会组织一个真正的 C++ 项目。

20.1 多文件项目结构

tree项目结构
my_project/
├── CMakeLists.txt
├── include/
│   └── Student.h
├── src/
│   ├── Student.cpp
│   └── main.cpp
├── build/               // 编译产物
└── README.md
C++Student.h(推荐:#pragma once)
#pragma once
#include <string>

class Student {
public:
    Student(const std::string& name, int age);
    std::string getName() const;
private:
    std::string name_;
    int age_;
};
C++Student.cpp
#include "Student.h"

Student::Student(const std::string& n, int a)
    : name_(n), age_(a) {}

std::string Student::getName() const { return name_; }
C++main.cpp
#include <iostream>
#include "Student.h"

int main() {
    Student s("Alice", 20);
    std::cout << s.getName();
}

20.2 CMakeLists.txt(多文件)

CMakeCMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(my_project LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(include)        # 头文件搜索路径

add_executable(app
    src/main.cpp
    src/Student.cpp
)

20.3 Git 版本管理(与 C 项目学习对应)

强烈建议把每个 C++ 项目都用 Git 管理 —— 提交粒度建议"一个功能/一次提交"。

shellgit
$ git init
$ git add .
$ git commit -m "feat: 完成 Student 类的基本实现"
$ git log --oneline                # 查看历史
21

调试与代码质量

写出能跑的代码只是开始 —— 能调试才叫工程能力。

21.1 常见错误类型

阶段典型错误
编译错误语法错误、类型不匹配、未声明标识符
链接错误undefined reference to 'xxx'
运行错误段错误(Segfault)、栈溢出、迭代器失效
逻辑错误程序能跑但结果不对

21.2 C++ 专属常见 bug

  • 忘记虚析构:基类指针 delete 派生类对象 → 派生部分不析构;
  • 对象切片:值传递派生类对象给基类参数,多态失效;
  • 悬空引用:函数返回局部变量的引用;
  • 迭代器失效:vector 扩容后老迭代器全部失效;
  • double free:同一智能指针被多次释放;
  • 拷贝导致性能问题:大对象传值而非 const 引用;
  • 头文件重复包含:必须用 #pragma once 或 include guard。

21.3 现代 C++ 代码规范建议

  • 命名:类 PascalCase,函数/变量 camelCase,常量 kCamelUPPER_CASE
  • 成员变量后缀下划线name_)方便区分;
  • 优先 const / constexpr:表达"不变"语义,编译器也能优化;
  • 避免裸 new/delete:用智能指针;
  • 零规则(Rule of Zero):能用编译器生成的特殊成员函数就用,别手写;
  • RAII:所有资源都用对象管理;
  • const 正确性:能加 const 就加。
22

综合项目:从语法到作品

以下项目按难度递进,建议至少完成前 3 个。

项目一:命令行计算器 🧮

难度 ★☆☆ · 函数 / switch / 类

支持加减乘除与括号;用类封装;可选用函数指针或 std::function 实现调度。

项目二:学生成绩管理系统 📊

难度 ★★★ · 类 / vector / string / sort / 文件

Student 类保存一名学生,vector<Student> 保存多名;支持增删改查、按成绩排序、文件持久化。

项目三:通讯录系统 📞

难度 ★★★★ · 类 / STL / 文件 / 搜索

每条联系人含姓名、电话、邮箱;用 unordered_map 实现 O(1) 查找;支持模糊搜索;CSV 持久化。

项目四:图书借阅系统 📚

难度 ★★★★ · OOP / STL / 文件 / 异常

含 Book 与 Borrower 类;用继承与多态表达不同种类图书(教材/小说/工具书);支持借还状态机。

项目五:银行账户系统 🏦

难度 ★★★★★ · 继承 / 多态 / 封装 / 文件

基类 Account;派生 SavingsAccountCheckingAccountCreditAccount;多态调用利息计算;交易记录到文件。

项目六:简单文本编辑器 ✏️

难度 ★★★★ · string / vector / 文件 / STL

支持新建/打开/编辑/保存/查找替换;用 string 做 buffer,vector<string> 做多行。

项目七:综合 —— 学生选课数据库 🎓

难度 ★★★★★ · 全面综合(OOP + STL + 文件 + 异常 + CMake + Git)

多文件工程:Student / Course / Enrollment 三个类,vector/map 组合;文件持久化 + 异常处理 + CMake 构建 + Git 版本管理。

综合项目学习建议
  • 从最小可用版本(MVP)开始,逐步加功能;
  • 每完成一个功能就编译运行一次,不要攒到最后;
  • 用 Git 做版本管理,每个功能/修复一次提交;
  • 优先使用 STL 与智能指针,避免裸指针;
  • 遇到 bug 先复现,再定位,再修复;完成后写 README。

C++ 速查手册(Cheat Sheet)

写代码时随手翻一翻 —— 比每次去搜更快。

常用关键字(C++17 共 84 个)

alignasalignofautoboolbreakcasecatchcharclassconstconstexprcontinuedecltypedefaultdeletedodoubleelseenumexplicitexportexternfalsefinalfloatforfriendgotoifinlineintlongmutablenamespacenewnoexceptnullptroperatoroverrideprivateprotectedpublicregisterreinterpret_castreturnshortsignedsizeofstaticstatic_assertstatic_caststructswitchtemplatethisthread_localthrowtruetrytypeidtypenameunionunsignedusingvirtualvoidvolatilewchar_twhile

常用容器选择速查

需求推荐容器
通用序列std::vector<T>
头尾都频繁增删std::deque<T>
中间频繁插入/删除std::list<T>
std::stack<T>
队列std::queue<T>
优先队列 / 堆std::priority_queue<T>
有序去重集合std::set<T>
哈希集合std::unordered_set<T>
有序键值对std::map<K, V>
哈希键值对std::unordered_map<K, V>
固定大小数组std::array<T, N>

常用头文件

头文件内容
<iostream>cout / cin / cerr / endl
<string>std::string
<vector> / <list> / <deque> / <array>序列容器
<map> / <set> / <unordered_map> / <unordered_set>关联容器
<algorithm> / <numeric>STL 算法
<functional>std::function / std::bind / 哈希
<memory>智能指针、allocator
<fstream>文件流
<sstream>字符串流
<stdexcept>标准异常类
<tuple>std::tuple / std::pair
<optional> C++17std::optional<T>
<variant> C++17std::variant
<filesystem> C++17文件操作
<chrono>时间
<thread> / <mutex>多线程(C++11)
<ranges> C++20std::ranges
<concepts> C++20Concepts
<coroutine> C++20协程

常用代码模板(剪贴即用)

C++常见模式
// 1) RAII 文件句柄
std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("a.txt", "r"), &fclose);

// 2) 读整行
std::getline(std::cin, line);

// 3) split 字符串
std::istringstream iss(s); std::string tok;
while (iss >> tok) { /* ... */ }

// 4) 类型擦除的函数包装
std::function<int(int,int)> f = [](int a, int b){ return a + b; };

// 5) 时间测量
auto t0 = std::chrono::steady_clock::now();
// ... do work ...
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::steady_clock::now() - t0).count();

常见错误自查清单

  • vector 边遍历边 push_back —— 可能导致迭代器失效
  • ❌ 派生类对象值赋给基类变量 —— 对象切片,多态失效
  • ❌ 函数返回局部对象的引用 —— 悬空引用
  • ❌ 基类析构函数不是 virtual —— 通过基类指针 delete 派生对象时资源泄漏
  • auto p = new T; 而不用 make_unique
  • ❌ 大对象传值而非 const T&
  • ❌ 头文件没用 #pragma once 或 include guard
  • ❌ 忘记在 .cpp 中 include 对应 .h
@media print{ #topnav,#progress,.back-top{display:none!important} .hero{padding:20px 0!important;background:none!important;color:#000!important} .hero h1{color:#000!important} .hero .sub,.hero .hero-chips{color:#333!important} section{break-inside:avoid;box-shadow:none!important;border:1px solid #ccc!important} pre{background:#f5f5f5!important;color:#000!important} }