1. const 与 #define 的区别

  • 处理阶段‌:#define 在预处理阶段进行文本替换;const 在编译阶段处理。
  • 类型检查‌:#define 没有类型,不进行类型安全检查;const 有具体数据类型,编译器会进行类型检查。
  • 调试支持‌:const 常量可以被调试器跟踪;#define 宏在预处理后消失,难以调试。
  • 内存分配‌:const 通常会分配内存(存储在数据段);#define 只是代码展开,不分配内存。

2. sizeof 和 strlen 的区别

  • sizeof 是运算符,计算的是变量或类型所占用的内存字节数,在编译时确定。对于数组,它返回整个数组的大小。
  • strlen 是库函数,计算的是字符串的有效长度(不包含结尾的 \0),在运行时确定。

3、引用与指针的区别

  • 本质‌:指针是一个实体,存放地址,需要分配内存;引用是变量的别名,底层通常由指针实现,但不单独分配内存空间。
  • 初始化‌:引用必须在定义时初始化,且一旦绑定不能改变指向;指针可以不初始化,且可以随时改变指向。
  • 多级‌:有多级指针(如 int**),但没有多级引用。
  • 自增运算‌:指针自增指向下一个内存地址;引用自增则是所绑定变量的值增加。
  • 空值‌:指针可以为 nullptr;引用必须绑定有效对象,不存在空引用。

4、malloc/free 与 new/delete 的区别

  • 属性‌:malloc/free 是 C 语言标准库函数new/delete 是 C++ 运算符
  • 构造/析构‌:new 在分配内存后会自动调用构造函数,delete 在释放内存前会自动调用析构函数;malloc/free 仅负责内存分配与释放,不涉及对象生命周期管理。
  • 返回值‌:malloc 返回 void*,需强制类型转换;new 返回指定类型的指针,无需转换。
  • 失败处理‌:malloc 失败返回 NULLnew 失败抛出 std::bad_alloc 异常。
  • 大小计算‌:malloc 需手动指定字节数;new 自动计算类型大小。

5、delete 与 delete[] 的区别

  • delete 用于释放单个对象内存,调用一次析构函数。
  • delete[] 用于释放数组内存,会为数组中的每个元素调用析构函数,然后释放内存。
  • 注意‌:对于基本数据类型(如 int),两者效果通常相同;但对于自定义类对象,混用会导致未定义行为(如内存泄漏或析构不完全)。

6、如何避免野指针?

  • 指针定义时初始化为 nullptr
  • 指针释放后立即置为 nullptr
  • 避免返回局部变量的地址或引用。
  • 使用智能指针(shared_ptrunique_ptr)管理资源,利用 RAII 机制自动释放。

7、内存泄漏与智能指针

  • RAII (Resource Acquisition Is Initialization)‌:资源获取即初始化,利用对象生命周期管理资源。
  • unique_ptr‌:独占所有权,不可拷贝,可移动。轻量级,性能开销小。
  • shared_ptr‌:共享所有权,通过引用计数管理。当计数为 0 时释放资源。注意循环引用问题。
  • weak_ptr‌:配合 shared_ptr 使用,不增加引用计数,用于解决循环引用。可通过 expired() 判断对象是否存活,通过 lock() 获取 shared_ptr

8、多态的实现原理

  • 静态多态‌:通过函数重载和模板实现,在编译期确定。
  • 动态多态‌:通过虚函数实现,在运行期确定。
  • 底层机制‌:包含虚函数的类会有一个‌虚函数表 (vtable)‌,对象头部有一个‌虚指针 (vptr)‌ 指向该表。调用虚函数时,通过 vptr 找到 vtable,再找到对应的函数地址进行调用。

9、虚函数与纯虚函数

  • 虚函数‌:允许子类重写,基类可以提供默认实现。
  • 纯虚函数‌:声明为 virtual void func() = 0;,基类无实现,包含纯虚函数的类称为‌抽象类‌,不能实例化,强制子类实现该接口。

10、构造函数与析构函数

  • 构造函数‌:不能是虚函数(因为对象尚未完全构建,vptr 未初始化)。
  • 析构函数‌:基类析构函数应声明为‌虚函数‌。如果基类析构函数不是虚函数,通过基类指针删除派生类对象时,只会调用基类析构函数,导致派生类部分资源泄漏。
  • 初始化列表‌:以下情况必须使用初始化列表:
    • 成员是引用类型。
    • 成员是 const 类型。
    • 成员类没有默认构造函数。
    • 调用父类带参构造函数。

11、vector 与 list 的区别

  • vector‌:动态数组,内存连续。支持随机访问(O(1)),尾部插入删除高效(O(1)),中间插入删除低效(O(n),需移动元素)。扩容时可能涉及内存重新分配和数据拷贝。
  • list‌:双向链表,内存不连续。不支持随机访问(O(n)),任意位置插入删除高效(O(1),只需修改指针)。

12、map 与 unordered_map 的区别

  • map‌:基于‌红黑树‌实现,键值对有序,查找、插入、删除时间复杂度为 O(log n)。
  • unordered_map‌:基于‌哈希表‌实现,键值对无序,平均查找、插入、删除时间复杂度为 O(1),最坏情况 O(n)。
13、迭代器失效
  • vector/deque‌:执行 erase 或 insert 后,指向被操作元素及其之后元素的迭代器失效。vector 扩容会导致所有迭代器失效。
  • list‌:erase 仅使指向被删除元素的迭代器失效,其他迭代器依然有效。
  • map/set‌:erase 仅使指向被删除元素的迭代器失效,其他迭代器有效(因为红黑树节点独立分配)。

14、右值引用与移动语义 (move)

  • 左值‌:有名称、可取地址的对象。
  • 右值‌:临时对象、字面量等。
  • 移动语义‌:通过 std::move 将左值转换为右值引用,触发移动构造函数或移动赋值运算符,从而“窃取”资源(如指针指向的内存),避免深拷贝,提升性能。

15、完美转发 (forward)

  • 在模板编程中,为了保持参数的左右值属性不变地传递给内部函数,使用 std::forward<T>(arg)。结合万能引用 (T&&) 使用。

更多推荐