如何将类继承为一个?
摘要:前言 类的继承是面向对象编程(OOP)的核心特性之一,核心价值在于 代码重用 和 逻辑分层,通过抽取不同对象的共性抽象为父类,子类基于父类扩展特有属性和方法,减少重复代码并提升框架稳定性。 类继承的核心概念 定义 类的继承是对对象概念的 纵
前言
类的继承是面向对象编程(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 关键字明确意图
