C 面试高频 10 题深度解析从 new/delete 到虚函数表附 5 个易错点1. 内存管理new/delete 与 malloc/free 的底层差异核心区别在于 new/delete 是 C 运算符而 malloc/free 是 C 标准库函数。当处理非内部数据类型时new 会调用构造函数完成对象初始化delete 会调用析构函数进行资源清理而 malloc/free 仅进行原始内存分配和释放。典型易错场景class MyClass { public: MyClass() { buffer new char[100]; } ~MyClass() { delete[] buffer; } private: char* buffer; }; // 错误示例1混用内存管理方式 MyClass* obj (MyClass*)malloc(sizeof(MyClass)); // 构造函数未被调用 free(obj); // 析构函数未被调用内存泄漏 // 错误示例2数组与非数组混用 MyClass* arr new MyClass[10]; delete arr; // 应使用 delete[]否则仅第一个元素析构深度对比表特性new/deletemalloc/free语言层面C 运算符C 库函数构造函数/析构函数自动调用不调用内存计算自动计算大小需手动计算失败处理抛出 bad_alloc返回 NULL重载可能性支持运算符重载不可重载类型安全是否关键提示对于自定义类类型必须使用 new/delete 以保证构造和析构的正确执行。内部数据类型虽可混用但会降低代码可读性。2. 虚函数实现机制与多态成本虚函数通过**虚函数表vtable**实现动态绑定。每个含虚函数的类拥有自己的 vtable其中按声明顺序存储虚函数指针。对象内存布局首部包含指向 vtable 的指针vptr运行时通过此指针找到实际调用的函数。典型内存布局class Base { public: virtual void func1() {} virtual void func2() {} int data; }; class Derived : public Base { public: void func1() override {} virtual void func3() {} };对应的内存结构Derived 对象内存布局 ------------------- | vptr | - Derived的vtable ------------------- | Base::data | ------------------- | Derived新增成员 | ------------------- Derived的vtable ------------------- | Derived::func1 | ------------------- | Base::func2 | ------------------- | Derived::func3 | -------------------性能影响每次虚函数调用需要额外解引用操作通过vptr找到vtable对象体积增大每个对象需存储vptr编译器难以内联优化虚函数3. const 关键字的全方位应用const 的正确使用能显著提升代码健壮性主要应用场景包括常量定义const int MAX_SIZE 1024; // 替代#define函数修饰// 参数保护 void Print(const std::string str); // 返回值保护 const char* GetName(); // 成员函数承诺不修改对象状态 int GetValue() const;指针与const的组合const char* p1; // 指向常量内容 char* const p2; // 常量指针 const char* const p3; // 双重const易错点分析const int* p new int(10); *p 20; // 编译错误不能修改const内容 int* const q new int(20); q nullptr; // 编译错误不能修改指针本身 *q 30; // 允许修改指向内容4. 深度拷贝与浅拷贝的陷阱当类包含指针成员时默认的拷贝构造函数和赋值运算符执行浅拷贝这会导致双重释放问题class String { public: String(const char* str nullptr) { if (str) { data new char[strlen(str)1]; strcpy(data, str); } else data nullptr; } ~String() { delete[] data; } // 问题根源没有自定义拷贝构造 private: char* data; }; String s1(hello); String s2 s1; // 浅拷贝两个对象指向同一内存 // 析构时同一内存被释放两次正确实现// 拷贝构造函数 String(const String other) { if (other.data) { data new char[strlen(other.data)1]; strcpy(data, other.data); } else data nullptr; } // 赋值运算符 String operator(const String rhs) { if (this ! rhs) { delete[] data; // 释放原有资源 if (rhs.data) { data new char[strlen(rhs.data)1]; strcpy(data, rhs.data); } else data nullptr; } return *this; }5. 虚函数表的高级话题多重继承下的vtableclass Base1 { virtual void f1(); }; class Base2 { virtual void f2(); }; class Derived : public Base1, public Base2 { void f1() override {} void f2() override {} }; Derived d; Base2* pb2 d; // 需要调整指针位置此时对象内存包含两个vptr------------------- | vptr (Base1) | ------------------- | Base1 members | ------------------- | vptr (Base2) | ------------------- | Base2 members | ------------------- | Derived members | -------------------typeid 和 dynamic_cast 的实现依赖 vtable 中的 RTTI运行时类型信息会增加可执行文件体积约10-20%6. 智能指针的工程实践三种智能指针对比类型所有权策略线程安全循环引用典型用途unique_ptr独占所有权否无替代原始指针shared_ptr共享所有权是可能多对象共享资源weak_ptr不拥有所有权是解决循环打破循环引用循环引用示例struct Node { shared_ptrNode next; weak_ptrNode prev; // 使用weak_ptr打破循环 }; auto n1 make_sharedNode(); auto n2 make_sharedNode(); n1-next n2; n2-prev n1; // 安全不会导致引用计数增加7. 移动语义与完美转发右值引用实现了资源的高效转移class Vector { public: // 移动构造函数 Vector(Vector other) noexcept : data(other.data), size(other.size) { other.data nullptr; // 确保源对象处于可析构状态 } // 移动赋值运算符 Vector operator(Vector rhs) noexcept { if (this ! rhs) { delete[] data; data rhs.data; size rhs.size; rhs.data nullptr; } return *this; } private: int* data; size_t size; };完美转发保持参数原始类型templatetypename... Args void emplace_back(Args... args) { // std::forward保持参数的左值/右值特性 new (end) T(std::forwardArgs(args)...); }8. 类型推导与模板元编程auto 和 decltype 的区别const int x 42; auto y x; // y 是 int忽略顶层const decltype(x) z x; // z 是 const int std::vectorint vec; auto size vec.size(); // 正确推导size_typeSFINAE 技术示例templatetypename T auto print(const T val) - decltype(std::cout val, void()) { std::cout val; } templatetypename T void print(...) { static_assert(false, Type not printable); }9. 异常安全的代码设计基本保证确保异常发生时程序处于有效状态强保证要么操作完全成功要么状态完全不改变不抛保证承诺不抛出异常示例实现class File { public: void write(const std::string content) { std::string newContent buffer; newContent content; // 先在副本上操作 FILE* f fopen(path, w); if (!f) throw std::runtime_error(Open failed); if (fputs(newContent.c_str(), f) EOF) { fclose(f); throw std::runtime_error(Write failed); } fclose(f); buffer std::move(newContent); // 最后更新状态 } private: std::string path; std::string buffer; };10. 现代 C 的最佳实践资源管理优先使用智能指针而非原始指针使用 RAII 封装资源文件、锁等类型系统用 enum class 替代传统 enum使用 nullptr 替代 NULL性能优化移动语义减少不必要的拷贝constexpr 实现编译期计算多线程使用 std::atomic 进行无锁编程优先选择 std::async 而非直接创建线程易错点总结在构造函数中调用虚函数不会多态忽略异常规格说明noexcept 的影响错误估计 shared_ptr 的线程安全性引用计数原子对象访问非原子滥用 dynamic_cast性能开销大忽略移动操作的 noexcept 声明影响标准库行为