zoukankan      html  css  js  c++  java
  • 【C++】浅谈三大特性之一继承(三)

    四,派生类的六个默认成员函数

    在继承关系里,如果我们没有显示的定义这六个成员函数,则编译系统会在适合场合为我们自动合成。

    继承关系中构造函数和析构函数的调用顺序:

    class B
    {
    public:
    	B()
    	{
    		cout<<"B()"<<endl;
    	}
    	~B()
    	{
    		cout<<"~B()"<<endl;
    	}
    };
    
    class D:public B
    {
    public:
    	D()
    	{
    		cout<<"D()"<<endl;
    	}
    	~D()
    	{
    		cout<<"~D()"<<endl;
    	}
    };
    
    void Funtest()
    {
    	D d;
    }
    int main()
    {
    	Funtest();
    	return 0;
    }

    非常简单的一段代码,你觉得会打印什么呢?一起来看看


    有人看到这里,肯定会说,那明摆着嘛,先调用B类的构造函数再调用D类的构造函数,根据栈空间先进后出的原则,接着先析构B类自己,再析构从基类那继承来的部分,可是,事实真的这么简单吗?当然不。

    你想想,你创建的是子类的对象,怎么可能先去调用基类的构造函数呢,既然这样,为什么打印结果显示的确是先调用父类的构造函数呢???这里牵扯到构造函数的调用次序和函数体的执行顺序的问题,注意不要混淆它们。

    实际上,调用顺序是这样的:


    你可能会有点疑惑,明明先调用了子类的构造函数,可是为什么会去先执行基类的构造函数体,这是因为我们显示定义了父类的缺省构造函数,你想想,既然显示定义了,就必须调用它对吧,如果我们不在子类的构造函数中调用它,那何时调用呢,但是由于我们创建的是子类对象,所以必须先调用子类的构造函数,思来想去,将基类的构造函数放在子类的初始化列表中调用似乎再合适不过了,所以,我们C++的设计者们就是这样做的,是不是很聪明呢?


    在这里,还需要注意一点,如果基类的显示的定义了缺省的构造函数,那么基类的构造函数即使不显示定义,编译器也会为我们合成默认的构造函数用来调用基类的构造函数,组合也是如此。

    五,继承体系中的作用域

    1,继承体系中,子类的作用域和父类的作用域属于两个作用域。(在子类中不能访问父类的私有成员足以说明此点)

    2,同名隐藏。如果子类中包含和父类相同名字的成员,则子类成员将屏蔽对父类成员的直接访问,如果想要在子类中访问父类的同名成员,就必须采用作用域限定符。

    eg:

    class B
    {
    protected:
    	int _a;
    };
    
    class D:public B
    {
    public:
    	void test()
    	{
    		_a = 10;
    	}
    private:
    	int _a;
    };
    
    void Funtest()
    {
    	D d;
    	d.test();
    }
    int main()
    {
    	Funtest();
    	return 0;
    }

    运行结果:


    从监视窗口中,我们清晰的看到了子类对象的_a被改为10,而父类的成员数据_a仍然是一个随机值。怎样做到在子类对象中改变的是父类对象的成员数据呢?

    eg2:

    class B
    {
    protected:
    	int _a;
    };
    
    class D:public B
    {
    public:
    	void test()
    	{
    		B::_a = 10;
    	}
    private:
    	int _a;
    };
    
    void Funtest()
    {
    	D d;
    	d.test();
    }
    int main()
    {
    	Funtest();
    	return 0;
    }

    运行结果:

    注意:尽量避免父类和子类使用同名成员,不要给自己挖坑哦!大笑

    六,赋值与转换----赋值兼容规则


    1,子类对象可以直接赋值给父类对象(切片/割片)。

    2,父类对象不能直接赋值给子类对象。

    3,父类对象的引用或指针可以直接指向子类对象。

    4,子类对象的引用或指针不可以直接指向父类对象。(强制类型转换可完成)

    对象赋值:


    引用或指针:

    class B
    {
    protected:
    	int _b;
    };
    
    class D:public B
    {
    private:
    	int _d;
    };
    
    void Funtest()
    {
    	D d;
    	B *b;
    	b = &d;//父类指针指向子类对象
    
    	D *d1;
    	B b1;
    	d1 = (D*)&b1;//子类对象可通过强制类型转换指向父类对象(尽量避免)
    
    	D &d2 = d;
    	B b2;
    	b2 = d2;//父类引用指向子类对象
    
    	D d3;
    	B &b3 = b1;
        d3 = (D&)b3;//父类引用指向子类对象
    }
    int main()
    {
    	Funtest();
    	return 0;
    }
    

    七,单继承&多继承&菱形继承

    <1>单继承:一个子类仅有一个直接的父类。

    单继承中类中成员数据的分布与成员变量在类中的定义顺序有关。


    <2>多继承:一个子类有两个或两个以上直接的父类。


    多继承中派生类成员的分布与继承类的先后次序有关



    <3>菱形继承(钻石继承)


    菱形继承中成员的分布与最底层类继承的先后次序有关


    上图中我们标出了菱形继承中各个类所占字节数,可是C1类和C2类中都继承了B类中的数据成员_b,那么如果我们通过D类的对象对_b进行访问,必然会产生二义性,

    class B
    {
    	int _b;
    };
    class C1:public B
    {
    	int _c1;
    };
    class C2:public B
    {
    	int _c2;
    };
    class D:public C1,public C2
    {
    	int _d;
    };
    void Funtest()
    {
    	D d;
    	d._b = 10;//错误,访问不明确
    	d.C1::_b = 10;//正确
    	d.C2::_b = 10;//正确
    }

    如何避免这种访问不明确呢,是否可以将重复部分_b只在D类中保存一份呢,这将引入虚拟继承的概念。


    eg:

    class B
    {
    	int _b;
    };
    class C1:virtual public B
    {
    	int _c1;
    };
    class C2:virtual public B
    {
    	int _c2;
    };
    class D:public C1,public C2
    {
    	int _d;
    };
    void Funtest()
    {
    	B b;
    	C1 c1;
    }

    上面这段代码就是一个虚拟继承的例子,注意关键字virtual的位置不要写错哦吐舌头


    当创建好C1类的变量c1时,编译器会为C1合成一个默认的构造函数,这个合成的默认构造函数会做哪些事呢?

    首先,如果基类有缺省构造函数,它会去调它,其次,它会将偏移量的地址指针(虚指针)放在c1对象的前4个字节处。



    再来看一下如果是D类的对象,又是怎么存储的呢?


    动脑筋想一下,如果B,C1,C2,D均为空类,每个类所占字节大小又是多少呢?

    最后,需要注意的几点:

    1、友元关系不能继承,因为友元关系不属于类的成员(就好比你朋友的女朋友并不是你的女朋友)。

    2、如果类中包含静态成员,无论继承了多少派生类,静态成员都只保存一份。

    3、析构函数和构造函数不能被继承下来。原因:派生类除了继承基类的成员外,还可以添加只属于自己的新成员,如果用继承来的构造函数初始化,只能初始化从基类继承来的那部分,而派生类本身新添加的那部分成员初始化不了。析构函数也是一样的,初始化不到派生类新添加的成员,导致内存泄漏。

  • 相关阅读:
    轻量模型之Mobilenet
    GAN的Loss
    Ubuntu16.04安装后配置一条龙
    Hardnet论文阅读
    orb-slam2编译时遇到的问题
    编译opencv+opencv_contrib
    Sophus库使用踩坑
    CloudCompare Viewer使用心得
    交通场景语义分割
    ROS编译中遇到的问题
  • 原文地址:https://www.cnblogs.com/qq329914874/p/6005882.html
Copyright © 2011-2022 走看看