• c++基础(十四)——继承


    一、继承的基本语法

    继承是面向对象三大特性之一,有些类与类之间存在特殊的关系,当定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候我们就可以考虑利用继承的技术,减少重复代码。
    基本语法:class 子类 :继承方式(pubilc等) 父类
    其中,子类也称为派生类;父类也称为基类。
    其中派生类中的成员,包含两大部分:
    一类是从基类继承过来的,一类是自己增加的成员。
    从基类继承过来的表现其共性,而新增的成员体现了其个性。

    样例如下:

    //继承的实现
    class BasePage
    {
    public:
    	void head()
    	{
    		cout << "这是一个公共的头" << endl;
    	}
    	void top()
    	{
    		cout << "这是一个公共的底" << endl;
    	}
    };
    
    //java页面
    class Java: public BasePage
    {
    public:
    	void centent()
    	{	
    		cout << "这是java" << endl;
    	}
    };
    
    class Python : public BasePage
    {
    public:
    	void centent()
    	{
    		cout << "这是Python" << endl;
    	}
    };
    
    class CPP : public BasePage
    {
    public:
    	void centent()
    	{
    		cout << "这是c++" << endl;
    	}
    };
    
    void test01()
    {
    	cout << "这是Python" << endl;
    	Python py;
    	py.head();
    	py.centent();
    	py.top();
    
    	cout << "-----------------" << endl;
    	cout << "这是java" << endl;
    	Java jv;
    	jv.head();
    	jv.centent();
    	jv.top();
    
    	cout << "-----------------" << endl;
    	cout << "这是C++" << endl;
    	CPP cpp;
    	cpp.head();
    	cpp.centent();
    	cpp.top();
    }
    
    • 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

    二、继承方式

    基类中有如下几种不同的属性,分别为公共属性,保护属性和私有属性,那么反映到继承中有如下三种:

    class A
    {
    public:
    	int a;
    protected:
    	int b;
    private:
    	int c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    维承方式一共有三种:
    1、公共继承

    class B : public A
    {
    public:
    	int a;
    protected:
    	int b;
    不可访问:
    	int c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、保护继承

    class B : protected A
    {
    public:
    	int a;
    	int b;
    不可访问:
    	int c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、私有继承

    class B : private A
    {
    private:
    	int a;
    	int b;
    不可访问:
    	int c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    从上述三种方式我们可以看出,三种继承方式都不可访问基类中的私有属性。而公共属性和保护属性均可访问,但其中不同的是,公共属性并不会改变基类中的属性变化,而保护继承会将基类中所有的属性转变为保护的属性,私有继承会将基类中的所有属性变为私有。这里的样例就不再一一举出了。

    三、继承中的对象模型

    从父类继承过来的成员,哪些属于子类对象中?
    父类中的所有非静态成员变量属性,包括私有属性、子类自身的变量,都会在子类中,但是私有成员并没有权力访问这一属性。
    在这里介绍一个实用工具VS的开发人员命令提示符查看类对象的分布布局:
    该命令提示符是在VS下的一个命令行:
    在这里插入图片描述

    打开之后如下图:在这里插入图片描述

    首先将路径切换到项目所在的路径:
    在这里插入图片描述
    输入如下指令:cl /d1 reportSingleClassLayout要显示的类名 “源文件名称”
    在这里插入图片描述

    输入完成之后则会显示如上图,其中size(16)代表的是改类别占据16个字节的内存空间,它的基类是base且基类下共有三个属性,分别为:m_A、m_B、m_C,子类中自带的属性为m_D。

    四、继承中构造和析构顺序

    子类继承父类后,当创建了类对象,也会调用父类的构造函数。

    那么当一个子类先调用时,先有父类构造函数还是现有子类构造函数呢?
    答案是先有父类构造函数,再有子类构造函数,再有子类析构函数,最后调用父类析构函数。

    五、继承中同名成员处理方式

    当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
    1、当访问子类同名成员时,直接访问即可
    样例代码如下:

    class Base
    {
    public:
    	Base()
    	{
    		m_A = 10;	
    	}
    		int m_A;
    };
    
    class Son:public Base
    {
    public:
    	Son()
    	{
    		m_A = 200;
    	}
    	int m_A;
    };
    
    void test02()
    {
    	Son s;
    	cout << "m_A = " << s.m_A << endl;
    }
    
    • 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

    2、访问父类同名成员时,则需要加载作用域。
    样例代码如下:

    cout << "Base m_A = " << s.Base::m_A << endl;
    
    • 1

    但是当父类与子类出现了同名成员函数之后,子类的同名成员函数会隐藏掉父类中所有同名成员函数,并不能直接对父类中的成员函数进行访问,如果想要访问到父类被隐藏的同名成员函数,则需要加作用域。,具体的语法如上。

    六、继承同名静态成员处理方式

    在这之前先要复习一下静态成员变量有什么特点:
    1、所有对象共享同一份数据;
    2、编译阶段就分配内存;
    3、类内声明,类外初始化
    静态成员函数有什么特点:
    1、只能访问静态成员变量,不能访问非静态成员变量;
    2、所有对象共享同一份函数实例;

    那么当静态成员和非静态成员出现同名,处理方式是怎么样的呢?
    1、访问子类同名成员直接访问即可
    2、访问父类同名成员需要加作用域
    可以看出,继承同名静态成员的处理方式与非静态的成员变量是一样的。这里就不再举例了。
    与非静态成员变量不同的是,静态成员变量可以通过类名进行访问,样例如下:
    访问子类的静态成员变量时:

    Son::m_A
    
    • 1

    访问父类的静态成员变量时:

    Son::Base::m_A
    
    • 1

    上述的两个::分别代表通过类名的方式访问以及访问父类作用域下的静态成员变量。

    七、多继承语法

    在C++是允许一个类继承多个类的,其基本语法如下:

    class 子类: 继承方式 父类1, 继承方式 父类2 ...
    
    • 1

    注意:多继承可能会引发父类中有同名成员出现,需要加作用域区分,在c++实际开发中,并不建议用多继承。
    样例如下:

    class Base1
    {
    public:
    	Base1() 
    	{
    		m_A = 100;
    	}
    	int m_A;
    };
    
    class Base2
    {
    public:
    	Base2()
    	{
    		m_A = 100;
    	}
    	int m_A;
    };
    
    class Son:public Base1, public Base2
    {
    public:
    	Son()
    	{
    		m_C = 200;
    	}
    	int m_C;
    };
    
    void test03()
    {
    	Son s;
    	cout << "sizeof = " << sizeof(s) << endl;
    	cout << "Base1.m_A = " << s.Base1::m_A << endl;
    }
    
    • 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

    八、菱形继承问题以及解决方法

    菱形继承概念:
    两个派生类继承同一个基类
    又有某个类同时继承者两个派生类
    这种继承被称为菱形继承,或者钻石继承
    具体示例如下:

    在这里插入图片描述
    菱形继承问题:
    1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
    2.草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
    样例如下:

    class animal
    {
    public:
    	animal()
    	{
    		m_Age = 30;
    	}
    	int m_Age;
    };
    
    class sheep :public animal{};
    class camel :public animal{};
    class alpaca :public sheep, public camel{};
    
    void test04()
    {
    	alpaca a;
    	a.sheep::m_Age = 10;
    	a.camel::m_Age = 20;
    	cout << "sheep.age" << a.sheep::m_Age << endl;
    	cout << "camel.age" << a.camel::m_Age << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    1、从上述样例中我们可以看到,当出现菱形继承时,两个父类拥有相同的数据,需要加以作用域区分。
    2、当录入羊驼对的年龄时,我们只需要一份数据即可,但是菱形继承导致了有两份数据的出现,造成了资源的浪费。
    这个时候我们就可以利用虚继承来解决如上的问题。
    虚继承的样例如下:

    class sheep :virtual public animal{};
    class camel :virtual public animal{};
    class alpaca :virtual public sheep, public camel{};
    
    • 1
    • 2
    • 3

    在虚继承之前,加上关键字virtual可以变为虚继承。经过虚继承处理之后,则该基类就称为虚基类。虚继承会将继承类的成员变量公有化,这份数据就只有一个了。通过继承指针的方式来访问这块内存,这块内存只有一个。

  • 相关阅读:
    访问器模式(Visitor Pattern)
    项目上线就炸,这谁受得了
    eclipse-kepler-SR1-4.3.1版本下载
    Nacos安装
    windows下查看端口及占用端口的进程
    第六章 Ambari二次开发之自定义Flink服务 -- metainfo.xml配置文件详解
    MyBatis完成添加、修改、删除功能
    Redis未授权访问漏洞实战
    JS 高级
    redis基础数据结构
  • 原文地址:https://blog.csdn.net/qq_52302919/article/details/127658511