C++面试7——继承与多态
在C++面试中,继承与多态是考察面向对象理解深度的核心考点。以下是深入浅出的解析,包含关键概念、技术细节和面试常见陷阱:
继承(Inheritance)
核心概念
- "is-a"关系:派生类(Derived)是基类(Base)的特殊化(如
Car
继承Vehicle
) - 代码复用:派生类继承基类的成员(数据+方法)
- 访问控制:
public
继承:基类public→派生public,protected→protectedprotected
继承:基类public/protected→派生protectedprivate
继承:基类所有成员→派生private(极少用)
面试高频坑点
-
切片问题(Slicing)
class Base { /*...*/ }; class Derived : public Base { /*...*/ };Derived d; Base b = d; // 对象切片!丢失Derived特有成员
解决方案:使用指针/引用
Base& b = d;
-
构造函数调用顺序
class Derived : public Base { public:Derived() : Base() {} // 必须显式调用基类有参构造 };
- 构造顺序:基类→成员对象→自身
- 析构顺序:自身→成员对象→基类
-
名字隐藏(Name Hiding)
class Base { public:void func(int) {} }; class Derived : public Base { public:void func() {} // 隐藏Base::func(int)! };Derived d; d.func(1); // 错误!被隐藏 d.Base::func(1); // 正确但丑陋
解决:使用
using Base::func;
引入基类函数
多态(Polymorphism)
核心概念
- 动态绑定:运行时根据对象实际类型调用函数
- 实现机制:虚函数表(vtable)+ 虚指针(vptr)
- 每个含虚函数的类有自己的vtable
- 对象创建时vptr指向对应vtable
- 关键语法:
class Shape { public:virtual double area() const = 0; // 纯虚函数→抽象类virtual ~Shape() {} // 虚析构!必须! }; class Circle : public Shape { public:double area() const override { /*...*/ } // override确保正确重写 };
面试致命陷阱
-
忘记虚析构函数
Base* p = new Derived(); delete p; // 若~Base()非虚→仅调用~Base()!内存泄漏!
规则:基类有虚函数时,析构函数必须为虚
-
虚函数默认参数静态绑定
class Base { public:virtual void print(int x = 1) { cout << x; } }; class Derived : public Base { public:void print(int x = 2) override { cout << x; } };Base* p = new Derived(); p->print(); // 输出1!默认参数静态绑定(Base版)
解决:避免在虚函数中使用默认参数
-
构造函数/析构函数中调用虚函数
class Base { public:Base() { callVirt(); } // 危险!virtual void callVirt() { cout << "Base"; } }; class Derived : public Base { public:void callVirt() override { cout << "Derived"; } };Derived d; // 输出"Base"!构造期间对象类型是Base
原理:构造/析构期间虚函数机制未完全生效
继承 vs 组合:面试灵魂拷问
场景 | 继承 | 组合(包含对象成员) |
---|---|---|
关系 | “is-a” (Car is a Vehicle) | “has-a” (Car has an Engine) |
耦合度 | 高耦合 | 低耦合 |
复用 | 白盒复用(了解基类实现) | 黑盒复用(仅使用接口) |
典型误用 | 为复用公共代码而继承(应组合) | 需要多态时未用继承 |
黄金法则:
- 需要多态 → 用继承
- 只需复用代码 → 用组合
- 避免多继承(菱形继承问题)
面试实战技巧
-
必考问题:
- “虚函数实现原理?” → 答vptr+vtable
- “为什么需要虚析构?” → 举例内存泄漏场景
- “override关键字作用?” → 防止签名错误隐藏函数
-
代码题陷阱:
class A { public: virtual void f() { cout << "A"; } }; class B : public A { public:void f() { cout << "B"; } };A* pa = new B(); pa->f(); // 问输出?→ B(多态正确) delete pa; // 问是否安全?→ 不安全!若~A()非虚则UB
-
高级话题:
- 动态转换:
dynamic_cast<Derived*>(basePtr)
(需RTTI) - final关键字:禁止派生/重写
class NoDerived final { /*...*/ }; // 禁止继承 class Base { public:virtual void lock() final; // 禁止重写 };
- 动态转换:
总结:面试回答模板
"C++多态通过虚函数机制实现,核心是运行时动态绑定。使用时需注意:
- 基类声明虚析构函数防止资源泄漏
- 使用override确保正确重写虚函数
- 避免在构造/析构中调用虚函数
- 优先用组合代替继承除非需要多态
继承应严格满足Liskov替换原则:派生类必须能替代基类工作"
掌握这些要点,面试官会认为你真正理解C++面向对象的精髓!