继承,虚继承

一.我们先来说说什么是继承

继承是面向对象的复用的重要手段,通过继承定义一个类,继承是类型之间的关系建模,共享共有的东西,实现各自本质不同的东西。

继承分为以下几种:   

                                  

      下面是三种继承关系下基类成员在派生类中的访问变化:

继承方式                   基类的public              基类的protected            基类的private        继承引起的访问控制关系                                   成员                                   成员                                         成员              变化概括                                   

public继承                 仍为public                  仍为protected                 不可见                   基类的非私有成员在子类

                                    成员                             成员                                                                的访问属性都不变

protected继承          变为protected              变为protected                 不可见                  基类的非私有成员都成

                                   成员                               成员                                                               为子类的保护成员

private继承                变为private                  变为private                      不可见                  基类的非私有成员都成

扫描二维码关注公众号,回复: 2056584 查看本文章

                                     成员                              成员                                                               为子类的私有成员

区别隐藏/重定义,重载,重写/覆盖:

隐藏/重定义:子类与父类的同名成员,子类隐藏父类的成员,在访问时是按照就近原则。想要指定访问,必须指定作用域。

重载(前提:在同一个作用域)函数名相同,参数不同或者返回值可以不同。

重写/覆盖:在子类定义一个与父类完全相同的虚函数,要求返回值,函数名,参数列表都相同。


(has-a)protected/private继承

(is-a)public继承

使用关键字class时默认的继承方式是private。使用struct时默认的继承方式是public。

二.赋值兼容规则

示例代码:

#include<iostream>
using namespace std:
class person
{
public:
void Display()
{
cout << "person()"<< endl:
}
protected:
string _name;
private:
int _age;
};


class student; public person
{
public:
int _num;
};
int main()
{
person b;
student a;
//子类对象可以赋给父类对象(切片/切割)
b = a:   //yes


//父类对象不可以赋给子类对象
//a = b;    //no

//父类指针/引用可以指向子类对象
person* p1 = &a;
person& p1 = a;


//子类指针/引用不能指向父类对象(可以通过强制类型转换来指向)
//student* s1 = &b;    //no
student*s1 = (student*)&b;     //yes
//student& s2 = b;        //no
student& s2 = (student&)b;    //yes


return o;
}


子类对象可以赋值给父类对象(切片/切割)

父类对象不可以赋值给子类对象

父类指针/引用可以指向子类对象

子类指针/引用不能指向父类对象(可以通过强制类型转换来指向)

解释

什么是切片/切割?


子类可以赋值给父类,是因为子类里边包含父类里的全部成员变量,所以可以通过切片把父类的成员都赋予相应的值。父类赋给子类为什么不可以呢?是因为父类里面没有子类独有的那部分变量,所以无法给子类独有的成员变量赋值。


在派生类中的默认成员函数:

在继承关系里,如果派生类没有显示的定义6个默认成员函数(构造函数,拷贝构造,析构函数,赋值运算符重载,取地址操作符重载,const修饰的取地址重载符重载),编译系统则会默认合成这6个成员函数

(1)当基类构造函数不带参数时,派生类不一定需要定义构造函数,系统会自动的调用基类的无参构造函数;然而当基类的构造函数哪怕只带有一个参数,它所有的派生类都必须定义构造函数,参数只是被传递给了要调用的基类构造函数

(2)如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始,依次上溯。


多继承与菱形继承会出现二义性和数据冗余现象:


#include<iostream>
using namespace std;


class A
{
public:
int _a;
};


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


class C : virtual public A
{


public:
int _c;
};


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


int main()
{
D dd;


cout << sizeof(dd) << endl;


dd.B::_a = 1;
dd._b = 3;


dd.C::_a = 2;
dd._c = 4;


dd._d = 5;


B bb;
C cc;

cout << sizeof(bb) << endl;
//bb = dd;
//cc = dd;


//A* pa = &dd;


//B* pb = &dd;


//C* pc = &dd;


//D* pd = &dd;
return 0;
}


为什么结果会是这样?下面我们来看看对象的内存分布就明白了


若要解决二义性和数据冗余就得使用虚继承(在继承基类时,使用关键字virtual)



从上图可以发现,当使用虚继承后,通过虚基表就解决了数据二义性的问题。多了虚基表的空间所以sizeof(dd)

就变成了24。

一般不到万不得已的情况下,不要定义虚继承和菱形继承结构,因为它会带来性能上的损耗。




















































猜你喜欢

转载自blog.csdn.net/huaijiu123/article/details/77823234