• 【C++进阶(八)】C++继承深度剖析


    💓博主CSDN主页:杭电码农-NEO💓

    ⏩专栏分类:C++从入门到精通

    🚚代码仓库:NEO的学习日记🚚

    🌹关注我🫵带你学习C++
      🔝🔝


    在这里插入图片描述

    1. 前言

    接下来的几篇博客会进入C++
    继承和多态的学习,在校招笔试
    和面试中这一章节考察的很多!
    请同学们耐心学习!

    本章重点:

    本篇文章着重讲解继承的概念和定义,
    父类和子类的对象赋值转换,
    继承中的作用域以及子类的默认成员函数
    以及继承和友元,继承和静态成员的关系
    最后讲解菱形继承和虚继承概念


    2. 继承的基本概念

    继承,其实就是一种代码的复用手段
    子类继承父类就能用父类中的变量!

    举一个例子:

    在师生管理系统中,有学生和老师两个
    角色,学生和老师的共同信息有:姓名
    性别,年龄和身高等等,然而学生又有一些
    专有的信息,比如学号和所属学院
    老师也有专属信息如:工号和所教学科

    这样就可以将师生的共同信息提取出来:

    struct Person
    {
    	string name;
    	string sex;
    	int age;
    	int height;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在实现student类和teacher类时
    只需要继承上面的person类即可!

    class Student : public Person
    {
    protected:
    	int _stuid; // 学号
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    class Teacher : public Person
    {
    protected:
    	int _jobid; // 工号
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    派生类也被称为子类
    基类也被称为父类

    父子类成员的使用:

    Student st;
    st._stuid=123456;
    st.name="张三";
    st.age=20;
    
    • 1
    • 2
    • 3
    • 4

    子类对象中课直接调用父类变量!


    3. 继承关系和访问限定符

    继承方式和访问限定符一样有三种:
    在这里插入图片描述

    继承的方式不同,那么子类中继承
    到的父类的变量的访问权限就不同
    可以用下面的表格来表示它们的关系:

    在这里插入图片描述
    我简单的总结以下几点:

    1. 无继承体系中,protected和private没有区别
    2. 在继承体系中,父类的protected成员在子类
      中也是protected或保护成员
    3. 父类的private成员在子类是不可见的!
      (继承下来了但不能使用)
    4. 实际中使用继承时一般都用public继承
    5. 使用关键字class时默认的继承方式是private
      使用struct时默认的继承方式是public

    4. 继承中的作用域

    先说以下结论:

    1. 继承体系中基类和子类有独立的作用域
    2. 子类和父类中有同名成员时,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义(在子类成员函数中,可以使用基类::基类成员显示访问)
    3. 需要注意的是如果是成员函数的隐藏
      只需要函数名相同就构成隐藏
    4. 实际中在继承体系里面最好不要定义同名的成员
    class Person
    {
    protected :
    	int _num = 111;   // 身份证号
    };
    
    class Student : public Person
    {
    protected:
    	int _num = 999; // 学号
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在main函数中定义student对象
    后再打印_num默认为子类中的_num
    若想打印父类中的_num,需要指定类域

    Student st;
    cout<<st._num;
    cout<<st.Person::_num;
    
    • 1
    • 2
    • 3

    有同学可能会疑惑:函数名相同的话
    不应该是构成函数重载吗?是的,在同一
    作用域下,函数名相同确实构成函数重载
    但是父子类是不同作用域,这里是构成隐藏!


    5. 父子类的对象赋值转换

    老样子,先说结论:

    • 子类对象可以赋值给基类的
      -/对象/基类的指针/基类的引用

    • 基类对象不能赋值给派生类对象

    在这里插入图片描述

    注意这里能够赋值不是隐式类型转换!


    6. 子类中的默认成员函数

    还记得类的六个默认成员函数吗?
    就是不显示写系统会自动生成的:

    在这里插入图片描述

    子类的默认成员函数有哪些特殊的行为?
    下面我直接给出结论:

    1. 子类的构造函数必须显示调用父类的构造
      去初始化父类的那部分成员(拷贝构造也是)
    2. 子类的operator=中必须调用父类的
      operator=完成父类成员赋值
    3. 子类的析构函数不用显示调用父类的析构
      编译器会自动去调用
    4. 子类初始化对象时,先初始化父类的成员变量
      再初始化子类的成员变量
    5. 子类析构清理时先调用子类的析构函数
      再调用父类的析构函数(与构造反过来)

    可以使用以下代码区验证此结论:

    class Person
    {
    public :
     Person(const char* name = "peter")
     : _name(name )
     {
     cout<<"Person()" <<endl;
     }
        
     Person(const Person& p)
     : _name(p._name)
     {
     	cout<<"Person(const Person& p)" <<endl;
     }
        
     Person& operator=(const Person& p )
     {
     	cout<<"Person operator=(const Person& p)"<< endl;
     	if (this != &p)
     	_name = p ._name;
            
     	return *this ;
     }
        
     ~Person()
     {
     	cout<<"~Person()" <<endl;
     }
    protected :
     	string _name ; // 姓名
    };
    class Student : public Person
    {
    public :
     Student(const char* name, int num)
     	: Person(name )
     	, _num(num )
     	{
     	cout<<"Student()" <<endl;
     	}
     
     Student(const Student& s)
     	: Person(s)
     	, _num(s ._num)
     {
     	cout<<"Student(const Student& s)" <<endl ;
     }
     
     Student& operator = (const Student& s )
     {
     	cout<<"Student& operator= (const Student& s)"<< endl;
     	if (this != &s)
     	{
     		Person::operator =(s);
    		_num = s ._num;
     	}
     return *this ;
     } 
     
     ~Student()
     {
     	cout<<"~Student()" <<endl;
     }
    protected :
     	int _num ; //学号
    };
    void Test ()
    {
     	Student s1 ("jack", 18);
     	Student s2 (s1);
     	Student s3 ("rose", 17);
     	s1 = s3 ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    7. 继承与友元,继承与静态变量

    继承与友元的关系很简单一句话:

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

    继承和静态成员的关系也很简单:

    基类中定义的静态成员被整个继承体系共享
    整个继承体系里面只有一个这样的成员
    无论派生出多少个子类
    都只有一个static成员实例


    8. 菱形继承和虚拟继承

    在使用继承时会遇见以下情况:

    类B继承了类A,类C也继承了类A
    然而类D继承了类B和C

    在这里插入图片描述

    此时会有一个问题,类D的实例化对象中
    有类B和类C,然而B类和C类都有A类
    所以说D类对象中的A类成员就重复了!

    class A
    {
    	int _a = 1;
    };
    class B :public A
    {
    	int _b = 2;
    };
    class C :public A
    {
    	int _c = 3;
    };
    class D :public B, A
    {
    	int _d = 4;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们通过内存窗口观察一下:

    在这里插入图片描述

    D对象中有两个_a,一个在B类一个在C类
    这就造成了数据冗余,于是可以使用虚拟继承
    来解决这一问题:

    虚拟继承:在继承前加上virtual关键字

    class A
    {
    	int _a = 1;
    };
    class B :virtual public A
    {
    	int _b = 2;
    };
    class C :virtual public A
    {
    	int _c = 3;
    };
    class D :public B, A
    {
    	int _d = 4;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意,只用腰部的类加上virtual即可!
    virtual这一关键字在多态还有大用处!


    9. 总结以及拓展

    继承是多态的基础,而笔试面试的时候
    继承和多态是考察的很多的,希望同学们
    把基础打扎实.当然关于继承的内容其实不止
    这些,这些只是最重要的内容,关于继承问题
    我们将在下一章节:多态时再展开叙述

    对于is-a和has-a的拓展阅读:

    拓展阅读


    🔎 下期预告:C++继多态 🔍
  • 相关阅读:
    QCustomPlot的下载和使用
    jQuery自定义类封装
    【网站】让自己的个人主页能被Google检索
    Selenium+Webdriver实现自动化登录
    存储器的校验:汉明码
    排查服务器异常流量保姆级教程
    nginx中将指定文件夹设置为虚拟目录
    [附源码]java毕业设计商务酒店管理系统
    mysql建表考虑那些,怎么建
    2022-6学习笔记
  • 原文地址:https://blog.csdn.net/m0_61982936/article/details/133868018