【C++】从0到1讲继承|复杂的菱形继承

这篇具有很好参考价值的文章主要介绍了【C++】从0到1讲继承|复杂的菱形继承。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

 

【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

 

个人主页:🍝在肯德基吃麻辣烫

我的gitee:gitee仓库
分享一句喜欢的话:热烈的火焰,冰封在最沉默的火山深处。


前言

本文主要讲述的是继承的概念,以及基类和派生类和衍生出的各种东西,还有多继承,菱形继承等,从0到1讲解继承。


一、什么是继承?

与日常生活中的人的继承相关,你可以继承你父亲的财富,继承你父亲的房产等等。

二、继承的语法表示

1.基类和派生类

【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

基类也叫父类,派生类也叫子类,子类通过继承方式,继承父类。

2.继承的方式

继承方式有三种:public,protected,private。

不同的继承对应着不同的访问方式的变化,变化如下:

【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

 其中,我们最经常使用的是公有继承。

有需要注意的点:
1.基类的private一旦被继承,就不可见。这里的不可见是在派生类中无法被访问,而不是没有继承。

2.只推荐使用公有继承,其他的继承方式不推荐使用。

3.class默认的继承方式是私有继承,struct默认的继承方式是公有继承,但是推荐显式写出继承方式。

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

  • 1.子类对象可以直接赋值给基类对象/基类的指针/基类的引用但是基类对象不能赋值给子类。因为编译器认为基类的对象类型不完全包含子类。在这里也叫做切片。【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言
  • 2.基类对象本身不能赋值给子类对象。
  • 3.基类的指针/引用可以赋值通过强制类型转换赋值给子类的指针/引用,但必须是基类的指针指向子类才安全。(了解即可)

 四、继承中的新概念——隐藏(重定义)

  • 1.在子类继承父类中,子类的作用域和父类的作用域是独立的。
  • 2.如果子类和父类有同名成员变量,子类成员会将父类的同名成员变量隐藏起来,可以理解成父类的成员变量被揣进裤兜里了。
  • // Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
    class Person
    {
    protected :
        string _name = "小李子"; // 姓名
        int _num = 111;  // 身份证号
    };
    
    class Student : public Person
    {
    public:
        void Print()
        {
            cout<<" 姓名:"<<_name<< endl;
            cout<<" 身份证号:"<<Person::_num<< endl;
            cout<<" 学号:"<<_num<<endl;
        }
    
    protected:
        int _num = 999; // 学号
    };
    
    void Test()
    {
        Student s1;
        s1.Print();
    };

    上面代码的情况就符合隐藏,虽然代码能跑,不过不容易进行区分。

  • 3.如果子类和父类有同名的成员函数,同样也会隐藏起来,这个也叫做重定义。
  • // B中的fun和A中的fun不是构成重载,因为不是在同一作用域
    // B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
    
    class A
    {
    public:
    
        void fun()
        {
            cout << "func()" << endl;
        }
    
    };
    
    class B : public A
    {
    public:
    
        void fun(int i)
        {
            A::fun();
            cout << "func(int i)->" <<i<<endl;
        }
    
    };
    
    void Test()
    {
        B b;
        b.fun(10);
    };

    对于类成员函数来说,只要同名就构成隐藏。

  • 4.实际中最好不要定义重名成员。

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

  • 1.构造函数

在子类的构造函数中会调用父类的构造函数。所以如果父类中没有默认构造函数,则在子类的构造函数的初始化列表中必须显式地调用父类的构造函数来完成父类那部分成员的初始化。

  • 并且在子类的构造函数的初始化列表中,会按照声明出现的顺序依次初始化,所以应该先调用父类的构造函数,再初始化子类的成员。
  • 总结:构造保证先付猴子
  • 2.拷贝构造

在子类的拷贝构造中,必须显式地调用父类的拷贝构造,否则编译器会自动调用父类的默认构造,而不是调用父类的拷贝构造。

拷贝构造也是构造,最后作用域结束会调用父类的析构对父类成员进行释放。

  • 3.赋值

在子类的赋值运算符重载同样需要显式地调用父类的赋值运算符重载。

  • 4.析构

析构函数就不同了,不能显式调用父类的析构函数。

原因如下:
1.在构造函数中是先构造父类再构造子类,析构的顺序应该是先析构子类再析构父类。如果显式调用就会改变顺序,不合理。

2.有可能在子类会使用父类的成员,如果父类先析构,可能会造成非法访问。

六、继承和友元

友元关系不能继承,也就是说基类的友元不能访问子类的私有成员和保护成员。

举个简单的例子:我父亲的朋友不是我的朋友。

如果想要父类的友元也变成子类的友元,则需要在子类中声明该函数为友元。

七、继承和静态成员

在继承中,你可以认为静态成员继承了,也可以认为没有继承。

因为对于静态成员,子类只继承了使用权。

在整个继承体系中,静态成员只有一份,子类和父类都可以共同使用。

用下面一段代码可以证明:
 

class Person
{
public :

    Person () {++ _count ;}
protected :

    string _name ; // 姓名
public :

    static int _count; // 统计人的个数。
};

int Person :: _count = 0;

class Student : public Person
{
protected :

    int _stuNum ; // 学号

};

class Graduate : public Student
{
protected :
    string _seminarCourse ; // 研究科目
};

void TestPerson()
{
    Student s1 ;
    Student s2 ;
    Student s3 ;
    Graduate s4 ;

    cout <<" 人数 :"<< Person ::_count << endl;

    Student ::_count = 0;

    cout <<" 人数 :"<< Person ::_count << endl;
}

这段代码计算整个继承体系一共创建了多少个类对象,包括父类和子类。

八、菱形继承和菱形虚拟继承

继承可以分为单继承和多继承.

下面这样的情况就是多继承。

【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

 


而菱形继承就是多继承的一种特例。

【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

8.1 菱形继承的问题

对于菱形继承来说,真正出问题的是上图的Assistant。

1.在它的成员中有两份重复的Person的成员,出现了数据冗余的情况。

2.如果想在Assistant中调用Person的成员变量/成员函数,编译器就无法确定到底该调用Teacher继承下来的还是调用Student继承下来的。

在上面的继承体系中,内存关系如下图:
【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

 按照各个类声明出现的顺序依次继承,内存从上到下放置。

为了解决菱形继承的问题,我们可以使用菱形虚拟继承来解决。

我们在菱形继承体系的腰部加上两个virtual,让Student和Teacher继承Person是虚拟继承。 

 【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

用虚拟继承可以解决菱形继承的原因:
 

 我们重新定义一个菱形继承:
 

class A
{
public:
	int _a;
};

// class B : public A
class B : virtual public A
{
public:
	int _b;
};

// class C : public A
class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

int main(){
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

在这个菱形继承中,我们通过调试观察可以发现,D对象的内存地址如下:

【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言【C++】从0到1讲继承|复杂的菱形继承,C++,c++,开发语言

 可以看到,前两个地址是B对象的地址,接着的是C对象的地址,再下来的地址是D对象中的成员_d的地址,而最后一个地址,其实是A对象中的成员_a的地址。

使用了菱形虚拟继承后,继承的对象在内存中会放到内存的最下面,在B和C对象的第一个地址中,存的是一个虚基表的地址,在虚基表中存的又是该对象相对于A对象的偏移量。

所以菱形虚拟继承可以通过查找到对象对应的虚基表的偏移量来获取对象A的地址,进而访问对象A的成员。这样就不用再在每个继承对象中都存一份A对象,并且在D子类中只有一份A对象,解决了数据冗余和二义性的问题。

A对象越大,越能够节省空间,因为在B和C对象中,只存了一个指针,指向虚基表,只有4字节。如果是存一个很大的数组,则需要花费巨大的空间。

总结:菱形虚拟继承是在对象中存一个指针,该指针指向一个虚基表,在虚基表中存着该对象相对于父类对象的偏移量,而父类在内存中是存储在整个继承体系内存的下面,能够通过偏移量找到父类对象的地址,进而访问父类对象的成员。

九、继承和组合

继承:白盒测试,每一部分细节都展示,需要测试每一部分代码的功能。

继承中每一个子类对象都是一个父类对象。

组合:黑盒测试,隐藏了细节,只暴露接口,用接口进行测试。 

组合中每一个子类对象都有一个父类对象。

十、常见笔试面试题

1. 什么是菱形继承?菱形继承的问题是什么?
2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的
3. 继承和组合的区别?什么时候用继承?什么时候用组合?

1.菱形继承是多继承中的一种,假如有一个父类对象A,子类对象B继承A,C也继承A,同时有一个子类对象D同时继承了B和C,这样的继承关系就是菱形继承。菱形继承的问题是在B类和C类中都有一份A类的成员,造成数据冗余,并且如果用D类对象访问A类的成员时,会出现二义性,也就是不知道该访问谁。

2、在B类继承A类和C类继承A类时加上一个virtual,就是菱形虚拟继承。菱形虚拟继承是在B类和C类中存储一个指针,该指针指向一个叫做虚基表的表,表中存着该对象和父类对象的地址偏移量,可以通过自己相对父类的偏移量找到父类的地址,进而访问父类成员。在上述的菱形继承案例中,A类的成员在整个继承体系中只有一份,就解决了二义性问题。而在B类和C类中只存储一个虚基表指针,可以解决数据冗余的问题。

3.继承是子类继承父类,可以使用父类的所有属性和方法,组合是将已存在的类作为新的类的成员,两者无上下级的关系。当我们只需要用一个类的接口函数时,用组合;其他情况用继承。

总结

本文讲解了C++继承的众多概念。文章来源地址https://www.toymoban.com/news/detail-604389.html

到了这里,关于【C++】从0到1讲继承|复杂的菱形继承的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++:面向对象大坑:菱形继承

    单继承:一个子类只有 一个直接父类 时称这个继承关系为单继承。 图示: 多继承:一个子类有 两个或以上直接父类 时称这个继承关系为多继承。 图示: 1.概念 菱形继承:菱形继承是多继承的一种特殊情况。即:一个类是另外几个类的子类,而这几个子类又是另外一个类

    2024年04月27日
    浏览(37)
  • 【C++练级之路】【Lv.12】继承(你真的了解菱形虚拟继承吗?)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 继承(inheritance),是面向对象的三大特性之一。 它是面向对象编程中, 使代码可以复用 的最重要的手段,它允许程序员在 保持原有类特性的基础上进行扩展

    2024年03月14日
    浏览(48)
  • C++中菱形继承中的多态在底层是如何实现的。

    如果还不了解菱形继承和多态的底层可以看这两篇文章: C++中多态的底层实现_Qianxueban的博客-CSDN博客 C++的继承以及virtual的底层实现_Qianxueban的博客-CSDN博客

    2024年02月09日
    浏览(40)
  • 【浅尝C++】继承机制=>虚基表/菱形虚继承/继承的概念、定义/基类与派生类对象赋值转换/派生类的默认成员函数等详解

    🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。 🎯每日格言:每日努力一点点,技术变化看得见。 我们生活中也有继承的例子,例如:小明继承了孙老师傅做拉面的手艺。继承就是一种延续、复用的方式。C++为了提高代码的可复用性,引入了继承机制,

    2024年04月10日
    浏览(48)
  • C++--菱形继承

    1.什么是菱形继承         单继承:一个子类只有一个直接父类时称这个继承关系为单继承                                            多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承的问题:菱形继承有数据冗余和二义性

    2024年02月15日
    浏览(38)
  • 想要入坑C++?当我拿出菱形虚拟继承,阁下又该如何应对

    🌸作者简介: 花想云 ,目前大二在读 ,C/C++领域新星创作者、运维领域新星创作者、CSDN2023新星计划导师、CSDN内容合伙人、阿里云专家博主、华为云云享专家致力于 C/C++、Linux 学习 🌸 本文收录于 C++系列 ,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造

    2024年02月07日
    浏览(39)
  • C语言打印各种三角形和菱形(包括星形菱形与空白格菱形)

    多重循环,也称嵌套循环,由一个外层循环和一个或多个内层循环组成。 for循环在C语言基础中占有重要地位,其中最能体现for的多重循环的就是打印各种形状的三角形,进而根据打印各种三角形的规律合并打印出菱形。 首先,我们来打印最简单的几个不同的三角形: 靠右直

    2024年02月04日
    浏览(50)
  • C语言打印菱形

    题目:输入对角线长度,打印对应的菱形(对角线必须是奇数,否则打印出的不是菱形) 代码如下:

    2024年01月24日
    浏览(37)
  • 打印菱形(C语言)

    首先,可以将菱形分成上下两部分 代码如下 代码如下 完整代码

    2024年02月06日
    浏览(37)
  • c、c++、java、python、js对比【面向对象、过程;解释、编译语言;封装、继承、多态】

    目录 内存管理、适用 区别 C 手动内存管理:C语言没有内置的安全检查机制,容易出现内存泄漏、缓冲区溢出等安全问题。 适用于系统级编程 C++ 手动内存管理:C++需要程序员手动管理内存,包括分配和释放内存,这可能导致内存泄漏和指针错误。 适用于游戏引擎和系统级编

    2024年02月08日
    浏览(76)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包