当前位置: 首页 > news >正文

侯捷 C++ 课程学习笔记:深入理解C++内存管理与类对象构造全过程

侯捷 C++ 课程学习笔记:深入理解C++内存管理与类对象构造全过程

学习笔记目录

  • 侯捷 C++ 课程学习笔记:深入理解C++内存管理与类对象构造全过程
    • 一、学习背景与心得
      • 1.1 为什么选择侯捷老师的课程
      • 1.2 学习方法总结
      • 1.3 C++学习路线图
    • 二、内存管理专题深度解析
      • 2.1 C++对象模型
      • 2.2 内存分配过程
      • 2.3 内存管理实战案例
    • 三、类对象构造过程详解
      • 3.1 构造函数调用顺序
      • 3.2 深拷贝与浅拷贝
      • 3.3 性能优化实践
      • 3.4 RAII资源管理
      • 3.5 完美转发
    • 四、实际项目应用
      • 4.1 智能指针封装
      • 4.2 项目实践:线程安全的单例模式
      • 4.3 线程池实现
      • 4.4 高性能日志系统
    • 五、现代C++特性应用
      • 5.1 可变参数模板
      • 5.2 Lambda表达式与函数对象
    • 六、性能优化技巧
      • 6.1 内存对齐与缓存优化
      • 6.2 编译期计算
    • 七、学习总结与心得
      • 7.1 知识体系构建
      • 7.2学习路线
      • 7.3学习感悟

一、学习背景与心得

作为一名大学生,在学习C++的过程中,我一直在寻找能够真正理解这门语言本质的学习资源。很幸运地接触到了侯捷老师的C++系列课程,这些课程不仅帮助我建立了完整的C++知识体系,更重要的是让我理解了C++的设计理念和底层实现机制。

1.1 为什么选择侯捷老师的课程

  • 系统性强:从内存模型到设计模式,层层递进
  • 深入浅出:通过大量实例讲解抽象概念
  • 实战导向:理论结合实践,重视代码实现
  • 独特见解:对C++语言特性有深刻理解
  • 经验丰富:结合实际工程经验讲解

1.2 学习方法总结

  • 跟随课程编写代码,反复调试验证
  • 绘制知识脑图,建立知识关联
  • 通过实际项目实践,巩固所学内容
  • 记录学习笔记,定期复习回顾
  • 参与开源项目,实践所学知识

1.3 C++学习路线图

基础语法
面向对象
STL标准库
内存管理
多线程编程
设计模式
性能优化

二、内存管理专题深度解析

2.1 C++对象模型

本节通过Complex类展示C++对象在内存中的布局方式。每个对象实例在内存中按成员声明顺序连续存储,构造函数通过初始化列表完成成员初始化,这是C++对象构造的基础模型。

class Complex {
private:
    double real;
    double imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    // 其他成员函数...
};

对象在内存中的布局:

classDiagram
    class Complex {
        -double real
        -double imag
        +Complex(double r, double i)
    }
    note for Complex "内存布局:\n|  real (8字节)  |\n|  imag (8字节)  |"

内存布局示意图展示了Complex类实例的内存结构。每个double类型占8字节,两个成员变量按声明顺序连续排列,总大小为16字节。构造函数初始化列表直接操作这些内存位置。

2.2 内存分配过程

C++内存分配分为静态和动态两种方式,理解它们的差异是掌握内存管理的关键。静态分配由编译器自动管理生命周期,动态分配需要开发者手动管理。

  1. 静态分配
Complex c1(1.0, 2.0);  // 栈上分配
static Complex c2;      // 静态存储区

c1在函数栈帧中分配,生命周期与作用域绑定。c2在程序静态存储区分配,生命周期持续到程序结束。两者都不需要手动释放。

  1. 动态分配
Complex* pc = new Complex(1.0, 2.0);  // 堆上分配
// ...使用对象
delete pc;  // 释放内存

new操作符在堆上分配内存并构造对象,返回指针需要手动管理。delete操作符先调用析构函数再释放内存,必须配对使用避免内存泄漏。

内存分配流程:

new操作符调用
operator new分配内存
构造函数调用
对象创建完成
delete操作符调用
析构函数调用
operator delete释放内存

流程图揭示了new/delete的底层机制:内存分配与对象构造分离。operator new仅分配原始内存,构造函数初始化对象,这个分离机制是placement new等技术的基础。

2.3 内存管理实战案例

自定义内存池是优化频繁内存分配的有效方案。通过预分配内存块和链表管理,减少系统调用次数,提升内存分配效率。

// 自定义内存池
class MemoryPool {
private:
    struct Block {
        Block* next;
        char data[1024];  // 实际的内存块
    };
    Block* freeList;
    
public:
    MemoryPool() : freeList(nullptr) {
        // 预分配一些内存块
        expandPool();
    }
    
    void* allocate(size_t size) {
        if (size > sizeof(Block::data)) return nullptr;
        if (!freeList) expandPool();
        
        Block* block = freeList;
        freeList = freeList->next;
        return block->data;
    }
    
    void deallocate(void* p) {
        Block* block = reinterpret_cast<Block*>(
            reinterpret_cast<char*>(p) - offsetof(Block, data)
        );
        block->next = freeList;
        freeList = block;
    }
    
private:
    void expandPool() {
        // 分配新的内存块链表
        for (int i = 0; i < 10; ++i) {
            Block* block = new Block;
            block->next = freeList;
            freeList = block;
        }
    }
};

内存池实现要点:

  1. 通过freeList链表管理空闲内存块
  2. 分配时从链表头部取内存块
  3. 释放时通过offsetof计算块头地址,将内存块插回链表
  4. 内存不足时批量扩展(expandPool)
  5. 每个内存块包含控制头(Block)和用户数据区(data)

三、类对象构造过程详解

3.1 构造函数调用顺序

派生类对象的构造遵循严格顺序:基类构造->成员构造->派生类构造体。理解这个顺序对正确初始化对象至关重要。

class Base {
    int x;
public:
    Base() : x(0) { 
        cout << "Base constructor\n"; 
    }
};

class Derived : public Base {
    int y;
public:
    Derived() : y(0) { 
        cout << "Derived constructor\n"; 
    }
};

构造过程:

Main Derived Base 创建派生类对象 调用基类构造函数 基类构造完成 成员初始化 派生类构造完成 Main Derived Base

构造顺序的实践意义:

  1. 基类构造函数中不要调用虚函数
  2. 成员变量按声明顺序初始化(而非初始化列表顺序)
  3. 派生类构造函数可以依赖基类已初始化的成员

3.2 深拷贝与浅拷贝

拷贝控制是C++类设计的核心问题。深拷贝保证对象独立性,移动语义(C++11)提升资源管理效率。

class String {
private:
    char* str;
public:
    // 构造函数
    String(const char* s = nullptr) {
        if (s) {
            str = new char[strlen(s) + 1];
            strcpy(str, s);
        } else {
            str = new char[1];
            str[0] = '\0';
        }
    }
    
    // 深拷贝构造函数
    String(const String& other) {
        str = new char[strlen(other.str) + 1];
        strcpy(str, other.str);
    }
    
    // 移动构造函数 (C++11)
    String(String&& other) noexcept {
        str = other.str;
        other.str = nullptr;
    }
    
    ~String() {
        delete[] str;
    }
};

关键点分析:

  1. 默认拷贝构造函数执行浅拷贝,会导致双重释放
  2. 深拷贝通过new分配独立内存,适合需要副本的场景
  3. 移动构造转移资源所有权,避免不必要的拷贝
  4. noexcept声明保证移动操作不会抛出异常
  5. 析构函数需要安全处理空指针

3.3 性能优化实践

构造函数优化是提升C++性能的重要手段,主要方法包括使用初始化列表和利用返回值优化。

  1. 使用初始化列表
class Point {
    int x, y;
public:
    // 推荐:使用初始化列表
    Point(int xx, int yy) : x(xx), y(yy) {}
    
    // 不推荐:构造函数体内赋值
    Point(int xx, int yy) {
        x = xx;
        y = yy;
    }
};
  1. 返回值优化(RVO)
class Heavy {
    // 大量数据成员
public:
    Heavy() { /* 初始化 */ }
};

Heavy createHeavy() {
    return Heavy();  // 编译器会优化掉不必要的拷贝
}

int main() {
    Heavy h = createHeavy();  // 直接构造,无需拷贝
}

3.4 RAII资源管理

RAII(Resource Acquisition Is Initialization)是C++核心编程范式,通过对象生命周期管理资源。

template<typename Resource>
class ResourceGuard {
private:
    Resource* resource;
    
public:
    explicit ResourceGuard(Resource* r) : resource(r) {}
    
    ~ResourceGuard() {
        if (resource) {
            resource->release();
            delete resource;
        }
    }
    
    // 禁止拷贝
    ResourceGuard(const ResourceGuard&) = delete;
    ResourceGuard& operator=(const ResourceGuard&) = delete;
    
    // 允许移动
    ResourceGuard(ResourceGuard&& other) noexcept : resource(other.resource) {
        other.resource = nullptr;
    }
    
    Resource* get() const { return resource; }
};

// 使用示例
class DatabaseConnection {
public:
    void release() { /* 关闭数据库连接 */ }
};

void processData() {
    ResourceGuard<DatabaseConnection> db(new DatabaseConnection());
    // 使用数据库连接...
    // 离开作用域时自动释放资源
}

RAII实现要点:

  1. 资源在构造函数中获取
  2. 析构函数保证资源释放
  3. 禁用拷贝防止重复释放
  4. 允许移动实现资源所有权转移
  5. 通过模板实现通用资源管理

3.5 完美转发

完美转发保持参数的值类别(左值/右值),是泛型编程的重要技术,常用于工厂函数和包装器。

template<typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args) {
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

// 使用示例
class Widget {
public:
    Widget(int x, string str) {}
};

auto w = make_unique<Widget>(42, "Hello");

关键要素:

  1. 通用引用(Args&&)保持参数类型
  2. std::forward有条件转换回原始值类别
  3. 避免参数传递过程中的不必要拷贝
  4. 与变参模板结合实现任意参数转发
  5. 是现代C++智能指针工厂函数的基础实现

四、实际项目应用

4.1 智能指针封装

classDiagram
    class SmartPtr~T~ {
        -T* ptr
        +SmartPtr(T* p)
        +~SmartPtr()
        +operator*() T&
        +operator->() T*
        -SmartPtr(const SmartPtr&)
        -operator=(const SmartPtr&)
    }
    note for SmartPtr "RAII原则:\n资源获取即初始化\n析构时自动释放"
template<typename T>
class SmartPtr {
private:
    T* ptr;
    
public:
    explicit SmartPtr(T* p = nullptr) : ptr(p) {}
    
    ~SmartPtr() {
        delete ptr;
    }
    
    // 禁止拷贝
    SmartPtr(const SmartPtr&) = delete;
    SmartPtr& operator=(const SmartPtr&) = delete;
    
    // 移动语义
    SmartPtr(SmartPtr&& other) noexcept {
        ptr = other.ptr;
        other.ptr = nullptr;
    }
    
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
};

4.2 项目实践:线程安全的单例模式

Thread1 Instance Thread2 getInstance() 返回实例 getInstance() Double-Check Locking 返回相同实例 Thread1 Instance Thread2
class Singleton {
private:
    static std::mutex mutex_;
    static std::atomic<Singleton*> instance_;
    
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
public:
    static Singleton* getInstance() {
        Singleton* tmp = instance_.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(mutex_);
            tmp = instance_.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton;
                std::atomic_thread_fence(std::memory_order_release);
                instance_.store(tmp, std::memory_order_relaxed);
            }
        }
        return tmp;
    }
};

std::mutex Singleton::mutex_;
std::atomic<Singleton*> Singleton::instance_{nullptr};

4.3 线程池实现

创建
包含
包含
包含
任务分配
任务分配
任务分配
提交任务
主线程
线程池
工作线程1
工作线程2
工作线程N
任务队列
class ThreadPool {
private:
    vector<thread> workers;
    queue<function<void()>> tasks;
    mutex queue_mutex;
    condition_variable condition;
    bool stop;
    
public:
    ThreadPool(size_t threads) : stop(false) {
        for(size_t i = 0; i < threads; ++i)
            workers.emplace_back([this] {
                while(true) {
                    function<void()> task;
                    {
                        unique_lock<mutex> lock(queue_mutex);
                        condition.wait(lock,
                            [this]{ return stop || !tasks.empty(); });
                        if(stop && tasks.empty())
                            return;
                        task = move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
    }
    
    template<class F>
    void enqueue(F&& f) {
        {
            unique_lock<mutex> lock(queue_mutex);
            tasks.push(forward<F>(f));
        }
        condition.notify_one();
    }
    
    ~ThreadPool() {
        {
            unique_lock<mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for(auto &worker: workers)
            worker.join();
    }
};

// 使用示例
ThreadPool pool(4);
auto result = pool.enqueue([](int x) { return x * x; }, 42);

4.4 高性能日志系统

写入
消费
写入
通知
通知
应用线程
消息队列
写入线程
日志文件
条件变量
class Logger {
private:
    mutex write_mutex;
    ofstream log_file;
    queue<string> message_queue;
    condition_variable cv;
    bool running;
    thread writer_thread;
    
    void writer_loop() {
        while(running || !message_queue.empty()) {
            unique_lock<mutex> lock(write_mutex);
            cv.wait(lock, [this]{ 
                return !message_queue.empty() || !running; 
            });
            
            while(!message_queue.empty()) {
                log_file << message_queue.front() << endl;
                message_queue.pop();
            }
        }
    }
    
public:
    Logger(const string& filename) 
        : log_file(filename, ios::app)
        , running(true) {
        writer_thread = thread(&Logger::writer_loop, this);
    }
    
    void log(const string& message) {
        lock_guard<mutex> lock(write_mutex);
        message_queue.push(message);
        cv.notify_one();
    }
    
    ~Logger() {
        running = false;
        cv.notify_one();
        if(writer_thread.joinable())
            writer_thread.join();
    }
};

五、现代C++特性应用

5.1 可变参数模板

递归展开
递归展开
基本情况
sum
sum
sum
sum
template<typename T>
T sum(T t) {
    return t;
}

template<typename T, typename... Args>
T sum(T first, Args... args) {
    return first + sum(args...);
}

// 使用示例
int total = sum(1, 2, 3, 4, 5);  // 15

5.2 Lambda表达式与函数对象

classDiagram
    class Lambda {
        -captured_variables
        +operator()
    }
    class Function {
        +virtual call()
    }
    Lambda --|> Function
    note for Lambda "编译器生成的闭包类型"
class Widget {
    vector<int> data;
public:
    void processData() {
        // 捕获this指针的lambda
        auto printer = [this](int x) { 
            cout << "Processing: " << x << endl; 
        };
        
        // 使用标准算法
        for_each(data.begin(), data.end(), printer);
        
        // 排序示例
        sort(data.begin(), data.end(),
            [](int a, int b) { return abs(a) < abs(b); });
    }
};

六、性能优化技巧

6.1 内存对齐与缓存优化

GoodLayout
BadLayout
1字节
8字节
1字节
8字节
1字节
1字节
char a
double b
char c
6字节padding
7字节padding
char a
double b
char c
7字节padding
// 不良示例
struct BadLayout {
    char a;     // 1字节
    double b;   // 8字节
    char c;     // 1字节
};  // 实际占用24字节

// 优化后
struct GoodLayout {
    double b;   // 8字节
    char a;     // 1字节
    char c;     // 1字节
    // 6字节padding
};  // 实际占用16字节

6.2 编译期计算

5 *
4 *
3 *
2 *
1 *
1
Factorial<5>
Factorial<4>
Factorial<3>
Factorial<2>
Factorial<1>
Factorial<0>
结果: 120
template<unsigned n>
struct Factorial {
    static constexpr unsigned value = n * Factorial<n-1>::value;
};

template<>
struct Factorial<0> {
    static constexpr unsigned value = 1;
};

// 编译期计算阶乘
constexpr unsigned fact5 = Factorial<5>::value;

七、学习总结与心得

7.1 知识体系构建

C++核心知识体系
基础语法
面向对象
模板编程
内存管理
并发编程
性能优化
数据类型
控制流
函数
类与对象
继承多态
封装
类模板
函数模板
特化
智能指针
RAII
内存模型
线程
同步
原子操作
编译期优化
运行时优化

7.2学习路线

掌握语法
理解封装继承
熟练应用
深入理解
优化技巧
实战应用
入门基础
面向对象
STL使用
模板编程
内存管理
多线程开发
项目实践

7.3学习感悟

编程是思想的育苗,键盘声里逻辑抽枝。与指针博弈的深夜犹如星际迷航,当内存迷雾消散,指针已成新世界的通行证;调试化作侦探游戏,报错信息皆是解密线索;用代码积木构建项目,完成首个程序诞生时恍见思想破茧成蝶。习得不仅是语法规则,更是与机器的诗意对话,在01洪流中守护浪漫——编译等待的须臾恰似调试时的顿悟,终成青春独有留白。

相关文章:

  • 【极客时间】浏览器工作原理与实践-2 宏观视角下的浏览器 (6讲) - 2.6 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?
  • 第005文-模拟入侵网站实现0元购
  • µCOS-III从入门到精通 第八章(时间片调度)
  • 点云 基于法线的双边滤波原理和过程
  • LeetCode hot 100—二叉树的最大深度
  • 能量石[算法题]
  • YOLOv12 项目部署指南! 含报错解决
  • Flutter底层实现
  • Go学习笔记:基础语法3
  • 【由技及道】镜像星门开启:Harbor镜像推送的量子跃迁艺术【人工智障AI2077的开发日志010】
  • CSS+Html面试题(二)
  • python网络爬虫开发实战之爬虫基础
  • Unity自定义渲染管线(Scriptable Render Pipeline)架构设计与实现指南
  • netty中Future和ChannelHandler
  • Best practice-生产环境中加锁的最佳实践
  • Anaconda 部署 DeepSeek
  • Java 大视界 -- Java 大数据在智能政务公共服务资源优化配置中的应用(118)
  • Linux | Vim 鼠标不能右键粘贴、跨系统复制粘贴
  • 深入解析“Elaborate”——从详细阐述到精心制作的多重含义
  • 绝美焦糖暖色调复古风景画面Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • 男子服用头孢后饮酒应酬致昏迷在家,救援人员破门施救后脱险
  • 去年上海全市博物馆接待观众约4087万人次,同比增31.9%
  • 香港特区政府强烈谴责美参议员恐吓国安人员
  • 101岁陕西省军区原司令员冀廷璧逝世,曾参加百团大战
  • 竞彩湃|欧联杯决赛前,曼联、热刺继续划水?
  • 俄乌官员即将在土耳其会谈,外交部:支持俄乌开启直接对话