类的继承
前言
类的继承是面向对象编程(OOP)的核心特性之一,核心价值在于 代码重用 和 逻辑分层,通过抽取不同对象的共性抽象为父类,子类基于父类扩展特有属性和方法,减少重复代码并提升框架稳定性。
类继承的核心概念
定义
类的继承是对对象概念的 纵向抽象模拟:将不同对象的共性属性/方法抽离为 父类(基类,Base Class),再通过 派生 生成 子类(派生类,Derived Class),子类自动拥有父类的非私有成员,同时可扩展自身特有成员。
核心优势
- 代码重用:无需重复编写父类已定义的共性逻辑
- 逻辑清晰:构建对象间的层级关系,符合现实世界认知
- 易于维护:修改父类共性逻辑时,所有子类自动受益
经典示例(现实世界抽象)
示例逻辑:猫、狗均属于哺乳动物,共性(体温、体重)抽象到
Mammal父类,子类仅关注特有行为(喵喵叫、汪汪叫)。
继承的基本语法
语法格式
// 父类定义
class 父类名 {
// 成员属性 + 成员方法
};
// 子类继承语法:class 子类名 : 继承方式 父类名
class 子类名 : 继承方式 父类名 {
public:
// 子类构造函数(需显式调用父类构造函数)
子类名(参数列表) : 父类名(父类参数列表) {
// 子类初始化逻辑
}
// 子类特有属性 + 方法
};
实战示例(哺乳动物→猫/狗)
#include <iostream>
using namespace std;
// 父类:哺乳动物
class Mammal {
float heat; // 体温(共性属性)
float weight; // 体重(共性属性)
public:
// 父类构造函数(默认参数)
Mammal(float h = 0.0f, float w = 0.0f) : heat(h), weight(w) {}
// 父类共性方法
void showInfo() const {
cout << "体温:" << heat << "℃" << endl;
cout << "体重:" << weight << "kg" << endl;
}
// 【补充】提供访问私有成员的接口(供子类间接访问)
float getHeat() const { return heat; }
float getWeight() const { return weight; }
};
// 子类:狗(公有继承 Mammal)
class Dog : public Mammal {
public:
// 子类构造函数:显式调用父类构造函数
Dog(float h, float w) : Mammal(h, w) {}
// 子类特有方法
void bark() const {
cout << "汪汪!" << endl;
}
};
// 子类:猫(公有继承 Mammal)
class Cat : public Mammal {
public:
// 子类构造函数:显式调用父类构造函数
Cat(float h, float w) : Mammal(h, w) {}
// 子类特有方法
void miaow() const {
cout << "喵喵!" << endl;
}
};
// 测试代码
int main() {
Dog dog(38.5f, 20.0f);
cout << "=== 狗的信息 ===" << endl;
dog.showInfo(); // 继承自父类的方法
dog.bark(); // 子类特有方法
Cat cat(38.7f, 5.0f);
cout << "\n=== 猫的信息 ===" << endl;
cat.showInfo(); // 继承自父类的方法
cat.miaow(); // 子类特有方法
return 0;
}
三种继承方式与访问控制
继承方式决定了父类成员在子类中的 访问权限,核心是控制子类及子类对象对父类成员的访问范围。
访问权限基础(父类成员本身的权限)
| 权限修饰符 | 父类内部 | 子类内部 | 类外部(对象) | 比喻(便于理解) |
|---|---|---|---|---|
private |
✅ | ❌ | ❌ | 父亲的日记(仅自己看) |
protected |
✅ | ✅ | ❌ | 家族祖传手艺(父子可用,外人不可) |
public |
✅ | ✅ | ✅ | 父亲的代步车(所有人可用) |
. 公有继承(public inheritance)
核心逻辑
- 表达 is-a 关系(子类是父类的一种),最常用、最符合现实逻辑
- 父类成员权限在子类中 保持不变(除
private成员不可直接访问)
语法规则
- 父类
private成员:子类不可直接访问,需通过父类提供的public接口间接访问 - 父类
protected成员:子类中仍为protected(子类内可访问,对象不可访问) - 父类
public成员:子类中仍为public(子类内、对象均可访问)
代码示例
#include <iostream>
using namespace std;
// 父类
class Base {
private:
int a; // 私有成员:仅父类内部访问
protected:
int b; // 保护成员:父类+子类内部访问
public:
int c; // 公有成员:任意位置访问
// 父类构造函数
Base(int a, int b, int c) : a(a), b(b), c(c) {}
// 提供访问私有成员a的公有接口
int getA() const { return a; }
};
// 子类:公有继承父类
class Derived : public Base {
public:
// 子类构造函数:显式调用父类构造函数
Derived(int a, int b, int c) : Base(a, b, c) {
cout << "子类构造完成" << endl;
}
// 子类内部访问父类成员
void showMembers() {
// cout << a; // 错误:无法直接访问父类private成员
cout << "父类private成员a(通过接口):" << getA() << endl;
cout << "父类protected成员b:" << b << endl; // 正确:子类内可访问
cout << "父类public成员c:" << c << endl; // 正确:子类内可访问
}
};
// 测试
int main() {
Derived d(10, 20, 30);
d.showMembers(); // 子类内访问父类成员
// 子类对象访问父类成员
// cout << d.a; // 错误:private成员不可访问
// cout << d.b; // 错误:protected成员不可通过对象访问
cout << "子类对象访问父类public成员c:" << d.c << endl; // 正确
return 0;
}
典型应用场景
- 猫 is-a 哺乳动物、汽车 is-a 交通工具、单选框 is-a 按钮
- 所有能对父类执行的操作,子类均可执行(如哺乳动物能维持体温,猫也能)
保护继承(protected inheritance)
核心逻辑
- 语法允许,但 极少使用(不符合现实逻辑)
- 父类所有非
private成员,在子类中均变为protected(最高权限为protected)
语法规则
- 父类
private成员:子类不可直接访问 - 父类
protected成员:子类中仍为protected - 父类
public成员:子类中变为protected(对象不可访问)
代码示例
#include <iostream>
using namespace std;
class Base {
private:
int a;
protected:
int b;
public:
int c;
};
// 保护继承
class Derived : protected Base {
public:
void f() {
// a = 100; // 错误:无法访问父类private成员
b = 100; // 正确:子类内可访问protected成员
c = 100; // 正确:父类public成员在子类中变为protected,子类内可访问
}
};
int main() {
Derived d;
// d.a = 100; // 错误:private成员不可访问
// d.b = 100; // 错误:protected成员不可通过对象访问
// d.c = 100; // 错误:父类public成员已变为protected,对象不可访问
return 0;
}
私有继承(private inheritance)
核心逻辑
- 表达 has-a 关系(子类包含父类的实现),极少使用(推荐用「类复合」替代)
- 父类所有非
private成员,在子类中均变为private(最高权限为private)
语法规则
- 父类
private成员:子类不可直接访问 - 父类
protected成员:子类中变为private(仅子类内可访问) - 父类
public成员:子类中变为private(仅子类内可访问)
代码示例
#include <iostream>
using namespace std;
// 父类:轮子(被包含的组件)
class Wheel {
private:
int radius; // 半径
protected:
int width; // 宽度
public:
int getRadius() const { return radius; }
Wheel(int r, int w) : radius(r), width(w) {}
};
// 子类:汽车(私有继承轮子,表示“汽车包含轮子”)
class Car : private Wheel {
public:
Car(int r, int w) : Wheel(r, w) {}
void showWheelInfo() {
// cout << radius; // 错误:无法直接访问父类private成员
cout << "轮子宽度:" << width << endl; // 正确:子类内可访问(已变为private)
cout << "轮子半径:" << getRadius() << endl; // 正确:通过父类接口访问
}
};
int main() {
Car car(18, 255);
car.showWheelInfo(); // 子类提供接口访问父类实现
// cout << car.width; // 错误:子类中已变为private,对象不可访问
// cout << car.getRadius(); // 错误:父类public成员已变为private,对象不可访问
return 0;
}
注意事项
- 私有继承的意义:继承父类的实现,但隐藏父类的接口
- 替代方案:用「类复合」(子类中定义父类对象作为成员)更清晰,避免逻辑混乱
// 类复合(推荐替代私有继承) class Car { private: Wheel wheel; // 汽车包含轮子对象(has-a) public: Car(int r, int w) : wheel(r, w) {} void showWheelInfo() { cout << "轮子半径:" << wheel.getRadius() << endl; } };
三种继承方式权限总结表
| 父类成员权限 | 公有继承(public) | 保护继承(protected) | 私有继承(private) |
|---|---|---|---|
private |
不可直接访问 | 不可直接访问 | 不可直接访问 |
protected |
保持 protected |
保持 protected |
变为 private |
public |
保持 public |
变为 protected |
变为 private |
类继承中的构造与析构
构造函数的调用规则
核心逻辑
- 构造顺序:先构造父类,再构造子类(先有地基,再盖房子)
- 若父类无默认构造函数(无参/全缺省),子类必须显式调用父类构造函数
代码示例(基础调用)
#include <iostream>
using namespace std;
class Base {
public:
// 父类构造函数
Base() { cout << "✅ 父类 Base 构造" << endl; }
};
class Derived : public Base {
public:
// 子类构造函数
Derived() { cout << "✅ 子类 Derived 构造" << endl; }
};
int main() {
Derived d; // 先调用父类构造,再调用子类构造
// 输出顺序:
// ✅ 父类 Base 构造
// ✅ 子类 Derived 构造
return 0;
}
代码示例(父类无默认构造函数)
#include <iostream>
using namespace std;
class Base {
private:
int x;
public:
// 父类无默认构造函数(必须传参)
Base(int x) : x(x) {
cout << "✅ 父类 Base 构造(x=" << x << ")" << endl;
}
};
class Derived : public Base {
private:
int y;
public:
// 子类必须显式调用父类构造函数(否则编译错误)
Derived(int x, int y) : Base(x), y(y) {
cout << "✅ 子类 Derived 构造(y=" << y << ")" << endl;
}
};
int main() {
Derived d(10, 20);
// 输出顺序:
// ✅ 父类 Base 构造(x=10)
// ✅ 子类 Derived 构造(y=20)
return 0;
}
析构函数的调用规则
核心逻辑
- 析构顺序:先析构子类,再析构父类(先拆房子,再拆地基)
- 若子类有动态内存分配,需显式定义析构函数;若涉及多态,父类析构函数需定义为
virtual(避免内存泄漏)
代码示例(基础调用)
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "✅ 父类 Base 构造" << endl; }
~Base() { cout << "❌ 父类 Base 析构" << endl; } // 析构函数
};
class Derived : public Base {
public:
Derived() { cout << "✅ 子类 Derived 构造" << endl; }
~Derived() { cout << "❌ 子类 Derived 析构" << endl; } // 析构函数
};
int main() {
Derived d;
// 输出顺序:
// ✅ 父类 Base 构造
// ✅ 子类 Derived 构造
// ❌ 子类 Derived 析构
// ❌ 父类 Base 析构
return 0;
}
📌 拓展:虚析构函数
当父类指针指向子类对象时,若父类析构函数非虚函数,会导致子类析构函数不被调用,造成内存泄漏:
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "✅ 父类 Base 构造" << endl; }
virtual ~Base() { cout << "❌ 父类 Base 析构" << endl; } // 虚析构函数
};
class Derived : public Base {
private:
int* arr; // 动态内存分配
public:
Derived() {
arr = new int[10];
cout << "✅ 子类 Derived 构造" << endl;
}
~Derived() {
delete[] arr; // 释放动态内存
cout << "❌ 子类 Derived 析构" << endl;
}
};
int main() {
Base* ptr = new Derived(); // 父类指针指向子类对象
delete ptr; // 若父类析构非虚函数,仅调用父类析构,子类析构不执行(内存泄漏)
// 输出顺序(虚析构情况下):
// ✅ 父类 Base 构造
// ✅ 子类 Derived 构造
// ❌ 子类 Derived 析构
// ❌ 父类 Base 析构
return 0;
}
拓展知识(进阶要点)
继承中的访问控制总结
| 访问场景 | public继承 | protected继承 | private继承 |
|---|---|---|---|
| 子类内部访问父类成员 | ✅(除private) | ✅(除private) | ✅(除private) |
| 子类对象访问父类成员 | ✅(仅public) | ❌ | ❌ |
| 子类的子类访问父类成员 | ✅(除private) | ✅(除private) | ❌ |
| 表达的关系 | is-a | 无明确关系 | has-a(不推荐) |
is-a 与 has-a 关系深入理解
| 关系类型 | 含义 | 实现方式 | 示例 |
|---|---|---|---|
| is-a | 子类是父类的一种 | public继承 | 猫 is-a 哺乳动物 |
| has-a | 类包含另一个类的对象 | 类复合(成员对象) | 汽车 has-a 轮子 |
注意:不要用private继承表达has-a关系,类复合更直观、易维护!
单继承与多继承
单继承(原文档示例)
- 定义:子类仅继承一个父类
- 优点:逻辑清晰、无歧义,推荐使用
- 示例:
class Dog : public Mammal { ... }
多继承(拓展)
- 定义:子类继承多个父类(
class 子类 : 继承方式 父类1, 继承方式 父类2 { ... }) - 优点:可同时重用多个父类的代码
- 问题:菱形继承(钻石继承)导致的二义性
[图示位置:菱形继承结构图]
菱形继承问题示例
#include <iostream>
using namespace std;
// 顶层父类
class Animal {
public:
void eat() { cout << "动物进食" << endl; }
};
// 中间父类1
class Dog : public Animal { ... };
// 中间父类2
class Wolf : public Animal { ... };
// 子类:同时继承Dog和Wolf(菱形继承)
class WolfDog : public Dog, public Wolf { ... };
int main() {
WolfDog wd;
// wd.eat(); // 错误:二义性(Dog::eat 和 Wolf::eat 冲突)
wd.Dog::eat(); // 需显式指定父类,解决二义性
return 0;
}
解决菱形继承:虚继承(virtual inheritance)
通过 virtual 关键字让中间父类共享顶层父类的实例,避免重复继承:
// 中间父类1:虚继承Animal
class Dog : virtual public Animal { ... };
// 中间父类2:虚继承Animal
class Wolf : virtual public Animal { ... };
int main() {
WolfDog wd;
wd.eat(); // 正确:无歧义(仅一个Animal实例)
return 0;
}
继承与虚函数
- 虚函数:父类中用
virtual声明的成员函数,子类可重写(override) - 作用:实现多态(父类指针指向子类对象时,调用子类的重写方法)
- 示例:
#include <iostream>
using namespace std;
class Mammal {
public:
virtual void makeSound() { cout << "哺乳动物发出声音" << endl; } // 虚函数
};
class Dog : public Mammal {
public:
void makeSound() override { cout << "汪汪!" << endl; } // 重写虚函数
};
class Cat : public Mammal {
public:
void makeSound() override { cout << "喵喵!" << endl; } // 重写虚函数
};
int main() {
Mammal* m1 = new Dog();
Mammal* m2 = new Cat();
m1->makeSound(); // 输出:汪汪!(调用子类重写方法)
m2->makeSound(); // 输出:喵喵!(调用子类重写方法)
delete m1;
delete m2;
return 0;
}
核心要点总结
- 继承的核心是 代码重用 和 逻辑分层,优先使用
public继承(is-a关系) - 继承方式决定父类成员在子类的访问权限,
private成员永远不可被子类直接访问 - 构造顺序:先父后子;析构顺序:先子后父,多态场景下父类析构需为虚函数
- 避免用
protected/private继承,has-a关系用「类复合」实现更清晰 - 多继承易引发菱形继承问题,可通过「虚继承」解决
- 虚函数是实现多态的基础,子类重写时可加
override关键字明确意图

浙公网安备 33010602011771号