今天是第六天了也是终于快复习一半了为自己鼓鼓掌。今天依旧是五条知识点一起来查漏补缺吧。26.类和结构体什么区别27.被 delete或free释放的内存会直接返还给操作系统吗28.什么时候需要析构函数29.常函数内能否修改静态成员30.初始化参数列表什么特点好的我们从第二十六条开始。26.类和结构体什么区别语法层面只有两个默认权限不同在 C 中class和struct在功能上几乎是 100% 相同的都可以包含成员变量、成员函数、构造/析构函数也都支持继承和多态。它们唯二的硬性语法区别在于默认访问权限不同class的默认访问权限是private私有。struct的默认访问权限是public公开。默认继承权限不同class继承默认是private继承。struct继承默认是public继承。27.被 delete或free释放的内存会直接返还给操作系统吗不会会被存放在内存池中下次申请内存时直接去内存池中查找解决了内存碎片问 题。一般公司不会问的知识点曾经拼多多腾讯等问过想了解搜索 ptmalloc的实现如何主动将内存还给操作系统如果面试官继续深挖“如果程序长时间运行产生了大量空闲内存但都没还给系统导致进程占用内存RSS居高不下该怎么办”解决方案可以调用 glibc 提供的非标准函数malloc_trim(0)。作用它会尝试强制将堆顶top chunk连续的空闲内存通过brk(-size)系统调用收缩并归还给操作系统。但需要注意如果堆顶内存不连续比如被一些未释放的小对象挡住这个操作可能会失败或只能归还部分内存。、28.什么时候需要析构函数需要手写析构函数的根本原因是“类内部申请了需要手动释放的资源堆内存、文件句柄、网络连接等”。并且一旦手写了析构函数一定要警惕默认的浅拷贝带来的双重释放风险遵循“三/五法则”来完善类的拷贝和移动语义。1. 动态分配的内存最典型当类的成员变量中有通过new、new[]或 C 语言的malloc等函数在堆区申请的内存时必须在析构函数中使用delete、delete[]或free来释放。举例你提到的指针成员char* mName new char[100];如果不在析构函数中delete[] mName;就会导致内存泄漏。2. 系统资源句柄极易被忽略除了内存程序在运行中打开的各类系统资源如果不关闭会导致资源泄漏甚至影响整个系统的正常运行。这些资源通常不是通过new分配的但也必须在析构函数中手动关闭。打开的文件通过fopen打开的文件指针FILE*需要在析构中调用fclose关闭。网络连接/数据库连接比如 Socket 套接字、数据库连接句柄等需要在析构中调用close或专门的断开连接函数。互斥锁/信号量在多线程编程中如果类持有锁需要在析构中确保锁被正确释放防止死锁。3. 自定义类型成员需要特殊清理时如果类中包含其他自定义类型的成员且这些成员本身管理着上述资源通常这些成员自己的析构函数会处理清理工作。但如果你需要控制它们的销毁顺序或者进行一些额外的联动清理操作也需要手写当前类的析构函数。补充机制如果类中全是int、double或std::string这种自带完善析构机制的成员编译器生成的默认析构函数就完全够用了此时不需要手写。 面试核心加分项析构函数与“三/五法则”在面试中当你回答了“什么时候需要析构函数”后面试官紧接着大概率会追问“如果你手写了析构函数还需要注意什么”这时你可以抛出 C 著名的“三法则Rule of Three”或 C11 后的“五法则Rule of Five”核心逻辑如果你需要手写析构函数说明你的类在手动管理资源。那么编译器自动生成的拷贝构造函数和拷贝赋值运算符浅拷贝大概率是错误的。潜在风险默认的浅拷贝会导致两个对象的指针成员指向同一块堆内存。当其中一个对象被销毁调用析构函数释放内存后另一个对象的指针就会变成“野指针”再次析构时会引发重复释放Double Free导致程序崩溃。正确做法一旦手写了析构函数通常也必须显式手写或禁用拷贝构造函数和拷贝赋值运算符来实现深拷贝或禁止拷贝。在 C11 以后还需要考虑移动构造函数和移动赋值运算符。29.常函数内能否修改静态成员可以常函数内不能修改非静态成员因为访问非静态成员需要使用this指针例如在 类 A中的一个常函数内this指针的类型为 const A* const this; *this被const修饰代表不能 通过 this指针修改对象我门在访问非静态成员时需要使用this指针所以不能修改访问静 态成员时不需要使用 this指针区分是哪个对象的成员因为静态成员是共享的单独存放在静态 区不占对象的内存所以常函数内可以修改静态成员。30.初始化参数列表什么特点●构造函数和类名相同没有返回值也不写 void●如果没有实现构造函数编译器会提供默认的无参构造函数●构造函数是给成员变量赋值的不是初始化引出初始化参数列表●移动构造继承构造委托构造C11后的写法更加简洁高效#include iostream #include string // 1. 模拟一个没有默认构造函数的类作为成员对象 class Engine { public: Engine(int horsepower) : m_horsepower(horsepower) { std::cout Engine 构造函数被调用马力: m_horsepower std::endl; } private: int m_horsepower; }; // 2. 父类没有默认构造函数强制子类在初始化列表中调用 class Vehicle { public: Vehicle(std::string brand) : m_brand(brand) { std::cout Vehicle 父类构造函数被调用品牌: m_brand std::endl; } private: std::string m_brand; }; // 3. 子类 Car包含 const、引用、无默认构造成员 class Car : public Vehicle { public: // 构造函数演示初始化列表的各种用法 Car(std::string brand, int hp, int yearRef) : Vehicle(brand), // 特点5调用父类的带参构造函数 m_engine(hp), // 特点4调用成员对象 Engine 的带参构造函数 m_maxSpeed(220), // 特点3const 常量必须在初始化列表中初始化 m_yearRef(yearRef) // 特点3引用成员必须在初始化列表中绑定 { std::cout Car 子类构造函数体执行 std::endl; // 注意如果在这里写 m_maxSpeed 220; 或 m_yearRef yearRef; 编译器会直接报错 } void display() { std::cout 当前引用的年份: m_yearRef std::endl; } private: int m_maxSpeed; // const 常量成员 int m_yearRef; // 引用成员 Engine m_engine; // 没有默认构造函数的成员对象 // 演示初始化顺序的坑 int m_a; int m_b; }; int main() { int currentYear 2026; std::cout --- 开始创建 Car 对象 --- std::endl; // 传入品牌、马力、以及一个外部变量的引用 Car myCar(Tesla, 500, currentYear); myCar.display(); return 0; }