
🎬 个人主页:Vect个人主页 🎬 GitHub:Vect的代码仓库 🔥 个人专栏: 《数据结构与算法》《C++学习之旅》《计算机基础》
⛺️Per aspera ad astra.
在实现一个系统时,我们会经常遇到具有类似属性,但细节或行为存在细微差异的组件 。比如说外卖配送系统:最重要的三个角色:骑手、客户和商家,他们有共同的属性:地址、联系方式等等,也有每个类个性化的属性。
这种情况有两种解决方式:
这便是面向对象的第二大特性:继承
如图:类之间的继承关系

根据上述介绍,可以总结出继承的定义:
继承是面向对象程序设计使代码可以复用的手段,允许设计者在保证原有类特性的基础上进行扩展,增加新的功能,产生新的类。继承呈现了面向对象程序设计的层次结构

#include <iostream>
#include <string>
using namespace std;
// 鱼品种的基类
class Fish {
protected:
bool isFreshWaterFish;
public:
void swim() {
if (isFreshWaterFish) cout << "在湖泊小河中生存的鱼" << endl;
else cout << " 在海洋中生存的鱼" << endl;
}
};
class Carp : public Fish {
public:
Carp() { isFreshWaterFish = true; }
};
class Tuna : public Fish {
public:
Tuna() { isFreshWaterFish = false; }
};
int main() {
Carp myLunch;
Tuna myDinner;
cout << "我的食物:" << endl;
cout << "午餐:";
myLunch.swim();
cout << "晚餐:";
myDinner.swim();
return 0;
}注意protected这个限定符出现在基类中,它的意思是保护isFreshWaterFish只能在继承层次结构体系中访问和使用,而若没有protected,则能在继承层次结构体系外部访问和使用。
如果基类包含重载的构造函数,需要在实例化时给它提供实参,该如何办呢?创建派生对象时将如何实例化这样的基类?方法是使用初始化列表,并通过派生类的构造函数调用合适的基类构造函数,代码如下所示:
class Base {
public:
Base(int someNumber){
//...
}
};
class Derived : public Base {
public:
Derived()
:Base(5) // 用基类走初始化列表
{ }
};对于我们定义的Fish类,通过给Fish的构造函数提供一个布尔值,来初始化Fish::isFreshWaterFish,强制每个派生类指出自己的品种,代码如下:
class Fish {
protected:
bool isFreshWaterFish;
public:
Fish(bool isFreshWaterFish)
: isFreshWaterFish(isFreshWaterFish)
{ }
void swim() {
if (isFreshWaterFish) cout << "在湖泊小河中生存的鱼" << endl;
else cout << " 在海洋中生存的鱼" << endl;
}
};
class Carp : public Fish {
public:
Carp()
:Fish(true)
{ }
};
class Tuna : public Fish {
public:
Tuna()
:Fish(false)
{ }
};
int main() {
Carp myLunch;
Tuna myDinner;
cout << "我的食物:" << endl;
cout << "午餐:";
myLunch.swim();
cout << "晚餐:";
myDinner.swim();
return 0;
}Fish有一个构造函数,接受一个参数用于初始化Fish::isFreshWaterFish。因此,要创建Fish对象,必须提供一个用于初始化该保护成员的参数,这样Fish避免了保护成员包含随机值的情况,派生类Carp和Tuna被迫定义一个构造函数,使用合适的参数来实例化基类Fish
派生类用完全相同的函数签名重写基类的函数,相当于覆盖了基类的这个函数,
如下展示:
// 在派生类中覆盖基类的方法
class Base {
public:
void func() {
// ...
}
};
class Derived : public Base {
public:
void func() {
// ...
}
};如果使用Derived类的实例化对象调用Func,调用不是Base类中的Func
我们换一组新的继承层次关系演示:
class Livestock {
protected:
bool isWoolProducer;
public:
Livestock(bool isWoolProducer)
: isWoolProducer(isWoolProducer)
{
}
void introduce() {
if (isWoolProducer) cout << "以羊毛为主要产出的畜牧动物" << endl;
else cout << "以奶/肉为主要产出的畜牧动物" << endl;
}
};
class Cow : public Livestock {
public:
Cow()
: Livestock(false)
{ }
void introduce() {
cout << "体重基数大,肉质紧实" << endl;
}
};
class Sheep : public Livestock {
public:
Sheep()
: Livestock(true)
{ }
void introduce() {
cout << "体重基数小,肉质软嫩" << endl;
}
};
int main() {
Cow myLunch;
Sheep myDinner;
cout << "我的食物:" << endl;
cout << "午餐:";
myLunch.introduce();
cout << "晚餐:";
myDinner.introduce();
return 0;
}Sheep::introduce覆盖了Livestock::introduce,想要调用Livestock::introduce,只能在main()中使用域作用做限定符显式调用
// 在派生类中调用基类的方法 ::显式调用
class Livestock {
protected:
bool isWoolProducer;
public:
Livestock(bool isWoolProducer)
: isWoolProducer(isWoolProducer)
{
}
void introduce() {
if (isWoolProducer) cout << "以羊毛为主要产出的畜牧动物" << endl;
else cout << " 以奶/肉为主要产出的畜牧动物" << endl;
}
};
class Cow : public Livestock {
public:
Cow()
: Livestock(false)
{ }
void introduce() {
cout << " 体重基数大,肉质紧实" << endl;
}
};
class Sheep : public Livestock {
public:
Sheep()
: Livestock(true)
{ }
void introduce() {
cout << "体重基数小,肉质软嫩" << endl;
}
};
int main() {
Cow myLunch;
Sheep myDinner;
cout << "我的食物:" << endl;
cout << "午餐:";
myLunch.Livestock::introduce();
cout << "晚餐:";
myDinner.Livestock::introduce();
return 0;
}覆盖的一种极端情况是隐藏
派生类里出现了同名成员(变量或函数),会把基类同名成员全部挡住——不论参数是否相同。这是名字查找规则导致的,属于编译期静态绑定问题 注意:隐藏只要是同名成员即是隐藏,这里函数隐藏和函数重载一定要区分开
// 隐藏:派生类中出现和基类同名的成员 隐藏基类成员
class Livestock {
protected:
bool isWoolProducer;
public:
Livestock(bool isWoolProducer)
: isWoolProducer(isWoolProducer)
{
}
void introduce(bool isWoolProducer) {
if (isWoolProducer) cout << "以羊毛为主要产出的畜牧动物" << endl;
else cout << " 以奶/肉为主要产出的畜牧动物" << endl;
}
void introduece() { cout << "......" << endl; }
};
class Cow : public Livestock {
public:
Cow()
: Livestock(false)
{ }
void introduce() {
cout << " 体重基数大,肉质紧实" << endl;
}
};
int main() {
Cow myLunch;
cout << "我的食物:" << endl;
cout << "午餐:";
myLunch.introduce();
// error C2660: “Cow::introduce”: 函数不接受 1 个参数
// myLunch.introduce(false);
return 0;
}注意看:这个版本Livestock实现了两个introduce的重载,Cow也实现了introduce,将基类的两个重载都隐藏了,如果取消注释36行,会发生编译报错。
那么,如何调用这两个重载呢?
main()中使用域作用限定符myLunch.Livestock::introduce();
Cow类中,将Livestock的introduceclass Cow : public Livestock {
public:
using Livestock::introduce;
Cow()
: Livestock(false)
{ }
void introduce() {
cout << " 体重基数大,肉质紧实" << endl;
}
};Cow是从Livestock派生而来的,创建Cow对象时,先调用Cow的构造函数还是Livestock的构造函数?实例化对象时,成员属性(Livestock::isWoolProducer)是调用构造函数之前实例化还是之后实例化?
基类对象在派生对象之前被实例化
operator=必须要调用基类的operator=完成基类的复制// 派生类成员函数行为
// 打印名字,观察顺序
struct Tracker {
string _name;
Tracker(string n)
:_name(std::move(n)) {
cout << " 构造 " << _name << endl;
}
~Tracker() { cout << " 析构 " << _name << endl; }
};
class Livestock {
protected:
bool _isWoolProducer;
int _age;
Tracker _baseInfo;
Livestock(bool w,int age)
:_isWoolProducer(w)
,_age(age)
,_baseInfo("Livestock.baseInfo")
{
cout << "Livestock构造" << endl;
}
Livestock(const Livestock& other)
:_isWoolProducer(other._isWoolProducer)
,_age(other._age)
,_baseInfo(other._baseInfo)
{
cout << "Livestock拷贝构造" << endl;
}
~Livestock() { cout << "Livestock析构" << endl; }
Livestock& operator=(const Livestock& copy) {
if (this != ©) {
_isWoolProducer = copy._isWoolProducer;
_age = copy._age;
_baseInfo = copy._baseInfo;
cout << "Livestock赋值重载" << endl;
}
return* this;
}
void introduce(bool wool) {
if (wool) cout << "已羊毛为主要产出的畜牧动物" << endl;
else cout << "以奶/肉为主要产出的畜牧动物" << endl;
}
void introduce() { cout << "......" << endl; }
};
class Cow : public Livestock {
private:
int _weight;
Tracker _cowInfo;
const int _ID;
const int& _ageAlias;
static int weightInitial(int baseAge) { return 400 + baseAge * 10; } // 瞎编的
public:
Cow()
:Livestock(false, 3) // 构造基类子对象 永远在最先
, _ID(1234) // cosnt成员必须在初始化列表赋值
, _cowInfo("Cow._cowInfo") // 根据声明顺序走初始化列表
, _ageAlias(_age) // 用基类的_age 基类已经构造好 安全
, _weight(weightInitial(_age))
{
cout << "Cow 构造" << endl;
cout << "-->weight=" << _weight
<< ",age=" << _age
<< ",ID=" << _ID << endl;
}
Cow(const Cow& other)
:Livestock(other) // 先拷贝构造基类子对象(按基类列表顺序)
, _weight(other._weight)
, _cowInfo(other._cowInfo)
, _ID(other._ID)
, _ageAlias(_age) // 绑定自己本体的 _age 而不是 other._ageAlias
{
cout << "Cow拷贝构造" << endl;
}
Cow& operator=(const Cow& copy) {
if (this != ©) {
Livestock::operator=(copy); // 先赋基类子对象
_weight = copy._weight;
_cowInfo = copy._cowInfo;
// _ID 是 const:不可赋值(保持原值)
// _ageAlias 是引用:不可重新绑定(仍引用“本对象的 _age”)
cout << "Cow赋值重载" << endl;
}
return *this;
}
~Cow() { cout << "Cow析构" << endl; }
using Livestock::introduce;
void introduce() { cout << "体重基数大,肉质紧实" << endl; }
};
int main() {
cout << "=== 构造 ===" << endl;
Cow myLunch;
cout << "午餐:" << endl;
myLunch.introduce(); // 调用Cow无参版本
cout << "午餐(基类重载):" << endl;
myLunch.introduce(false); // 有using 可以访问到基类
cout << "=== 拷贝构造 ===" << endl;
Cow copyLunch = myLunch; // 调用 Cow(const Cow&)
cout << "=== 拷贝赋值 ===" << endl;
Cow assigned; // 先默认构造一个
assigned = myLunch; // 调用 Cow::operator=(const Cow&)
cout << "=== 析构 ===" << endl;
return 0;
}默认构造调用顺序
Livestock(false, 3);其成员按声明顺序初始化:_isWoolProducer → _age → _baseInfo,再执行基类构造函数体。_weight → _cowInfo → _ID → _ageAlias;
其中 _ID 必须在初始化列表给值,_ageAlias 在初始化列表绑定到本对象的 _age。Cow 构造函数体,打印“Cow 构造”。拷贝构造调用顺序
Livestock(const Livestock&) 拷贝构造基类部分。_weight, _cowInfo, _ID, _ageAlias;
_ID 在初始化列表拷贝,_ageAlias 绑定本对象的 _age(而非对方的引用)。Cow 拷贝构造函数体,打印“Cow拷贝构造”。拷贝赋值调用顺序
Livestock::operator=(copy) 给基类部分赋值。_weight, _cowInfo;
_ID(const)不可赋值,_ageAlias(引用)不可改绑。析构调用顺序
Cow 析构函数体并销毁其成员(逆声明顺序)。Livestock 析构函数体并销毁其成员(逆声明顺序)。函数可见性
using Livestock::introduce; 解除名字隐藏,因此既可调用 Cow::introduce(),也可调用基类重载 introduce(bool)。

私有继承不同之处在于,指定派生类的基类时使用关键字private
class Base{
// ...
};
class Derived: private Base{
// ...
};私有继承意味着在派生类的实例中,基类的公有成员和方法都是私有的——不能从外部访问。即是==即便是Base类的公有成员和方法,也只能被Derived类使用,而无法通过Derived实例来使用
class Base{
// ...
};
class Derived: protected Base{
// ...
};保护继承意味着:在 Derived 的实例里,Base 的公有成员和受保护成员在 Derived 中都变成了 protected。
所以:
Derived 实例来访问这些成员(对外不可见)。Derived 自己以及Derived 的子类:可以使用这些从 Base 继承来的成员(在类内/子类内可见)。Derived 隐式当作 Base 用(不能隐式转换成 Base* / Base&)。换句话说——
即便是
Base的公有成员和方法,在“保护继承”下,也只能被Derived以及它的派生类使用,无法通过Derived实例在类外直接使用。
以下是各种继承的关系:
类成员/继承方式 | public继承 | protected继承 | private继承 |
|---|---|---|---|
public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
public成员
public继承:在派生类中仍为public成员(派生类对象可直接访问,派生类的派生类也能按规则继承)。protected继承:在派生类中变为protected成员(派生类对象不可直接访问,但派生类的成员函数和其派生类可访问)。private继承:在派生类中变为private成员(仅派生类自己的成员函数可访问,其派生类无法访问)。protected成员
public继承:在派生类中仍为protected成员(规则同上,保持 “保护” 特性)。protected继承:在派生类中仍为protected成员(继承后权限不变)。private继承:在派生类中变为private成员(权限被 “收紧” 为私有)。private成员
public/protected/private),基类的private成员在派生类中完全不可见(派生类的成员函数和对象都无法直接访问,只能通过基类提供的public/protected成员函数间接访问)。三种继承方式衍生出九种情况,这样的设计有点冗余,在实践中基本都是使用公有继承 4. 基类和派生类对象赋值转换 派生类对象可以赋值给基类的对象、基类的指针、基类的引用,这种行为称为切割,把派生类中基类的那一部分切割后赋值过去。 但是,基类对象不能赋值给派生类对象

// 派生类对象可以赋值给基类的对象、指针、引用
class Person {
protected:
int _age;
string _sex;
string _name;
};
class Student : public Person {
protected:
int _score;
};
int main() {
Student stuObj;
Person pA = stuObj;
Person& pB = stuObj;
Person* pC = &stuObj;
// 基类对象不能赋值给派生类对象 小->大 不行
//stuObj = pA;
return 0;
}多继承是指一个派生类会继承多个基类的属性
就比如说生活中的共享电动车,它可充电、可联网、属于电动车
// 多继承
// 共享电动车: 可充电 可联网 是电动车
class eBike {
public:
void name(){ cout << "是电动车" << endl; }
};
class chargeAble {
public:
void canCharge() { cout << "可充电" << endl; }
};
class webConnectable {
public:
void canConnectWeb() { cout << "可联网" << endl; }
};
class shareEBike : public eBike
, public chargeAble
, public webConnectable{
public:
void bike() { cout << "是共享电动车" << endl; }
};
int main() {
shareEBike bike;
bike.bike();
bike.name();
bike.canConnectWeb();
bike.canCharge();
return 0;
}从C++11起,编译器支持限定符final。被声明为final的类不能用作基类。
例如:
class shareEBike final: public eBike
, public chargeAble
, public webConnectable{
public:
void bike() { cout << "是共享电动车" << endl; }
};动机/场景:多个组件“有共同属性但细节不同”(外卖系统:骑手/客户/商家)。 两种做法: ① 各写一套(重复);② 抽公共到基类,差异在派生类中扩展/覆盖(继承)。
定义:继承让代码复用并形成层次结构,在保留原有类特性的基础上扩展新功能。
语法/示例(Fish):
class Derived : public Base {};protected成员仅类族可见。
class Fish { protected: bool isFreshWaterFish; public: void swim(){...} };
class Carp : public Fish { public: Carp(){ isFreshWaterFish = true; } };
class Tuna : public Fish { public: Tuna(){ isFreshWaterFish = false; } };基类初始化(向基类传参):派生类初始化列表调用合适的基构造,避免未初始化。
class Fish{ protected: bool isFreshWaterFish;
public: Fish(bool b):isFreshWaterFish(b){} };
class Carp: public Fish { public: Carp():Fish(true){} };
class Tuna: public Fish { public: Tuna():Fish(false){} };在派生类中覆盖/调用/隐藏基类方法
覆盖:派生类用相同函数签名重写基类函数。
class Base{ public: void func(){} };
class Derived: public Base{ public: void func(){} }; // 覆盖显式调用基类版本:对象.Base::func()。
名字隐藏:派生类出现同名成员会遮挡基类同名成员(与参数是否相同无关)。
解除隐藏:在派生类中 using Base::introduce;
示例(Livestock/Cow/Sheep):
Sheep::introduce() 覆盖 Livestock::introduce();Livestock::introduce() 或 using 访问被遮挡的重载。operator= 先赋基类,再赋派生成员;const/引用成员不可重新赋值/改绑。using Base::introduce; 可解除隐藏,保留基类重载的可见性。public → 派生 public;语义清晰(is-a)。public/protected 在派生类中都变为 protected(仅类族可用,对外不可见)。public/protected 在派生类中都变为 private(对外完全隐藏,更像实现复用)。private 成员对子类不可见(只能经由基类接口访问)。实务上优先 public 继承,其余两种少用、语义不直观。
派生 → 基类:允许(对象切片 / 指针或引用向上转型)。
基类 → 派生:禁止(不安全)。
Student stu; Person pa = stu; Person& pr = stu; Person* pp = &stu; // 反向不行定义:一个派生类可同时继承多个基类。
优点:可把**互不相关的“能力”*独立为接口,进行*横向组合(比如“可充电”“可联网”)。
风险:若多基类之间存在继承,易引出菱形与二义性
生活化示例:
class eBike{ public: void name(){ cout<<"是电动车\n"; } };
class chargeAble{ public: void canCharge(){ cout<<"可充电\n"; } };
class webConnectable{ public: void canConnectWeb(){ cout<<"可联网\n"; } };
class shareEBike : public eBike, public chargeAble, public webConnectable {
public: void bike(){ cout<<"是共享电动车\n"; }
};→ 接口式多继承
final 禁止被继承(C++11)class X final { ... }; —— X 不可再作基类。class shareEBike final : public eBike, ... { ... };Tuna : public Fish、shareEBike : public eBike)。class EBike { Battery battery_; })。Rechargeable + NetworkConnectable)