c++复习自存--继承

继承

继承基础

继承与派生
cpp 复制代码
class Base 
{ 
 // ... base class members 
}; 
class Derived: access-specifier Base 
{ 
 // ... derived class members 
}; 

其中 access-specifier 可以是 public(这是最常见的,表示派生类是一个基类)、private 或 protected(表示派生类有一个基类)。

下面的继承层次结构表明,Carp 类是从 Fish 类派生而来的:

cpp 复制代码
class Fish // base class 
{ 
 // ... Fish's members 
}; 
class Carp:public Fish // derived class 
{ 
 // ... Carp's members 
};

阅读介绍继承的文献时,您将遇到"从...继承而来"和"从...派生而来"等术语,它们的含义相同。

同样,基类也被称为超类;从基类派生而来的类称为派生类,也叫子类。

访问限定符protected

与 public 和 private 一样,protected 也是一个访问限定符。将属性声明为 protected 的时,相当于允许派生类和友元类访问它,但禁止在继承层次结构外部(包括 main( ))访问它。

一种更好的 Fish 类设计,它使用关键字 protected 将成员属性只暴露给派生类:

cpp 复制代码
 0: #include <iostream> 
 1: using namespace std; 
 2: 
 3: class Fish 
 4: { 
 5: protected: 
 6: bool isFreshWaterFish; // accessible only to derived classes 
 7: 
 8: public: 
 9: void Swim() 
10: { 
11: if (isFreshWaterFish) 
12: cout << "Swims in lake" << endl; 
13: else 
14: cout << "Swims in sea" << endl; 
15: } 
16: }; 
17: 
18: class Tuna: public Fish 
19: { 
20: public: 
21: Tuna() 
22: { 
23: isFreshWaterFish = false; // set protected member in base 
24: } 
25: }; 
26: 
27: class Carp: public Fish 
28: { 
29: public: 
30: Carp() 
31: { 
32: isFreshWaterFish = false; 
33: } 
34: }; 
35: 
36: int main() 
37: { 
38: Carp myLunch; 
39: Tuna myDinner; 
40: 
41: cout << "About my food" << endl; 
42: 
43: cout << "Lunch: "; 
44: myLunch.Swim(); 
45: 
46: cout << "Dinner: "; 
47: myDinner.Swim(); 
48: 
49: // uncomment line below to see that protected members 
50: // are not accessible from outside the class hierarchy 
51: // myLunch.isFreshWaterFish = false; 
52: 
53: return 0; 
54: }
基类初始化,向基类传递参数

包含初始化列表的派生类构造函数:

cpp 复制代码
 0: #include <iostream> 
 1: using namespace std; 
 2: 
 3: class Fish 
 4: { 
 5: protected: 
 6: bool isFreshWaterFish; // accessible only to derived classes 
 7: 
 8: public: 
 9: // Fish constructor 
10: Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){} 
11: 
12: void Swim() 
13: { 
14: if (isFreshWaterFish) 
15: cout << "Swims in lake" << endl; 
16: else 
17: cout << "Swims in sea" << endl; 
18: } 
19: }; 
20: 
21: class Tuna: public Fish 
22: { 
23: public: 
24: Tuna(): Fish(false) {} // constructor initializes base 
25: }; 
26: 
27: class Carp: public Fish 
28: { 
29: public: 
30: Carp(): Fish(true) {} 
31: }; 
32: 
33: int main() 
34: { 
35: Carp myLunch; 
36: Tuna myDinner; 
37: 
38: cout << "About my food" << endl; 
39: 
40: cout << "Lunch: "; 
41: myLunch.Swim(); 
42: 
43: cout << "Dinner: "; 
44: myDinner.Swim(); 
45: 
46: return 0; 
47: }

为了最大限度地提高安全性,对于派生类不需要访问的基类属性,别忘了将其声明为私有的。

在派生类中覆盖基类的方法

如果派生类实现了从基类继承的函数,且返回值和特征标相同,就相当于覆盖了基类的这个方法,如下面的代码所示:

cpp 复制代码
class Base 
{ 
public: 
 void DoSomething() 
 { 
 // implementation code... Does something 
 } 
}; 
class Derived:public Base 
{ 
public: 
 void DoSomething() 
 { 
 // implementation code... Does something else 
 } 
};

因此,如果使用 Derived 类的实例调用方法 DoSomething( ),调用的将不是 Base 类中的这个方法。

要调用 Base::DoSomething( ),只能在 main( )中使用作用域解析运算符显式地调用它。

调用基类中被覆盖的方法

派生类 Tuna 通过实现 Swim( )覆盖了 Fish 类的 Swim( )函数,其结果如下:

cpp 复制代码
Tuna myDinner; 
myDinner.Swim(); // will invoke Tuna::Swim() 

如果要在 main( )中调用 Fish::Swim( ),需要使用作用域解析运算符(::),如下所示:

cpp 复制代码
myDinner.Fish::Swim(); // invokes Fish::Swim() using instance of Tuna
在派生类中调用基类的方法

通常,Fish::Swim( )包含适用于所有鱼类(包括金枪鱼和鲤鱼)的通用实现。如果要在 Tuna::Swim( )和 Carp::Swim( )的实现中重用 Fish::Swim( )的通用实现,可使用作用域解析运算符(::),如下面的代码所示:

cpp 复制代码
class Carp: public Fish 
{
public: 
 Carp(): Fish(true) {} 
 void Swim() 
 { 
 cout << "Carp swims real slow" << endl; 
 Fish::Swim(); // invoke base class function using operator:: 
 } 
};
在派生类中隐藏基类的方法

覆盖的一种极端情形是,Tuna::Swim( )可能隐藏 Fish::Swim( )的所有重载版本,使得调用这些重载版本会导致编译错误(因此称为被隐藏),

Tuna::Swim( )隐藏了重载方法 Fish::Swim(bool) :

cpp 复制代码
 0: #include <iostream> 
 1: using namespace std; 
 2: 
 3: class Fish 
 4: { 
 5: public: 
 6: void Swim() 
 7: { 
 8: cout << "Fish swims... !" << endl; 
 9: } 
10: 
11: void Swim(bool isFreshWaterFish) // overloaded version 
12: { 
13: if (isFreshWaterFish) 
14: cout << "Swims in lake" << endl; 
15: else 
16: cout << "Swims in sea" << endl; 
17: } 
18: }; 
19: 
20: class Tuna: public Fish 
21: { 
22: public: 
23: void Swim() 
24: { 
25: cout << "Tuna swims real fast" << endl; 
26: } 
27: }; 
28: 
29: int main() 
30: { 
31: Tuna myDinner; 
32: 
33: cout << "About my food" << endl; 
34: 
35: // myDinner.Swim(false);//failure: Tuna::Swim() hides Fish::Swim(bool) 
36: myDinner.Swim(); 
37: 
38: return 0; 
39: }

要通过 Tuna 实例调用 Fish::Swim(bool),可采用如下解决方案。

  • 解决方案 1:在 main( )中使用作用域解析运算符(::):
cpp 复制代码
myDinner.Fish::Swim(); 
  • 解决方案 2:在 Tuna 类中,使用关键字 using 解除对 Fish::Swim( )的隐藏:
cpp 复制代码
class Tuna: public Fish 
{ 
public: 
 using Fish::Swim; // unhide all Swim() methods in class Fish 
 void Swim() 
 { 
 cout << "Tuna swims real fast" << endl; 
 } 
}; 
  • 解决方案 3:在 Tuna 类中,覆盖 Fish::Swim( )的所有重载版本(如果需要,可通过 Tuna::Fish(...) 调用方法 Fish::Swim( )):
cpp 复制代码
class Tuna: public Fish 
{ 
public: 
 void Swim(bool isFreshWaterFish) 
 { 
 Fish::Swim(isFreshWaterFish); 
 } 
 void Swim() 
 { 
 cout << "Tuna swims real fast" << endl; 
 } 
};
C++ 继承中构造、析构完整执行顺序
一、单一继承(父类 → 子类)
1. 构造函数执行顺序(先父后子)
  1. 基类(父类)构造函数 先执行
  2. 子类自身成员变量按声明顺序构造
  3. 派生类(子类)构造函数体 执行

原理:子类依赖父类的成员,必须先把父类完整构造好,再构造子类。

cpp 复制代码
#include <iostream>
using namespace std;
class Base {
public:
    Base() { cout << "基类构造\n"; }
};
class Son : public Base {
public:
    Son() { cout << "子类构造\n"; }
};
int main() {
    Son s;
    return 0;
}

输出:

复制代码
基类构造
子类构造
2. 析构函数执行顺序(先子后父,和构造完全相反)
  1. 派生类(子类)析构函数体 先执行
  2. 子类成员变量逆序销毁
  3. 基类(父类)析构函数 最后执行
cpp 复制代码
// 接上例,增加析构
class Base {
public:
    Base() { cout << "基类构造\n"; }
    ~Base() { cout << "基类析构\n"; }
};
class Son : public Base {
public:
    Son() { cout << "子类构造\n"; }
    ~Son() { cout << "子类析构\n"; }
};
int main() {
    Son s;
    return 0;
}

输出:

复制代码
基类构造
子类构造
子类析构
基类析构

二、包含成员对象(类中有其他类成员)

规则:先构造成员,再执行自身构造函数体;析构相反

执行流程:

  1. 父类构造(如有继承)
  2. 类内所有成员变量,按声明从上到下依次构造(和初始化列表顺序无关)
  3. 当前类构造函数体
    析构完全逆序:
  4. 当前类析构函数体
  5. 成员变量按声明倒序销毁
  6. 父类析构(如有继承)

示例:

cpp 复制代码
struct A { A(){cout<<"A构造";} ~A(){cout<<"A析构";} };
struct B { B(){cout<<"B构造";} ~B(){cout<<"B析构";} };
class Test {
    A a; // 先声明
    B b; // 后声明
public:
    Test() { cout << "Test构造"; }
    ~Test() { cout << "Test析构"; }
};

构造顺序:A → B → Test

析构顺序:Test → B → A


三、多重继承(多个父类)
构造顺序:按继承声明顺序,不是初始化列表顺序
cpp 复制代码
class A{};
class B{};
// 继承顺序:先A,后B
class C : public A, public B {
public:
    // 哪怕列表先写B(),构造依然先A后B
    C():B(),A(){}
};

构造:A → B → C

析构:C → B → A


四、虚继承(菱形继承)

公共虚基类最先构造 ,只构造一次;其余普通父类按继承顺序。

析构时虚基类最后销毁。


五、多态重点考点:虚析构函数

如果用父类指针指向子类对象 ,父类析构不加virtual只会调用父类析构,子类析构不执行,内存泄漏!

cpp 复制代码
class Base {
public:
    virtual ~Base(){} // 必须virtual
};
class Son : public Base {};

int main() {
    Base* p = new Son();
    delete p; // 先Son析构,再Base析构
    return 0;
}

私有继承与保护继承

注:派生类不能继承父类的private,不论是什么继承方式。

前面介绍的都是公有继承,私有继承的不同之处在于,指定派生类的基类时使用关键字 private:

cpp 复制代码
class Base 
{ 
 // ... base class members and methods 
}; 
class Derived: private Base // private inheritance 
{ 
 // ... derived class members and methods 
}; 

私有继承意味着在派生类的实例中,基类的所有公有成员和方法都是私有的------不能从外部访问。

换句话说,即便是 Base 类的公有成员和方法,也只能被 Derived 类使用,而无法通过 Derived 实例来使用它们。

一、基础规则(所有继承通用)

任何派生类,永远无法直接访问基类 private 私有成员

仅能直接访问基类 publicprotected 成员。

基类private成员会存在于子类内存,只能通过基类提供的接口间接操作。

二、三种继承方式权限转换(父类Motor → 子类Car)
继承方式 Motor::public 在Car内 Motor::protected 在Car内 Motor::private
public 公有继承 public protected 不可访问
protected 保护继承 protected protected 不可访问
private 私有继承 private private 不可访问
记忆
  • public继承:protected/public权限原样保留,不收紧
  • protected继承:protected/public 进子类统一变成protected
  • private继承:protected/public降级为子类私有,阻断下层派生类访问
三、多层继承访问规则(Motor → Car → RaceCar)
场景1:Car : private Motor(题目示例)
  1. Motor的public/protected → 全部变为Car的私有成员
  2. 无论RaceCar以何种方式继承Car,都不能访问Car的私有成员
  3. 最终结论:RaceCar 无法访问 Motor 的任何 public/protected
  4. 核心原理:继承链中最严格的private继承会锁住上层所有成员,下层派生类无法穿透
场景2:Car : public Motor
  1. Motor的public保留为Car的public,Motor的protected保留为Car的protected
  2. RaceCar继承Car后,仍可正常访问Motor的public/protected
  3. 整条链路无权限收紧,访问权限可向下传递

只有基类自己的成员函数,能直接操作自身 private;

子类想操作基类 private,只能调用基类提供的public/protected成员函数中转;

三种继承的区别,只影响基类 public/protected 在子类中的访问等级,完全不改变基类 private 的访问限制。

切除问题

如果程序员像下面这样做,结果将如何呢?

cpp 复制代码
Derived objDerived; 
Base objectBase = objDerived; 

如果程序员像下面这样做,结果又将如何呢?

cpp 复制代码
void UseBase(Base input); 
... 
Derived objDerived; 
UseBase(objDerived); // copy of objDerived will be sliced and sent 

它们都将 Derived 对象复制给 Base 对象,一个是通过显式地复制,另一个是通过传递参数。在这

些情形下,编译器将只复制 objDerived 的 Base 部分,即不是整个对象。换句话说,Derived 的数据成员包含的信息将丢失。这种无意间裁减数据,导致 Derived 变成 Base 的行为称为切除(slicing)。

要避免切除问题,不要按值传递参数,而应以指向基类的指针或 const 引用的方式传递。

多继承

使用多继承模拟具备哺乳动物、鸟类和爬行动物特征的鸭嘴兽:

cpp 复制代码
 0: #include <iostream> 
 1: using namespace std; 
 2: 
 3: class Mammal 
 4: { 
 5: public: 
 6: void FeedBabyMilk() 
 7: { 
 8: cout << "Mammal: Baby says glug!" << endl; 
 9: } 
10: }; 
11: 
12: class Reptile 
13: { 
14: public: 
15: void SpitVenom() 
16: { 
17: cout << "Reptile: Shoo enemy! Spits venom!" << endl; 
18: } 
19: }; 
20: 
21: class Bird 
22: { 
23: public: 
24: void LayEggs() 
25: { 
26: cout << "Bird: Laid my eggs, am lighter now!" << endl; 
27: } 
28: }; 
29: 
30: class Platypus: public Mammal, public Bird, public Reptile 
31: { 
32: public: 
33: void Swim() 
34: { 
35: cout << "Platypus: Voila, I can swim!" << endl; 
36: } 
37: }; 
38: 
39: int main() 
40: { 
41: Platypus realFreak; 
42: realFreak.LayEggs(); 
43: realFreak.FeedBabyMilk(); 
44: realFreak.SpitVenom(); 
45: realFreak.Swim(); 
46: 
47: return 0; 
48: }

使用 final 禁止继承

从 C++11 起,编译器支持限定符 final。被声明为 final 的类不能用作基类。如下:Platypus 类表示一种进化得很好的物种,因此您可能想将其声明为 final 的,从而禁止继承它。可像下面这样做:

cpp 复制代码
class Platypus final: public Mammal, public Bird, public Reptile 
{ 
public: 
 void Swim() 
 { 
 cout << "Platypus: Voila, I can swim!" << endl; 
 } 
}; 

除用于类外,还可将 final 用于成员函数来控制多态行为。

相关推荐
叩码以求索2 小时前
经典算法实例分析:写字符串需要的行数
开发语言·javascript·ecmascript
我是唐青枫2 小时前
Kotlin 运算符重载详解:为什么 a += b 有时改对象,有时换对象?
开发语言·kotlin
2zcode2 小时前
基于MATLAB图像处理的饮料瓶识别与价格显示系统设计与实现
开发语言·图像处理·matlab
王老师青少年编程3 小时前
2026年6月GESP真题及题解(C++二级):完全平方数计数
c++·题解·真题·gesp·二级·2026年6月·完全平方数计数
zwenqiyu3 小时前
非线性字符串数据结构串讲
数据结构·c++·学习·算法
骑士雄师3 小时前
大模型:临时会话
开发语言·python
努力中的编程者4 小时前
STL-vector的模拟实现
开发语言·c++·算法·stl·vector
薛定猫AI4 小时前
【深度解析】GLM-5.2 与 Z-Code:AI 编程智能体的原理拆解与 Python 调用实战
开发语言·人工智能·python
没文化的阿浩4 小时前
【C++】详解继承机制
开发语言·c++