C++八股文(面向对象编程2)

1. C++ 中如何使用析构函数进行资源释放?

  • RAII原则:资源获取即初始化,构造函数获取资源,析构函数释放资源。对象生命周期结束时自动释放
  • 释放内容:动态分配的内存(delete/delete[])、文件句柄(fclose)、网络连接、互斥锁、数据库连接等
  • 调用时机:栈对象离开作用域、delete堆对象、程序结束时全局对象销毁
  • 异常安全:析构函数不应抛出异常,用try-catch捕获并处理
  • 虚析构函数:多态基类的析构函数必须是虚函数,确保通过基类指针删除派生类对象时正确释放资源
  • 智能指针:现代C++推荐用unique_ptr、shared_ptr自动管理资源,避免手动delete
  • 最佳实践:一个类管理一种资源,遵循单一职责原则

2. 如何实现 C++ 中的单例模式(Singleton)?

懒汉式(线程安全,C++11)

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
public:
    static Singleton& getInstance() {
        static Singleton instance;  // C++11保证线程安全
        return instance;
    }
};

  • Meyers单例:上面的方法,利用C++11静态局部变量初始化的线程安全性
  • 饿汉式:程序启动时就创建实例,线程安全但可能浪费资源
  • 双重检查锁定:C++11前的方案,现在不推荐,复杂且容易出错
  • 关键点:①私有构造函数 ②删除拷贝构造和赋值运算符 ③静态获取实例方法 ④线程安全
  • 优点:全局唯一实例、延迟初始化、线程安全
  • 缺点:违反单一职责、难以测试、隐藏依赖关系
  • 使用场景:配置管理、日志系统、线程池、数据库连接池

3. C++ 中如何实现接口?

  • 纯虚函数接口:所有成员函数都是纯虚函数的抽象类
class IShape {
public:
    virtual ~IShape() = default;
    virtual double area() const = 0;
    virtual void draw() const = 0;
};

  • 命名约定:接口类名通常以I开头(IShape、ILogger)
  • 析构函数:应该是虚函数,可以有默认实现
  • 实现接口:派生类必须实现所有纯虚函数
  • 多接口实现:通过多重继承实现多个接口
  • 与Java/C#区别:C++没有interface关键字,用纯虚函数类模拟;可以有数据成员(但不推荐)
  • 最佳实践:接口应该小而专注,遵循接口隔离原则;不要在接口中放数据成员
  • 现代方案:C++20的concepts提供了更灵活的接口定义方式

4. C++ 中的拷贝构造函数是什么?

  • 定义:参数是同类型对象的const引用的构造函数,MyClass(const MyClass& other)
  • 调用时机:①用对象初始化另一对象 ②对象作为函数参数按值传递 ③函数按值返回对象(可能被优化掉)
  • 默认行为:编译器生成的默认版本执行浅拷贝(逐成员复制)
  • 何时自定义:类中有指针成员、动态资源、需要深拷贝时
  • 实现要点:分配新资源、复制内容、处理异常
  • 参数必须是引用:否则会无限递归(传参需要拷贝,拷贝又需要传参)
  • const引用:允许从临时对象和const对象拷贝
  • 移动语义:C++11引入移动构造函数,避免不必要的拷贝

5. C++ 中的赋值运算符重载如何实现?

class MyClass {
public:
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {  // 1. 检查自赋值
            delete[] data;      // 2. 释放旧资源
            data = new int[other.size];  // 3. 分配新资源
            size = other.size;
            std::copy(other.data, other.data + size, data);  // 4. 复制内容
        }
        return *this;  // 5. 返回*this支持链式赋值
    }
};

  • 关键步骤:①检查自赋值 ②释放旧资源 ③分配新资源 ④复制内容 ⑤返回*this
  • 自赋值检查if (this != &other) 防止自己给自己赋值导致资源被错误释放
  • 返回类型:返回引用支持链式赋值 a = b = c;
  • 异常安全:copy-and-swap惯用法,先复制再交换,保证强异常安全
  • 移动赋值:C++11的 operator=(MyClass&& other) 转移资源所有权
  • 三五法则:需要自定义赋值运算符,通常也需要自定义析构函数和拷贝构造函数
  • =default/=delete:C++11可以显式要求编译器生成或禁止

6. 如何在 C++ 中实现动态多态?

  • 三要素:①基类有虚函数 ②派生类重写虚函数 ③通过基类指针或引用调用
class Animal {
public:
    virtual void speak() { cout << "Animal sound"; }
    virtual ~Animal() = default;
};
class Dog : public Animal {
public:
    void speak() override { cout << "Woof!"; }
};
// 使用
Animal* ptr = new Dog();
ptr->speak();  // 输出"Woof!" 动态绑定

  • 实现机制:虚函数表(vtable)+ 虚函数指针(vptr)。每个类有vtable存函数地址,对象有vptr指向vtable
  • override关键字:C++11明确标记重写,编译器检查是否真的重写了基类虚函数
  • final关键字:防止虚函数被进一步重写或类被继承
  • 纯虚函数virtual void func() = 0; 定义接口,强制派生类实现
  • 性能开销:虚函数调用多一次间接寻址,通常可接受
  • 注意事项:构造/析构中调用虚函数不会动态绑定;基类析构函数应该是虚函数

7. C++ 中如何使用 new 和 delete 管理内存?

  • new操作:①分配内存(operator new)②调用构造函数 ③返回指针
  • delete操作:①调用析构函数 ②释放内存(operator delete)
  • 数组形式new[]delete[] 必须配对,否则只析构第一个元素
  • 失败处理:new失败抛bad_alloc异常;new(nothrow) 失败返回nullptr
  • 定位newnew(ptr) T() 在指定内存位置构造对象,需要手动调用析构函数
  • 重载:可以重载operator new/delete自定义内存分配策略
  • 常见错误:①内存泄漏(忘记delete)②重复释放 ③new/delete不匹配 ④delete后继续使用
  • 现代方案:用智能指针(unique_ptr、shared_ptr)自动管理,避免手动new/delete

8. C++ 中的类型擦除(Type Erasure)是什么?

  • 定义:隐藏具体类型信息,提供统一接口,在不使用模板或继承的情况下实现多态
  • 实现方式:①继承+虚函数(传统多态)②模板+类型擦除容器(如std::function、std::any)③手动实现(持有void*和函数指针)
  • std::function示例:可以存储任何可调用对象(函数、lambda、函数对象),隐藏了具体类型
std::function<int(int)> func = [](int x) { return x * 2; };

  • std::any:C++17引入,可以存储任意类型的值,运行时类型安全
  • 优点:灵活性高、解耦、支持异构容器
  • 缺点:性能开销(动态分配、虚函数调用)、类型信息丢失
  • 使用场景:回调函数、事件系统、插件架构、异构容器
  • 与模板区别:模板是编译期多态,类型擦除是运行期多态

9. C++ 中的模板特化(Template Specialization)是什么?

  • 定义:为特定类型提供模板的特殊实现
  • 全特化:为所有模板参数指定具体类型
template<typename T> class Vector { /*通用实现*/ };
template<> class Vector<bool> { /*bool特化实现*/ };

  • 偏特化(类模板):部分模板参数特化
template<typename T, typename U> class Pair { };
template<typename T> class Pair<T, int> { /*U=int的特化*/ };

  • 函数模板特化:只能全特化,不能偏特化(用重载代替)
  • 特化匹配规则:编译器选择最特化的版本
  • 使用场景:①优化特定类型(如Vector<bool>)②处理指针类型 ③类型萃取(type traits)
  • 注意事项:特化必须在使用前声明;函数模板优先考虑重载而非特化
  • STL应用:iterator_traits、char_traits等大量使用特化

10. 什么是C++中的多重继承,如何避免菱形继承?

  • 多重继承:一个类从多个基类继承,class C : public A, public B
  • 菱形继承问题:D继承B和C,B和C都继承A,导致D有两份A的副本,造成二义性和空间浪费
    A
   / \
  B   C
   \ /
    D

  • 虚拟继承解决:B和C用virtual继承A,class B : virtual public A,确保D只有一份A
  • 实现机制:虚基类表(vbtable)和虚基类指针(vbptr),运行时定位唯一的基类
  • 构造顺序:虚基类由最底层派生类(D)直接构造,B和C的构造调用被忽略
  • 性能开销:增加内存(vbptr)和访问开销(间接寻址)
  • 替代方案:①优先使用组合而非继承 ②接口继承+实现组合 ③单一继承链
  • 使用建议:多重继承容易导致复杂性,除非必要(如多接口实现),否则避免使用
  • 最佳实践:如果使用多重继承,确保只有一个基类有实现,其他都是纯接口

C++八股文全集 文章被收录于专栏

本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务