C++八股文(面向对象编程)
1. C++ 中的继承有几种类型?
- public继承:基类的public成员在派生类中仍是public,protected仍是protected。是"is-a"关系,最常用
- protected继承:基类的public和protected成员在派生类中都变成protected。外部无法访问
- private继承:基类的public和protected成员在派生类中都变成private。实现"用...实现"的关系,类似组合
- 访问规则:无论哪种继承,基类的private成员派生类都不能直接访问
- 使用建议:90%以上情况用public继承;需要隐藏基类接口时用private继承,但组合通常更好
- 多重继承:可以从多个基类继承,但要注意菱形继承问题
2. C++ 中如何实现多态?
- 编译时多态:函数重载、运算符重载、模板。编译期确定调用
- 运行时多态:通过虚函数实现。三个条件:①基类有虚函数 ②派生类重写虚函数 ③通过基类指针或引用调用
- 实现机制:每个有虚函数的类有虚函数表(vtable),对象有虚函数指针(vptr)指向vtable。运行时通过vptr查表调用正确的函数
- 典型应用:接口设计、策略模式、工厂模式等设计模式
- 性能开销:虚函数调用有间接寻址开销,但通常可以接受
- 注意事项:基类析构函数应该是虚函数,否则delete基类指针时不会调用派生类析构函数
3. C++ 中的虚函数是什么?
- 定义:用virtual关键字修饰的成员函数,支持动态绑定
- 作用:允许派生类重写(override)基类函数,通过基类指针/引用调用时执行派生类版本
- 语法:
virtual void func();派生类重写时virtual可省略,但建议加override关键字 - 虚函数表:编译器为每个有虚函数的类生成vtable,存储虚函数地址。对象的vptr指向对应的vtable
- 构造/析构中的虚函数:构造函数不能是虚函数;析构函数应该是虚函数;构造/析构过程中调用虚函数不会动态绑定
- 性能:虚函数调用比普通函数多一次间接寻址,通常影响很小
- final关键字:C++11可用final防止虚函数被进一步重写
4. C++ 中的纯虚函数(Pure Virtual Function)是什么?
- 定义:在基类中声明但不实现的虚函数,语法:
virtual void func() = 0; - 作用:定义接口规范,强制派生类必须实现该函数
- 抽象类:包含纯虚函数的类是抽象类,不能实例化
- 派生类要求:派生类必须实现所有纯虚函数才能实例化,否则仍是抽象类
- 可以有实现:纯虚函数可以提供默认实现,派生类可以显式调用
- 使用场景:定义接口、框架设计、多态基类
- 示例:
class Shape { virtual double area() = 0; };
5. C++ 中的抽象类(Abstract Class)是什么?
- 定义:包含至少一个纯虚函数的类
- 特点:不能实例化,只能作为基类;可以有数据成员、普通成员函数、构造函数和析构函数
- 用途:定义接口规范、提供公共实现、实现多态
- 与接口区别:C++没有interface关键字,抽象类可以有实现和数据成员;纯接口类所有函数都是纯虚函数
- 指针和引用:可以声明抽象类的指针和引用,指向派生类对象
- 设计原则:基类应该是抽象的,定义"做什么";派生类是具体的,定义"怎么做"
- 析构函数:抽象类的析构函数应该是虚函数
6. C++ 中的虚拟继承是什么?
- 问题背景:菱形继承导致最底层派生类有多份顶层基类的副本,造成二义性和空间浪费
- 解决方案:用virtual关键字继承,
class B : virtual public A,确保只有一份基类副本 - 实现机制:通过虚基类表(vbtable)和虚基类指针(vbptr)实现,运行时定位唯一的基类子对象
- 构造顺序:虚基类由最底层派生类直接构造,中间类的构造调用被忽略
- 性能开销:增加了内存开销(vbptr)和访问开销(间接寻址)
- 使用场景:解决菱形继承问题,但应该优先考虑组合而非多重继承
- 注意事项:所有中间类都要用virtual继承才能解决问题
7. 什么是构造函数和析构函数的顺序?
- 构造顺序:①虚基类(按声明顺序)②直接基类(按声明顺序,不是初始化列表顺序)③成员对象(按声明顺序)④构造函数体
- 析构顺序:完全相反,①析构函数体 ②成员对象 ③直接基类 ④虚基类
- 多重继承:按基类在派生类声明中的顺序
- 初始化列表:成员初始化顺序由声明顺序决定,不是初始化列表中的顺序,要注意依赖关系
- 异常安全:构造过程中抛异常,已构造的成员会自动析构,但对象本身的析构函数不会调用
- 虚函数调用:构造/析构过程中调用虚函数不会动态绑定,调用的是当前类的版本
- 最佳实践:初始化列表顺序应与声明顺序一致,避免混淆
8. C++ 中如何实现运算符重载?
- 语法:
返回类型 operator符号(参数列表),如Complex operator+(const Complex& other) - 成员函数形式:左操作数是this,
c1 + c2调用c1.operator+(c2) - 友元函数形式:两个操作数都是参数,
c1 + c2调用operator+(c1, c2),支持左操作数类型转换 - 不能重载的运算符:
::、.、.*、?:、sizeof、typeid - 必须是成员函数:
=、[]、()、-> - 常见重载:算术运算符、比较运算符、流运算符(
<<、>>)、下标运算符、函数调用运算符 - 返回值:算术运算符返回值,赋值运算符返回引用支持链式调用
- 最佳实践:保持运算符的直观语义,不要滥用
9. C++ 中如何使用友元函数(Friend Function)?
- 定义:在类中用friend声明的非成员函数,可以访问类的private和protected成员
- 声明:
class MyClass { friend void func(MyClass& obj); }; - 特点:不是成员函数,没有this指针;破坏封装性,但有时必要
- 使用场景:①运算符重载(如
<<、>>需要左操作数是流对象)②需要访问多个类的私有成员 ③提高效率避免getter/setter - 友元类:
friend class OtherClass;允许另一个类访问私有成员 - 单向性:A是B的友元,B不自动是A的友元
- 不传递:A是B的友元,B是C的友元,A不是C的友元
- 不继承:基类的友元不是派生类的友元
- 最佳实践:尽量少用,优先考虑public接口
10. 什么是深拷贝与浅拷贝?
- 浅拷贝:逐位复制对象,指针成员只复制地址值。多个对象共享同一块内存,析构时会重复释放导致崩溃
- 深拷贝:不仅复制对象,还为指针成员分配新内存并复制内容。每个对象独立管理资源
- 默认行为:编译器生成的拷贝构造函数和赋值运算符执行浅拷贝
- 何时需要深拷贝:类中有指针成员、动态分配资源、文件句柄等需要独立管理的资源
- 三五法则:需要自定义析构函数,通常也需要自定义拷贝构造函数和拷贝赋值运算符(C++11还包括移动构造和移动赋值)
- 实现要点:拷贝赋值要处理自赋值、释放旧资源、分配新资源、复制内容
- 现代方案:使用智能指针(shared_ptr、unique_ptr)自动管理资源,避免手动深拷贝
// 深拷贝示例
class String {
char* data;
public:
// 深拷贝构造
String(const String& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
// 深拷贝赋值
String& operator=(const String& other) {
if (this != &other) { // 防止自赋值
delete[] data;
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
return *this;
}
~String() { delete[] data; }
};
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。