1.继承是什么
就是通过定义一个公共的类(基类),让需要公共信息的类(派生类)直接去继承;继承有单继承和多继承;
2.继承的本质:
本质就是复用
3.继承的语法
class person //基类
{
public:
int id;
int age;
string place;
};
class student:public person //public为继承方式 student 派生类
{
public:
int _studid;
int _major;
}
①继承方式:public > protected > private;
访问方式:public > protected > private;
最后函数和成员变量到底能不能看到取决于 继承方式和访问方式中"小"的呢个;
②在 C++ 中,如果派生类使用 class 定义,则默认继承方式是 private;使用 struct 定义,则默认继承方式是 public。
③子类继承了基类的私有成员(在内存布局中存在),但不能直接访问它们(在派生类的作用域中不可见)。从"继承"一词在 C++ 标准语义上,私有成员是被继承但不可直接访问的,
④友元关系不能继承
⑤静态成员存储在静态区,继承后不仅仅属于父类,也属于子类;
4.赋值兼容转换(仅限于共有继承)
派生类的对象可以赋值给积累的对象/指针/引用;
class Base { /*...*/ };
class Derived : public Base { /*...*/ };
Derived d;
Base b = d; // 合法赋值
class Base { /*...*/ };class Derived : public Base { /*...*/ };
Derived d;
Base b = d; // 合法赋值
Derived d;Base& rb = d; // 合法绑定
这种赋值的本质是将派生类对象中的基类部分提取出来:
- 对于对象赋值,会调用基类的拷贝构造函数
- 对于指针/引用,只是改变了访问的视角,实际对象仍然是派生类对象
- 反向赋值(基类赋给派生类)是不允许的
- 如果基类有虚函数,通过基类指针/引用调用时会正确调用派生类的实现
5.重写(覆盖),重定义(隐藏 ) ,重载

①重载(overload)发生在同一作用域(如一个类内或全局),继承体系中不同作用域的同名函数不是重载,是隐藏
cpp
// 1. 重载 Overload(同一作用域,函数名相同,参数不同)
class OverloadDemo {
public:
void show(int x) { cout << "show(int): " << x << endl; }
void show(double x) { cout << "show(double): " << x << endl; } // 重载
};
②重定义(隐藏) 基类和派生类的作用域不同,同名函数不构成重载,而是隐藏。如果基类有 func(int),派生类有 func(double),则派生类对象调用时,基类的版本会被隐藏,不能直接找到;
++***重写(覆盖)和重定义(隐藏)*都发生在继承中++
++重写是重定义的一种特殊情况(特指虚函数且原型相同++)
③重写(覆盖) 当派生类中定义的函数与基类函数原型完全相同时,若基类函数为虚函数,则构成覆盖(重写)以实现多态;若基类函数非虚函数,则派生类函数会隐藏基类同名函数。虚函数是在基类中用virtual关键字声明的成员函数,允许派生类对其进行覆盖,以实现运行时多态。
cpp
// 2. 重写 Override(虚函数 + 继承 + 原型相同)
class Base {
public:
virtual void func() { cout << "Base::func()" << endl; } // 虚函数
};
class Derived : public Base {
public:
void func() override { cout << "Derived::func()" << endl; } // 重写(覆盖)
};
// 3. 重定义 Redefine(继承 + 同名隐藏)
class Base2 {
public:
void test() { cout << "Base2::test()" << endl; }
void test(int) { cout << "Base2::test(int)" << endl; }
};
class Derived2 : public Base2 {
public:
// 重定义(隐藏基类所有 test 版本)
void test() { cout << "Derived2::test()" << endl; }
};
return 0;
}
++"函数原型完全相同"是指函数名、参数列表(参数类型、个数、顺序)以及是否为const成员函数都完全一致。++
++必须是基类中的该函数声明为虚函数,派生类中重写(override)才构成运行时多态,若只是派生类里写virtual而基类没有,不是覆盖++
④重载要求函数名相同、参数不同;重写要求原型相同(且基类函数是虚函数);重定义只需同名即可(参数可同可不同,会隐藏基类同名函数)
6.什么是虚函数
严格来说"被 virtual 修饰的函数"不一定就是虚函数(必须是非静态成员函数):构造函数不能是虚函数,普通成员函数才可以设为虚函数(此外还有如静态成员函数也不能是虚函数)。

7.为什么静态成员函数不能是虚函数?
静态成员函数没有 this 指针,无法通过虚表调用,因此不能是虚函数,且静态函数不参与重写(覆盖)
8.为什么基类析构函数建议设为虚函数?
确保用基类指针删除派生类对象时能正确调用派生类析构函数,避免内存泄漏。
9.多态调用条件
通过基类指针或引用调用虚函数,且该虚函数在派生类中被覆盖。
10.纯虚函数,抽象类
①什么是纯虚函数:
纯虚函数是在基类中声明为 virtual 返回类型 函数名(参数) = 0 的特殊虚函数,它没有(或不要求)基类实现,强制派生类必须重写该函数,从而使该类成为抽象类,无法实例化,用于定义统一的接口规范。
cpp
class Shape { // 抽象类
public:
virtual void draw() const = 0; // 纯虚函数
// 纯虚函数可以有函数体(可选)
virtual double area() const = 0;
// 普通成员函数
void printType() const {
cout << "This is a shape" << endl;
}
virtual ~Shape() {} // 虚析构函数
};
②纯虚函数的作用
定义接口规范
强制派生类必须实现特定功能,确保所有子类都有统一的接口。
实现抽象类
使类成为抽象类,不能实例化,只能作为基类使用。
实现多态基础
通过基类指针/引用调用不同派生类的实现,实现运行时多态。
框架设计
在设计模式(如模板方法模式)中定义算法骨架,让子类实现具体步骤
③什么是抽象类
抽象类是包含至少一个纯虚函数的类,它不能直接实例化对象,主要用于作为基类定义接口。
④抽象类的作用
定义接口规范 - 强制派生类实现特定方法
实现多态基础 - 通过基类指针统一管理不同子类对象
cpp
// 1. 声明抽象类(含纯虚函数)
class Animal {
public:
virtual void makeSound() = 0; // 纯虚函数
virtual void move() = 0; // 纯虚函数
void sleep() { // 普通成员函数
cout << "Sleeping..." << endl;
}
virtual ~Animal() {}
};
// 2. 派生类必须实现所有纯虚函数
class Dog : public Animal {
public:
void makeSound() override {
cout << "Woof!" << endl;
}
void move() override {
cout << "Running on four legs" << endl;
}
};
11.基类派生类的析构构造顺序
初始化先父类后子类;
析构先子类后父类;(先父类析构,在调用父类,会出现"野指针")
注意:父类的析构不需要显式调用,会自动在所有析构调用后调用
12.多态
①什么是多态
多种形态,具体点就是为了去完成某个行为,当不同的对象去完成是会产生不同的效果;
②多态和继承的关系
多态依赖于继承,但不是所有继承都产生多态。
必要条件:继承 + 虚函数(覆盖) + 基类指针/引用调用
继承提供基础:派生类从基类派生,形成层次结构
多态实现方式:通过基类接口统一操作不同派生类对象
简言之:继承是多态的前提,多态是继承的高级应用。
③多态的条件
虚函数的重写(函数名、参数、返回值完全相同),重写它的实现
父类指针或者应用调用虚函数;
④多态的分类

14.面试题(为什么析构函数建议设置成虚函数以及虚函数为什么要重写)
析构函数设为虚函数,正是利用虚函数重写机制实现多态销毁:虚析构函数参与重写当基类析构函数声明为 virtual 时,派生类析构函数自动成为重写版本(即使不写 override)。
举个例子
cpp
#include <iostream>
using namespace std;
class Animal {
public:
Animal() { cout << "动物出生\n"; }
virtual ~Animal() { cout << "动物去世\n"; } // 虚析构函数
};
class Cat : public Animal {
private:
char* name;
public:
Cat() {
name = new char[10]; // 猫独有资源:动态内存
strcpy(name, "小花");
cout << "小猫" << name << "出生\n";
}
~Cat() {
cout << "小猫" << name << "去世\n";
delete[] name; // 清理猫独有资源
}
};
int main() {
Animal* myPet = new Cat(); // 基类指针指向派生类对象
delete myPet; // 关键在这里!
return 0;
}
情况1:虚析构函数(如上代码)
text
动物出生
小猫小花出生
小猫小花去世 ← 先调用Cat的析构函数,清理猫独有资源
动物去世 ← 再调用Animal的析构函数
✅ 资源完全释放
情况2:析构函数非虚(去掉virtual)
text
动物出生
小猫小花出生
动物去世 ← 只调用了Animal析构函数!
❌ 内存泄漏:name 指向的10字节内存永远无法释放,因为Cat::~Cat()根本没被调用!
这就是为什么:当通过基类指针删除派生类对象时,虚析构函数确保调用正确的派生类析构函数,避免资源泄漏。