C++最全面复习:从入门到精通(2026年)

写在前面:C++是我学编程以来觉得"又爱又恨"的一门语言。爱它是因为它性能无敌------操作系统、游戏引擎、高频交易、数据库内核、自动驾驶,这些对性能要求极致的领域几乎都是C++的天下。恨它是因为它太复杂了------指针、引用、模板、智能指针、移动语义、多线程......每一个都能让你debug到凌晨三点。这篇文章把C++的核心知识体系完整梳理了一遍,从基础语法到现代C++20,从面向对象到模板元编程,从STL到多线程并发,面试常问的、工作中真正用到的、初学者最容易踩坑的地方,我都尽量讲透了。全文大概两万多字,建议先收藏再看,当字典用也行。

文章目录


一、C++到底学什么?先给你一张全景图

C++的知识体系非常庞大,这也是它被戏称为"大而全"语言的原因。但核心知识可以归纳为以下八大板块

板块 核心内容 对应文章章节 难度
基础语法与类型系统 基本类型、auto、decltype、引用、const、constexpr、作用域与命名空间 第二章 ⭐⭐
指针、引用与内存管理 指针与数组、智能指针(unique/shared/weak)、new/delete、内存模型 第六章 ⭐⭐⭐
面向对象编程 封装、继承、多态、虚函数与vtable、运算符重载、多重继承 第三章 ⭐⭐⭐
模板与泛型编程 函数模板、类模板、模板特化、SFINAE、C++20 Concepts 第四章 ⭐⭐⭐⭐
STL标准模板库 容器(vector/map/set)、迭代器、算法、string/string_view 第五章 ⭐⭐⭐
现代C++特性(11/14/17/20) Lambda表达式、移动语义、optional/variant/any、结构化绑定、协程 第七章 ⭐⭐⭐⭐
多线程与并发编程 thread、mutex、async/future、atomic、无锁编程 第八章 ⭐⭐⭐⭐⭐
异常处理与RAII 异常机制、RAII编程范式、资源管理、智能指针原理 第九章 ⭐⭐⭐

学习建议:建议按表格顺序学习,前面的板块是后面的基础。标⭐⭐的先掌握,标⭐⭐⭐⭐⭐的可以放到后面深入研究。

二、基础语法:C++比C多了什么?

2.1 引用:C++独有的语法糖

引用是C++最重要的特性之一,很多人搞不清引用和指针的区别。

cpp 复制代码
#include <iostream>

// 值传递:修改不影响原变量
void swap_by_value(int a, int b) {
    int temp = a; a = b; b = temp;
    // 函数结束后,外面的a和b不受影响
}

// 指针传递:通过地址修改原变量
void swap_by_ptr(int *a, int *b) {
    int temp = *a; *a = *b; *b = temp;
}

// 引用传递:最简洁的方式
void swap_by_ref(int &a, int &b) {
    int temp = a; a = b; b = temp;
}

int main() {
    int x = 3, y = 5;
    
    swap_by_value(x, y);
    std::cout << "值传递后: x=" << x << ", y=" << y << std::endl; // x=3, y=5(没变)
    
    swap_by_ptr(&x, &y);
    std::cout << "指针传递后: x=" << x << ", y=" << y << std::endl; // x=5, y=3
    
    swap_by_ref(x, y);
    std::cout << "引用传递后: x=" << x << ", y=" << y << std::endl; // x=3, y=5
    
    return 0;
}

引用 vs 指针对比

特性 引用(&) 指针(*)
必须初始化 ✅ 声明时必须绑定 ❌ 可以先声明后赋值
可以为空 ❌ 不存在空引用 ✅ 可以是nullptr
可以重新绑定 ❌ 一生只绑定一个对象 ✅ 可以指向不同对象
语法 使用时像普通变量 需要解引用*p
sizeof 绑定对象的大小 指针本身的大小(8字节)
底层实现 实际上是指针 就是指针

踩坑提醒 :引用必须在声明时初始化,且一旦绑定就不能更改。但很多人不知道,引用在底层其实也是用指针实现的,编译器帮你做了自动解引用。所以引用传递本质上还是传地址,只是语法更干净。面试官经常问"引用和指针的区别",记住上面这张表就够了。

2.2 const的四种用法

cpp 复制代码
#include <iostream>

int main() {
    int val = 42;
    
    // 1. const变量
    const int MAX = 100;
    // MAX = 200;  // 编译错误
    
    // 2. const指针(4种组合,面试必问)
    const int *p1 = &val;      // 指向const int的指针(值不能改,指针能改)
    // *p1 = 43;  // 编译错误
    p1 = nullptr;  // 可以
    
    int *const p2 = &val;      // const指针(值能改,指针不能改)
    *p2 = 43;     // 可以
    // p2 = nullptr;  // 编译错误
    
    const int *const p3 = &val; // 都不能改
    
    // 3. const引用(常用于函数参数,避免拷贝同时不允许修改)
    // void print(const std::string &s);  // 传引用避免拷贝,const保证不修改
    
    // 4. const成员函数(承诺不修改对象状态)
    // class MyClass { int getValue() const; };
    
    // 5. constexpr(C++11,编译期常量)
    constexpr int SIZE = 10;
    int arr[SIZE];  // 合法,SIZE是编译期常量
    // const int SIZE2 = 10;  // 也是编译期常量(初始化为字面量时)
    
    return 0;
}

2.3 auto与decltype(C++11)

cpp 复制代码
#include <iostream>
#include <vector>
#include <type_traits>

int main() {
    // auto:编译器自动推导类型
    auto x = 42;        // int
    auto pi = 3.14;      // double
    auto name = "hello"; // const char*
    auto list = {1, 2, 3}; // std::initializer_list<int>
    
    // auto与引用
    int a = 10;
    auto &ref = a;       // int&(auto推导为int,加上&就是引用)
    auto ptr = &a;       // int*(auto推导为int*)
    
    // decltype:获取表达式的类型
    int b = 20;
    decltype(a) c = 30;   // c的类型和a一样,都是int
    decltype(a + b) d = 50; // d的类型是int(a+b的结果类型)
    decltype(a * 1.5) e = 0; // e的类型是double
    
    // auto vs decltype的区别
    const int ci = 10;
    auto f = ci;           // f是int(auto忽略const和引用)
    decltype(ci) g = 20;  // g是const int(decltype保留const)
    
    // C++14: auto返回类型
    auto add = [](int a, int b) -> auto { return a + b; }; // C++14可省略->auto
    
    std::cout << "x=" << x << ", pi=" << pi << std::endl;
    
    return 0;
}

三、面向对象:C++的核心竞争力

3.1 类与对象:封装

cpp 复制代码
#include <iostream>
#include <string>
#include <stdexcept>

class BankAccount {
private:
    std::string owner;
    double balance;
    
public:
    // 构造函数(初始化列表,比在函数体内赋值更高效)
    BankAccount(const std::string& name, double initial) 
        : owner(name), balance(initial) {
        if (initial < 0) throw std::invalid_argument("初始余额不能为负");
    }
    
    // 析构函数(virtual析构函数很重要!)
    virtual ~BankAccount() = default;
    
    // 存款
    void deposit(double amount) {
        if (amount <= 0) throw std::invalid_argument("存款金额必须大于0");
        balance += amount;
    }
    
    // 取款
    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
    
    // const成员函数:不修改对象状态
    double getBalance() const { return balance; }
    const std::string& getOwner() const { return owner; }
    
    // 友元函数(可以访问私有成员)
    friend std::ostream& operator<<(std::ostream& os, const BankAccount& acc);
};

// 运算符重载:输出流
std::ostream& operator<<(std::ostream& os, const BankAccount& acc) {
    os << acc.owner << "的余额: " << acc.balance;
    return os;
}

int main() {
    BankAccount acc("张三", 1000.0);
    acc.deposit(500.0);
    acc.withdraw(200.0);
    
    std::cout << acc << std::endl;  // 张三的余额: 1300
    
    return 0;
}

3.2 继承与多态:虚函数的奥秘

cpp 复制代码
#include <iostream>
#include <vector>
#include <memory>

// 基类:虚析构函数!
class Animal {
protected:
    std::string name;
public:
    Animal(const std::string& n) : name(n) {}
    
    // 虚函数:运行时多态
    virtual void speak() const {
        std::cout << name << "发出声音" << std::endl;
    }
    
    // 纯虚函数:使Animal成为抽象类
    virtual void move() const = 0;
    
    // 虚析构函数:确保正确释放派生类对象(面试必问!)
    virtual ~Animal() { std::cout << "~Animal()" << std::endl; }
};

class Dog : public Animal {
public:
    Dog(const std::string& n) : Animal(n) {}
    
    // override关键字:显式重写(C++11),编译器检查是否真的重写了
    void speak() const override {
        std::cout << name << ": 汪汪汪!" << std::endl;
    }
    
    void move() const override {
        std::cout << name << "在跑" << std::endl;
    }
    
    // final关键字:禁止进一步重写
    void fetch() const final {
        std::cout << name << "在捡球" << std::endl;
    }
    
    ~Dog() override { std::cout << "~Dog()" << std::endl; }
};

class Cat : public Animal {
public:
    Cat(const std::string& n) : Animal(n) {}
    
    void speak() const override {
        std::cout << name << ": 喵喵喵!" << std::endl;
    }
    
    void move() const override {
        std::cout << name << "在跳" << std::endl;
    }
    
    ~Cat() override { std::cout << "~Cat()" << std::endl; }
};

int main() {
    // 多态:基类指针指向派生类对象
    std::vector<std::unique_ptr<Animal>> zoo;
    zoo.push_back(std::make_unique<Dog>("旺财"));
    zoo.push_back(std::make_unique<Cat>("咪咪"));
    zoo.push_back(std::make_unique<Dog>("大黄"));
    
    for (const auto& animal : zoo) {
        animal->speak();  // 运行时决定调用哪个版本
        animal->move();
    }
    // 输出:
    // 旺财: 汪汪汪!
    // 旺财在跑
    // 咪咪: 喵喵喵!
    // 咪咪在跳
    // 大黄: 汪汪汪!
    // 大黄在跑
    
    // unique_ptr自动释放,不需要手动delete
    return 0;
}

面试高频 :面试官几乎必问"为什么析构函数要声明为virtual?"如果不声明为virtual,通过基类指针delete派生类对象时,只会调用基类的析构函数,派生类的资源不会被释放,导致内存泄漏 。这条规则:只要类中有虚函数,析构函数就必须是virtual的

3.3 虚函数表(vtable):多态的底层原理

复制代码
虚函数表原理:

每个有虚函数的类都有一个虚函数表(vtable):

┌─────────────────────────────────────┐
│  Dog的虚函数表 (vtable)            │
├─────────────────────────────────────┤
│  [0] Dog::speak() 的地址           │
│  [1] Dog::move() 的地址            │
│  [2] ~Dog() 的地址                 │
└─────────────────────────────────────┘

每个对象有一个指向虚函数表的指针(vptr):

┌─────────────────────────────────────┐
│  Dog对象                           │
├─────────────────────────────────────┤
│  vptr ─────────────→ vtable        │
│  name: "旺财"                      │
│  (Animal的成员)                     │
└─────────────────────────────────────┘

调用 animal->speak() 的过程:
1. 通过对象的 vptr 找到 vtable
2. 在 vtable 中找到 speak() 的地址
3. 调用对应的函数

虚函数调用的开销:
- 多一次间接寻址(查vtable)
- 通常影响很小(< 5%)
- 但在极端性能敏感的场景下需要考虑

3.4 多重继承与菱形问题

cpp 复制代码
#include <iostream>

// 菱形继承问题
class Animal {
public:
    int age;
    Animal() : age(0) { std::cout << "Animal()" << std::endl; }
};

// 虚继承:解决菱形问题
class Mammal : virtual public Animal {
public:
    Mammal() { std::cout << "Mammal()" << std::endl; }
};

class Bird : virtual public Animal {
public:
    Bird() { std::cout << "Bird()" << std::endl; }
};

class Bat : public Mammal, public Bird {
public:
    Bat() { std::cout << "Bat()" << std::endl; }
};

int main() {
    Bat bat;
    bat.age = 5;  // 不使用virtual继承时,这里会报错:age不明确
    // 使用virtual继承后,只有一个Animal子对象,age是唯一的
    
    return 0;
}
// 输出:
// Animal()
// Mammal()
// Animal()  ← 不用virtual只调用一次
// Bird()
// Bat()

经验之谈 :多重继承在实际项目中用得很少,容易引入复杂性。C++核心指南的建议是:优先使用组合,谨慎使用继承,避免多重继承。如果确实需要多重继承,一定要用虚继承来避免菱形问题。


四、模板与泛型编程

4.1 函数模板

cpp 复制代码
#include <iostream>
#include <string>

// 函数模板:一个定义,多种类型
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

// 多个模板参数
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

// 模板特化:为特定类型提供特殊实现
template <>
const char* getMax<const char*>(const char* a, const char* b) {
    return (std::strcmp(a, b) > 0) ? a : b;
}

// C++11: 可变参数模板
void print() { std::cout << std::endl; }

template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    print(rest...);  // 递归展开
}

int main() {
    std::cout << getMax(3, 5) << std::endl;           // 5
    std::cout << getMax(3.14, 2.72) << std::endl;     // 3.14
    std::cout << getMax("abc", "def") << std::endl;   // def(使用特化版本)
    
    std::cout << add(3, 1.5) << std::endl;            // 4.5
    
    print(1, "hello", 3.14, "world");  // 1 hello 3.14 world
    
    return 0;
}

4.2 类模板

cpp 复制代码
#include <iostream>
#include <stdexcept>

// 类模板:实现一个简单的动态数组
template <typename T>
class MyVector {
private:
    T* data;
    size_t size_;
    size_t capacity_;
    
    void resize(size_t newCapacity) {
        T* newData = new T[newCapacity];
        for (size_t i = 0; i < size_; i++) {
            newData[i] = data[i];
        }
        delete[] data;
        data = newData;
        capacity_ = newCapacity;
    }
    
public:
    explicit MyVector(size_t initCapacity = 4) 
        : size_(0), capacity_(initCapacity) {
        data = new T[capacity_];
    }
    
    ~MyVector() { delete[] data; }
    
    // 禁止拷贝(简化示例)
    MyVector(const MyVector&) = delete;
    MyVector& operator=(const MyVector&) = delete;
    
    // 移动构造函数
    MyVector(MyVector&& other) noexcept 
        : data(other.data), size_(other.size_), capacity_(other.capacity_) {
        other.data = nullptr;
        other.size_ = 0;
        other.capacity_ = 0;
    }
    
    void push_back(const T& value) {
        if (size_ >= capacity_) resize(capacity_ * 2);
        data[size_++] = value;
    }
    
    // 移动语义版本的push_back
    void push_back(T&& value) {
        if (size_ >= capacity_) resize(capacity_ * 2);
        data[size_++] = std::move(value);
    }
    
    T& operator[](size_t index) {
        if (index >= size_) throw std::out_of_range("Index out of range");
        return data[index];
    }
    
    const T& operator[](size_t index) const {
        if (index >= size_) throw std::out_of_range("Index out of range");
        return data[index];
    }
    
    size_t size() const { return size_; }
};

int main() {
    MyVector<int> intVec;
    intVec.push_back(10);
    intVec.push_back(20);
    intVec.push_back(30);
    
    for (size_t i = 0; i < intVec.size(); i++) {
        std::cout << intVec[i] << " ";  // 10 20 30
    }
    std::cout << std::endl;
    
    MyVector<std::string> strVec;
    strVec.push_back("Hello");
    strVec.push_back("C++");
    strVec.push_back("Templates");
    
    for (size_t i = 0; i < strVec.size(); i++) {
        std::cout << strVec[i] << " ";  // Hello C++ Templates
    }
    std::cout << std::endl;
    
    return 0;
}

经验之谈:模板是C++泛型编程的基石,STL的全部容器和算法都是基于模板实现的。但模板的错误信息出了名的难读------一个简单的模板实例化错误可能输出几十行甚至上百行的编译错误。C++20引入的Concepts可以大幅改善这个问题。


五、STL标准模板库:C++的瑞士军刀

5.1 容器

cpp 复制代码
#include <iostream>
#include <vector>
#include <map>
#include <unordered_map>
#include <set>
#include <deque>
#include <stack>
#include <queue>
#include <algorithm>

int main() {
    // 1. vector:动态数组(最常用)
    std::vector<int> vec = {3, 1, 4, 1, 5, 9};
    vec.push_back(2);
    vec.emplace_back(6);  // C++11:原地构造,比push_back更高效
    
    // 排序 + 去重
    std::sort(vec.begin(), vec.end());
    auto last = std::unique(vec.begin(), vec.end());
    vec.erase(last, vec.end());
    
    std::cout << "vector: ";
    for (int v : vec) std::cout << v << " ";  // 1 2 3 4 5 6 9
    std::cout << std::endl;
    
    // 2. map:有序键值对(红黑树)
    std::map<std::string, int> ages;
    ages["Alice"] = 20;
    ages["Bob"] = 25;
    ages["Charlie"] = 22;
    
    // C++17结构化绑定
    for (const auto& [name, age] : ages) {
        std::cout << name << ": " << age << std::endl;
    }
    
    // 3. unordered_map:哈希表(O(1)查找)
    std::unordered_map<std::string, int> scores;
    scores["math"] = 95;
    scores["english"] = 88;
    
    // 4. set:自动排序且不重复
    std::set<int> s = {5, 3, 1, 4, 2, 3};  // 自动去重+排序
    std::cout << "set: ";
    for (int v : s) std::cout << v << " ";  // 1 2 3 4 5
    std::cout << std::endl;
    
    // 5. deque:双端队列
    std::deque<int> dq = {1, 2, 3};
    dq.push_front(0);  // 头部插入
    dq.push_back(4);   // 尾部插入
    
    // 6. priority_queue:优先队列(默认大顶堆)
    std::priority_queue<int> pq;
    pq.push(3); pq.push(1); pq.push(5); pq.push(2);
    while (!pq.empty()) {
        std::cout << pq.top() << " ";  // 5 3 2 1
        pq.pop();
    }
    std::cout << std::endl;
    
    return 0;
}

STL容器选择指南

场景 推荐容器 时间复杂度 说明
随机访问 vector O(1) 默认首选
频繁头尾操作 deque O(1) 双端队列
有序键值对 map O(log n) 红黑树
哈希键值对 unordered_map O(1) 最常用
有序不重复 set O(log n) 自动排序去重
优先队列 priority_queue O(log n) 堆实现
stack O(1) LIFO
队列 queue O(1) FIFO

5.2 迭代器与常用算法

cpp 复制代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <functional>

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7, 4, 6};
    
    // 排序
    std::sort(vec.begin(), vec.end());  // 升序
    std::sort(vec.begin(), vec.end(), std::greater<int>());  // 降序
    
    // 查找
    auto it = std::find(vec.begin(), vec.end(), 5);
    if (it != vec.end()) {
        std::cout << "找到5,位置: " << std::distance(vec.begin(), it) << std::endl;
    }
    
    // 二分查找(必须先排序)
    bool found = std::binary_search(vec.begin(), vec.end(), 7);
    std::cout << "二分查找7: " << (found ? "找到" : "未找到") << std::endl;
    
    // 统计
    int count = std::count(vec.begin(), vec.end(), 3);
    
    // 累加
    int sum = std::accumulate(vec.begin(), vec.end(), 0);
    std::cout << "总和: " << sum << std::endl;
    
    // 最大值/最小值
    auto [minIt, maxIt] = std::minmax_element(vec.begin(), vec.end());
    std::cout << "最小: " << *minIt << ", 最大: " << *maxIt << std::endl;
    
    // transform:对每个元素做变换
    std::vector<int> doubled;
    std::transform(vec.begin(), vec.end(), std::back_inserter(doubled),
                   [](int x) { return x * 2; });
    
    // remove_if + erase(删除满足条件的元素)
    vec.erase(
        std::remove_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; }),
        vec.end()
    );
    
    // for_each
    std::for_each(vec.begin(), vec.end(), [](int x) {
        std::cout << x << " ";
    });
    std::cout << std::endl;
    
    return 0;
}

六、智能指针:告别内存泄漏

6.1 为什么需要智能指针

cpp 复制代码
// 传统方式:手动管理内存,容易泄漏
void traditional_way() {
    int* p = new int(42);
    // ... 如果这里抛异常或提前return,p就泄漏了
    delete p;
}

// 智能指针:自动释放
void smart_way() {
    std::unique_ptr<int> p = std::make_unique<int>(42);
    // 不管怎么退出,p都会自动释放
}

6.2 三种智能指针

智能指针 所有权 可拷贝 使用场景
unique_ptr 独占 ❌ 不可拷贝,可移动 默认首选
shared_ptr 共享(引用计数) ✅ 可拷贝 多个对象共享同一资源
weak_ptr 不拥有 ✅ 可拷贝 打破shared_ptr循环引用
cpp 复制代码
#include <iostream>
#include <memory>

class Node {
public:
    int value;
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 用weak_ptr避免循环引用
    
    Node(int v) : value(v) {
        std::cout << "Node(" << value << ") 创建" << std::endl;
    }
    
    ~Node() {
        std::cout << "~Node(" << value << ") 销毁" << std::endl;
    }
};

int main() {
    // unique_ptr:独占所有权
    {
        auto p1 = std::make_unique<int>(42);
        std::cout << "*p1 = " << *p1 << std::endl;  // 42
        auto p2 = std::move(p1);  // 可以移动,不能拷贝
        std::cout << "*p2 = " << *p2 << std::endl;  // 42
    }  // p2自动释放
    
    // shared_ptr:共享所有权
    {
        auto sp1 = std::make_shared<int>(100);
        std::cout << "use_count: " << sp1.use_count() << std::endl;  // 1
        
        auto sp2 = sp1;  // 引用计数+1
        std::cout << "use_count: " << sp1.use_count() << std::endl;  // 2
        
        {
            auto sp3 = sp1;
            std::cout << "use_count: " << sp1.use_count() << std::endl;  // 3
        }  // sp3离开作用域,引用计数-1
        std::cout << "use_count: " << sp1.use_count() << std::endl;  // 2
    }  // 所有shared_ptr离开作用域,内存释放
    
    // weak_ptr:打破循环引用
    {
        auto n1 = std::make_shared<Node>(1);
        auto n2 = std::make_shared<Node>(2);
        n1->next = n2;
        n2->prev = n1;  // weak_ptr不增加引用计数
    }  // 正常释放,不会内存泄漏
    
    return 0;
}

踩坑提醒shared_ptr的循环引用是C++最常见的内存泄漏场景之一。比如双向链表、树结构的父子节点、观察者模式中的互相引用。解决方案:一方用shared_ptr,另一方用weak_ptr。另外,make_shared比直接new更高效(一次分配对象和控制块),推荐使用。


七、现代C++特性(C++11/14/17/20)

7.1 Lambda表达式

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3};
    
    // Lambda基本语法:[捕获列表](参数) -> 返回类型 { 函数体 }
    
    // 1. 最简Lambda
    auto greet = []() { std::cout << "Hello!" << std::endl; };
    greet();
    
    // 2. 带参数
    auto add = [](int a, int b) { return a + b; };
    std::cout << "3 + 4 = " << add(3, 4) << std::endl;
    
    // 3. 捕获外部变量
    int threshold = 5;
    auto count = std::count_if(vec.begin(), vec.end(), 
        [threshold](int x) { return x > threshold; });
    std::cout << "大于" << threshold << "的个数: " << count << std::endl;
    
    // 4. 捕获方式对比
    int a = 10;
    auto f1 = [a]() { return a; };      // 值捕获(拷贝)
    auto f2 = [&a]() { return a; };     // 引用捕获
    auto f3 = [=]() { return a; };      // 全部值捕获
    auto f4 = [&]() { return a; };      // 全部引用捕获
    auto f5 = [=, &a]() { return a; }; // 默认值捕获,a引用捕获
    
    // 5. 泛型Lambda(C++20)
    // auto print = [](const auto& x) { std::cout << x << " "; };
    // print(42);     // int
    // print("hi");   // const char*
    
    return 0;
}

7.2 移动语义与右值引用

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <utility>

class Buffer {
private:
    int* data;
    size_t size_;
public:
    // 构造函数
    explicit Buffer(size_t size) : size_(size), data(new int[size]) {
        std::cout << "构造(size=" << size << ")" << std::endl;
    }
    
    // 析构函数
    ~Buffer() { delete[] data; std::cout << "析构" << std::endl; }
    
    // 拷贝构造(深拷贝)
    Buffer(const Buffer& other) : size_(other.size_), data(new int[other.size_]) {
        std::memcpy(data, other.data, size_ * sizeof(int));
        std::cout << "拷贝构造" << std::endl;
    }
    
    // 移动构造(转移资源,零拷贝)
    Buffer(Buffer&& other) noexcept : data(other.data), size_(other.size_) {
        other.data = nullptr;
        other.size_ = 0;
        std::cout << "移动构造(零拷贝!)" << std::endl;
    }
    
    // 移动赋值
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size_ = other.size_;
            other.data = nullptr;
            other.size_ = 0;
        }
        std::cout << "移动赋值" << std::endl;
        return *this;
    }
    
    size_t size() const { return size_; }
};

Buffer createBuffer() {
    Buffer buf(1024);
    return buf;  // 触发移动构造(或NRVO优化)
}

int main() {
    // std::move:将左值转换为右值引用
    std::string str1 = "Hello, C++!";
    std::string str2 = std::move(str1);  // str1的资源转移给str2
    std::cout << "str2: " << str2 << std::endl;  // Hello, C++!
    // str1现在是空字符串(已移动)
    
    // 移动语义的实际应用
    std::vector<Buffer> buffers;
    buffers.push_back(createBuffer());  // 移动构造
    buffers.emplace_back(2048);           // 原地构造,更高效
    
    return 0;
}

经验之谈 :移动语义是C++11最重要的特性之一。简单来说,拷贝是"复制一份",移动是"把资源偷过来"。对于管理大量资源的对象(大字符串、容器、文件句柄),移动比拷贝快几个数量级。std::move本身不做任何移动,它只是把左值转换成右值引用,真正的移动由移动构造函数/移动赋值运算符完成。

7.3 C++17/20 新特性

cpp 复制代码
#include <iostream>
#include <optional>
#include <variant>
#include <tuple>
#include <any>

int main() {
    // C++17: optional(可能没有值)
    std::optional<int> maybeInt;
    std::cout << "has_value: " << maybeInt.has_value() << std::endl;  // false
    maybeInt = 42;
    std::cout << "value: " << maybeInt.value() << std::endl;  // 42
    std::cout << "or: " << maybeInt.value_or(0) << std::endl;  // 42
    
    // C++17: variant(类型安全的union)
    std::variant<int, double, std::string> v = "Hello";
    std::cout << "variant: " << std::get<std::string>(v) << std::endl;
    v = 3.14;
    std::cout << "index: " << v.index() << std::endl;  // 1(double)
    
    // C++17: 结构化绑定
    auto [key, value] = std::pair(1, "one");
    std::cout << key << ": " << value << std::endl;
    
    auto [x, y, z] = std::make_tuple(1, 2.0, "three");
    std::cout << x << ", " << y << ", " << z << std::endl;
    
    // C++17: if constexpr(编译时条件)
    template<typename T>
    void process(T value) {
        if constexpr (std::is_integral_v<T>) {
            std::cout << "整数: " << value << std::endl;
        } else if constexpr (std::is_floating_point_v<T>) {
            std::cout << "浮点数: " << value << std::endl;
        } else {
            std::cout << "其他类型" << std::endl;
        }
    }
    
    process(42);      // 整数: 42
    process(3.14);    // 浮点数: 3.14
    process("hi");    // 其他类型
    
    // C++17: std::filesystem(文件系统操作)
    // #include <filesystem>
    // namespace fs = std::filesystem;
    // fs::path p = "/tmp/test.txt";
    // if (fs::exists(p)) { fs::remove(p); }
    
    return 0;
}

八、多线程与并发

8.1 std::thread基础

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
#include <chrono>

std::mutex mtx;
std::atomic<int> counter{0};

void increment(int times) {
    for (int i = 0; i < times; i++) {
        std::lock_guard<std::mutex> lock(mtx);  // RAII方式加锁
        counter++;
    }
}

int main() {
    const int TIMES = 100000;
    counter = 0;
    
    std::thread t1(increment, TIMES);
    std::thread t2(increment, TIMES);
    
    t1.join();
    t2.join();
    
    std::cout << "counter = " << counter << std::endl;  // 200000
    
    // 使用lambda创建线程
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; i++) {
        threads.emplace_back([i]() {
            std::cout << "线程 " << i << " 运行" << std::endl;
        });
    }
    for (auto& t : threads) t.join();
    
    return 0;
}

8.2 async与future

cpp 复制代码
#include <iostream>
#include <future>
#include <chrono>

int heavyComputation(int n) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return n * n;
}

int main() {
    // async:异步执行任务
    std::future<int> result = std::async(std::launch::async, heavyComputation, 42);
    
    std::cout << "任务已提交,主线程继续执行..." << std::endl;
    
    int value = result.get();  // 阻塞等待结果
    std::cout << "计算结果: " << value << std::endl;  // 1764
    
    // 并行执行多个任务
    auto f1 = std::async(std::launch::async, heavyComputation, 10);
    auto f2 = std::async(std::launch::async, heavyComputation, 20);
    auto f3 = std::async(std::launch::async, heavyComputation, 30);
    
    std::cout << "10^2=" << f1.get() << ", 20^2=" << f2.get() 
              << ", 30^2=" << f3.get() << std::endl;
    
    return 0;
}

8.3 原子操作

cpp 复制代码
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>

// atomic:无锁编程的基础
std::atomic<int> atomicCounter{0};
int normalCounter = 0;

void atomicIncrement() {
    atomicCounter.fetch_add(1, std::memory_order_relaxed);
}

void normalIncrement() {
    normalCounter++;  // 非原子操作,结果不正确
}

int main() {
    const int THREADS = 10;
    const int TIMES = 100000;
    
    // 原子操作测试
    std::vector<std::thread> threads;
    for (int i = 0; i < THREADS; i++) {
        threads.emplace_back([&]() {
            for (int j = 0; j < TIMES; j++) {
                atomicIncrement();
            }
        });
    }
    for (auto& t : threads) t.join();
    std::cout << "atomic counter: " << atomicCounter << std::endl;  // 1000000
    
    // 普通操作测试(结果不正确)
    normalCounter = 0;
    threads.clear();
    for (int i = 0; i < THREADS; i++) {
        threads.emplace_back([&]() {
            for (int j = 0; j < TIMES; j++) {
                normalIncrement();
            }
        });
    }
    for (auto& t : threads) t.join();
    std::cout << "normal counter: " << normalCounter << std::endl;  // 不等于1000000
    
    return 0;
}

九、RAII:C++的核心编程范式

RAII(Resource Acquisition Is Initialization)是C++最重要的编程范式之一。

cpp 复制代码
#include <iostream>
#include <fstream>
#include <mutex>

// RAII示例1:文件操作
class FileGuard {
    std::ofstream file;
public:
    FileGuard(const std::string& path) {
        file.open(path);
        if (!file.is_open()) throw std::runtime_error("无法打开文件");
    }
    
    ~FileGuard() {
        if (file.is_open()) file.close();
    }
    
    void write(const std::string& content) {
        file << content << std::endl;
    }
};

// RAII示例2:锁守卫(std::lock_guard就是RAII)
class ScopedLock {
    std::mutex& mtx;
public:
    explicit ScopedLock(std::mutex& m) : mtx(m) { mtx.lock(); }
    ~ScopedLock() { mtx.unlock(); }
};

int main() {
    // 文件RAII:不管是否抛异常,文件都会被正确关闭
    {
        FileGuard fg("test.txt");
        fg.write("Hello, RAII!");
        // 即使这里抛异常,析构函数也会关闭文件
    }
    
    // 锁RAII:std::lock_guard
    std::mutex mtx;
    {
        std::lock_guard<std::mutex> lock(mtx);
        // 临界区操作...
    }  // 自动释放锁
    
    return 0;
}

面试高频 :RAII是C++面试的高频考点。核心思想:将资源的生命周期绑定到对象的生命周期 。对象构造时获取资源,析构时释放资源。无论正常退出还是异常退出,析构函数都会被调用,从而保证资源不泄漏。智能指针、std::lock_guardstd::fstream都是RAII的典型应用。


十、面试高频考点汇总

Q1:C++的虚函数是怎么实现的?

A :编译器为每个有虚函数的类生成一个虚函数表(vtable),表中存储了该类所有虚函数的地址。每个对象有一个指向vtable的指针(vptr)。调用虚函数时,通过vptr找到vtable,再从vtable中找到对应的函数地址进行调用。开销:多一次间接寻址,性能影响通常< 5%。

Q2:智能指针的选择原则?

A :默认用unique_ptr,它开销最小(和裸指针一样),语义最清晰------独占所有权。只有当确实需要多个对象共享同一资源时才用shared_ptrshared_ptr有引用计数开销,且可能产生循环引用问题。weak_ptr不拥有对象,主要用于打破shared_ptr的循环引用。

Q3:new/delete和malloc/free的区别?

Anew/delete是C++运算符,malloc/free是C库函数。new会调用构造函数,delete会调用析构函数,malloc/free不会。new自动计算大小,malloc需要手动指定。new失败抛异常,malloc失败返回NULL。C++中推荐使用new/delete(或更好的智能指针)。

Q4:移动语义解决了什么问题?

A:移动语义解决了不必要的深拷贝问题。在C++11之前,从函数返回大对象、容器扩容等场景都会触发深拷贝,即使原来的对象马上就要被销毁。移动语义允许"窃取"临时对象的资源(指针转移),避免了昂贵的内存分配和数据复制。

Q5:STL vector扩容机制?

Avector容量不够时,通常分配当前容量的2倍 (或1.5倍,取决于实现)的新内存,将旧数据拷贝到新内存,然后释放旧内存。扩容时所有迭代器、指针、引用都会失效。可以用reserve()提前分配足够的容量避免频繁扩容。

Q6:C++中的RAII是什么?

A :RAII(Resource Acquisition Is Initialization)是C++的核心编程范式。核心思想:将资源的生命周期绑定到对象的生命周期 。对象构造时获取资源,析构时释放资源。无论正常退出还是异常退出,析构函数都会被调用。智能指针、std::lock_guardstd::fstream都是RAII的典型应用。


十一、C++学习路线与资源推荐

11.1 推荐学习路线

复制代码
第1周:基础语法(变量、引用、const、命名空间)
第2周:面向对象(类、继承、多态、虚函数、运算符重载)
第3周:模板与STL(函数模板、类模板、vector/map/set、迭代器)
第4周:内存管理(new/delete、智能指针、RAII)
第5周:现代C++(Lambda、移动语义、auto/decltype)
第6周:多线程(thread、mutex、async/future、atomic)
第7周:进阶特性(模板元编程、C++17/20新特性)
第8周:综合项目(简易HTTP服务器/JSON解析器/线程池/简易数据库)

11.2 推荐学习资源

资源 说明
《C++ Primer》 C++入门的"圣经",系统全面
《Effective C++》 进阶必读,55条改善代码的建议
《Effective Modern C++》 Scott Meyers的现代C++最佳实践
《STL源码剖析》 侯捷著,深入理解STL底层实现
《C++ Concurrency in Action》 C++并发编程权威指南
cppreference.com 最权威的在线参考文档

写在最后

C++是一门"学无止境"的语言。从C++98到C++20,标准不断演进,新特性层出不穷。但不管标准怎么变,核心的东西始终不变:理解内存、理解对象模型、理解模板、理解RAII

我见过太多人,C++语法背得很熟,但不知道虚函数表长什么样,不知道std::move到底做了什么,不知道shared_ptr的循环引用怎么解决。语法只是工具,理解原理才是王道

这篇文章涵盖了C++的核心知识点,但学C++最重要的是动手实践。建议你今天就把代码编译运行一遍,遇到编译错误就查,遇到段错误就用gdb调试,这比看十篇文章都有用。


互动话题:你在学C++的过程中,哪个知识点让你印象最深或者踩过最深的坑?是指针和引用搞混了,还是模板的编译错误看不懂?欢迎在评论区分享你的经历,大家一起交流!

如果这篇文章对你有帮助,欢迎点赞、收藏、关注三连支持!后续我会持续更新C++进阶系列文章,包括模板元编程、C++20协程、现代C++设计模式等。

本文为C++全面教学系列的第一篇,后续文章正在撰写中,关注我不迷路 👇


参考资料

相关推荐
lilili也1 小时前
C++:lamda表达式
c++
xingpanvip1 小时前
使用 Webwright 在 CSDN 自动发文:Python 浏览器自动化实践
开发语言·python·自动化
坚果派·白晓明1 小时前
[鸿蒙PC三方库移植适配] 使用 AtomCode + Skills 自动完成libhv鸿蒙化适配
c++·华为·ai编程·harmonyos·atomcode
禅思院1 小时前
大列表性能优化 · 工程实战·四
开发语言·前端·性能优化·前端框架·php·异步加载
z落落1 小时前
C# 多接口实现、重名成员、显式实现、接口继承+抽象类和接口区别
java·开发语言·c#
caimouse1 小时前
Reactos 第 4 章 对象管理 — 4.6 对象的访问控制 / 4.7 句柄的遗传和继承
开发语言·windows·架构
2301_789015621 小时前
Linux基础开发工具一:软件包管理器、vim编辑器
linux·服务器·c语言·汇编·c++·编辑器·vim
玖玥拾1 小时前
C/C++ 基础笔记(十)
c语言·c++
huangdong_1 小时前
京东整店商品图片视频批量下载技术:从商品列表到自动分类
开发语言·python·音视频