
🎬 GitHub:Vect的代码仓库
文章目录
1.继承基础(公有继承基础下实现)
继承定义
在实现一个系统时,我们会经常遇到具有类似属性,但细节或行为存在细微差异的组件 。比如说外卖配送系统:最重要的三个角色:骑手、客户和商家,他们有共同的属性:地址、联系方式等等,也有每个类个性化的属性。
这种情况有两种解决方式:
- 将每个组件声明位一个类,并在每个类中实现所有的属性,这会重复实现相同的属性
- 继承: 从一个包含通用属性并且实现了通用功能的基类派生出类似的类,并在类中覆盖基本功能,来实现让每个类都有独一无二的功能。
这便是面向对象的第二大特性:继承
如图:类之间的继承关系

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

#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的introduce
class Cow : public Livestock {
public:
using Livestock::introduce;
Cow()
: Livestock(false)
{ }
void introduce() {
cout << " 体重基数大,肉质紧实" << endl;
}
};
2. 派生类中默认成员函数的行为
构造和析构
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,再执行基类构造函数体。 - 再构造派生成员(按 Cow 中的声明顺序,与初始化列表书写顺序无关):
_weight → _cowInfo → _ID → _ageAlias;
其中_ID必须在初始化列表给值,_ageAlias在初始化列表绑定到本对象的_age。 - 最后进入
Cow构造函数体,打印“Cow 构造”。
拷贝构造调用顺序
- 先调用
Livestock(const Livestock&)拷贝构造基类部分。 - 再按 Cow 的成员声明顺序拷贝构造成员:
_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)。

3. 私有继承和保护继承
私有继承
私有继承不同之处在于,指定派生类的基类时使用关键字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;
}
5. 多继承
多继承是指一个派生类会继承多个基类的属性
-
好处:能把互不相干的能力(例如“可充电”“可联网”)按横向能力接口拆开,某个具体对象可以一次性“拿来就用”。
-
风险:若多个基类间存在继承关系,可能出现菱形继承带来的二义性
就比如说生活中的共享电动车,它可充电、可联网、属于电动车
// 多继承
// 共享电动车: 可充电 可联网 是电动车
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;
}
6. final禁止基类被继承
从C++11起,编译器支持限定符final。被声明为final的类不能用作基类。
例如:
class shareEBike final: public eBike
, public chargeAble
, public webConnectable{
public:
void bike() { cout << "是共享电动车" << endl; }
};
7. 全文总结
- 继承基础(默认用公有继承)
-
动机/场景:多个组件“有共同属性但细节不同”(外卖系统:骑手/客户/商家)。
两种做法:
① 各写一套(重复);② 抽公共到基类,差异在派生类中扩展/覆盖(继承)。 -
定义:继承让代码复用并形成层次结构,在保留原有类特性的基础上扩展新功能。
-
语法/示例(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 / protected / private
- public 继承(推荐):基类
public→ 派生public;语义清晰(is-a)。 - protected 继承:基类
public/protected在派生类中都变为protected(仅类族可用,对外不可见)。 - private 继承:基类
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, ... { ... };
- is-a 与 has-a
- is-a(是一个):语义上“子类就是基类的一种”。
→ 采用公有继承(如Tuna : public Fish、shareEBike : public eBike)。 - has-a(有一个/由……组成):整体拥有部件。
→ 采用组合/聚合(成员对象),不要用继承(如电动车有电池:class EBike { Battery battery_; })。 - 横向“能力”拼装(既非父子、也非组成):
→ 用接口式多继承(如Rechargeable+NetworkConnectable)
- 概念从属 → public 继承(is-a);
- 组成拥有 → 组合(has-a);
- 能力叠加 → 接口式多继承(无菱形)。
198





