C++三大特性之“继承”

封装、继承、多态作为C++三大特性,将数据和操作方法都集合在类中体现了类的封装,而如何体现继承特性,就由下文揭晓

一、基本语法:

继承的基本语法如下:

复制代码
class 派生类名 : 访问修饰符 基类名
{
    // 派生类成员
};

继承方式有三种:public、protected 和 private,它们决定了基类成员在派生类中的访问权限:

|------------|---------------|----------------|--------------|
| 继承方式\基类成员 | 父类的public成员 | 父类的protected成员 | 父类的private成员 |
| public | 在子类是public | 在子类是protected | 在子类是private |
| proteed | 在子类是protected | 在子类是protected | 在子类是private |
| private | 在子类是private | 在子类是private | 在子类是private |

本文拿自定义的people类和student类来讲述:

复制代码
class People
{
public:
	People(const string& name = "") :_name(name)
	{
		cout << "People构造" << endl;
	}
	People(const People& rhs)
	{
		cout << "People的拷贝" << endl;
	}
	People& operator=(const People& rhs)
	{
		cout << "People的赋值" << endl;
		return *this;
	}
	~People()
	{
		cout << "People析构" << endl;
	}
	void Pfunc()
	{
		cout << "People func" << endl;
	}
private:
	string _name;
};
class Student :public People
{
public:
	Student(const string& name = "", int id = 0) : _id(id)
	{
		cout << "Student构造" << endl;
	}
	Student(const Student& rhs) :People(rhs)
	{
		cout << "Student的拷贝" << endl;
	}
	Student& operator=(const Student& rhs)
	{
		People::operator=(rhs);
		cout << "Student的赋值" << endl;
		return *this;
	}
	~Student()
	{
		cout << "Student析构" << endl;
	}
	void Sfunc()
	{
		cout << "People func" << endl;
	}
	int _id;
};

Student类public继承了People类,所以stu1对象中成员变量不仅有student类自带的_id,还有people类的_name

二、基类和派生类对象赋值转换

  • 基类指针和引用可以指向派生类,可以调用基类的函数。
  • 派生类的指针和引用也可以指向基类,但是要先强制转化一下,还得小心不要访问未知地址

派生类的指针和引用也可以指向基类,一定要注意不要越界访问。也尽量少用派生类指针访问基类对象

三、重定义

子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。

复制代码
class A {
public:
    int _n;
    void func() {
        cout << "A func n: " << _n << endl;
    }
};

class B : public A {
public:
    int _n;  // 隐藏了A::_n
    void func() {  // 隐藏了A::func()
        cout << "B func n: " << _n << endl;
    }
};

int main() {
    A a1;
    a1._n = 5;
    a1.func();  // 输出: A func n: 5
    
    B b1;
    b1._n = 10;      // 访问B::_n
    b1.A::_n = 15;   // 显示访问A::_n
    b1.func();       // 输出: B func n: 10
    b1.A::func();    // 输出: A func n: 15
    
    return 0;
}

由结果可知,对于重名成员,默认的访问是子类同名成员将父类成员隐藏了,除非像代码中显示使用才能访问到父类的成员变量和函数,这样的问题导致了数据的二义性,容易让程序员混淆,所以我们尽量不要使用同名的成员变量,而同名函数可以通过虚函数来很好解决这个问题(下篇文章将讲解多态,虚函数)

注意在实际中在继承体系里面最好不要定义同名的成员。

四、派生类的默认成员函数

构造:初始化要先调用基类的构造初始化,再初始化派生类成员。**子类的有参构造函数必须首先构造父类,**如果父类有默认构造函数,就可以不必自己显示调用了,会自动调用的

拷贝赋值:如果是自己显示调用的话,一定必须要先调用基类的拷贝或者赋值函数。如果不自己手动实现,编译器会自动生成默认的拷贝构造或赋值函数,同样也是先调用基类的拷贝或者赋值函数

析构:先调用派生类的析构再调用基类的析构,无论是手动的还是编译器默认生成都是如此

五、菱形继承问题

问题描述

当我们创建如下继承结构时,会出现菱形继承问题:

复制代码
#include <iostream>
#include <string>
using namespace std;

class People
{
public:
    People(const string& name = "") :_name(name)
    {
        cout << "People构造: " << _name << endl;
    }
    string _name;
};

class Student : public People  // 普通继承
{
public:
    Student(const string& name = "") : People(name)
    {
        cout << "Student构造" << endl;
    }
    int _id;
};

class Teacher : public People  // 普通继承
{
public:
    Teacher(const string& name = "") : People(name)
    {
        cout << "Teacher构造" << endl;
    }
    int _tid;
};

class TeachingAssistant : public Teacher, public Student
{
public:
    TeachingAssistant(const string& tname = "", const string& sname = "") 
        : Teacher(tname), Student(sname)
    {
        cout << "TeachingAssistant构造" << endl;
    }
};

问题分析

这种继承结构会导致:

  1. 数据冗余TeachingAssistant 对象中包含两份 People 的成员

  2. 二义性 :无法直接访问 _name,需要指定访问路径

    int main()
    {
    TeachingAssistant ta("Dr. Smith", "Tom");

    复制代码
     // 错误:对成员 '_name' 的请求不明确
     // cout << ta._name << endl;
     
     // 必须指定访问路径
     cout << "Teacher name: " << ta.Teacher::_name << endl;
     cout << "Student name: " << ta.Student::_name << endl;
     
     return 0;

    }

解决方案:虚拟继承

使用虚拟继承

复制代码
class Student : virtual public People  // 虚拟继承
{
public:
    Student(const string& name = "") : People(name)
    {
        cout << "Student构造" << endl;
    }
    int _id;
};

class Teacher : virtual public People  // 虚拟继承
{
public:
    Teacher(const string& name = "") : People(name)
    {
        cout << "Teacher构造" << endl;
    }
    int _tid;
};

class TeachingAssistant : public Teacher, public Student
{
public:
    TeachingAssistant(const string& name = "") 
        : People(name), Teacher(name), Student(name)
    {
        cout << "TeachingAssistant构造" << endl;
    }
    
    void printName()
    {
        cout << "Name: " << _name << endl;  // 现在可以直接访问,没有二义性
    }
};

虚拟继承的优势

  1. 消除数据冗余People 子对象在 TeachingAssistant 中只有一份

  2. 消除二义性 :可以直接访问 _name,无需指定路径

    int main()
    {
    TeachingAssistant ta("Dr. Tom Smith");
    ta.printName(); // 输出: Name: Dr. Tom Smith

    复制代码
     // 也可以直接访问
     cout << "Direct access: " << ta._name << endl;
     
     return 0;

    }

底层实现原理

虚基表指针机制

虚拟继承通过以下机制实现:

  1. 虚基表指针:每个虚拟继承的类都有一个指向虚基表的指针
  2. 虚基表:存储了到虚基类子对象的偏移量
  3. 共享访问:通过偏移量找到共享的虚基类子对象

在student类中和teacher类中加入指针,指向虚基表,而虚基表的第二行开始就是想访问People类中成员的偏移量(这里的0x00000010=16字节),从0x001CFEA4到0x001CFEB的差值正好相等。

六、继承和组合:

继承:

优势:

  • 可以复用基类代码,减少代码量,并在此基础上易增加新功能
  • 支持多态,针对不同的对象调用不同的虚函数

劣势:

  • 在一定程度上破坏了类的封装性
  • 强耦合,子类依靠基类,改变一个基类可能会影响多个子类
  • 有多继承、菱形虚拟继承这些复杂的关系

组合:

优势:

  • 不破坏类的封装性,灵活性高
  • 低耦合,更易于维护

劣势:

  • 代码较多、需要提供更多接口供外部使用

优先使用组合,只在真正需要多态时才使用继承。对于接口使用继承,对于实现使用组合。

七、总结

继承是 C++ 中强大的代码复用机制,但也带来了复杂性。合理使用继承需要注意:

  1. 谨慎选择继承方式,大多都是public继承
  2. 避免同名成员引发的隐藏问题
  3. 正确处理派生类的构造、析构和赋值
  4. 使用虚拟继承解决菱形继承问题
  5. 优先考虑组合而非继承

通过合理使用继承,可以构建出清晰、可维护的类层次结构,实现代码的高效复用。

下图是我做的思维导图,供参考复习

结语:

以上就是我分享的C++继承的全部内容了,希望对大家有些帮助,也希望与一样喜欢编程的朋友们共进步

谢谢观看

如果觉得还阔以的话,三连一下,以后会持续更新的,我会加油的

祝大家早安午安晚安

相关推荐
毕设源码-李学长2 小时前
计算机毕业设计java高校多媒体教室管理系统高校多媒体教室综合管理系统高校智能多媒体教室管理平台
java·开发语言·课程设计
星眸2292 小时前
C++/QT 1
c++
先知后行。2 小时前
线程的创建.销毁
开发语言·c++·算法
DdduZe2 小时前
9.11作业
c++·qt
鱼嘻2 小时前
西嘎嘎学习 - C++ 继承 - Day 10
开发语言·c++·学习·算法
孤廖2 小时前
从 “模板” 到 “场景”,用 C++ 磨透拓扑排序的实战逻辑
开发语言·c++·程序人生·算法·贪心算法·动态规划·学习方法
我有火的意志2 小时前
Liunx执行source /etc/profile 报错, -bash: HISTTIMEFORMAT: readonly variable
开发语言·bash·histtimeformat·readonly
老歌老听老掉牙2 小时前
OpenCascade几何建模:平面创建与法向拉伸的工程实现
c++·平面·opencascade
-凌凌漆-3 小时前
【Qt】【C++】虚析构函数及 virtual ~Base() = default
java·c++·qt