【c++】继承
目录
一、继承的表现
子类对父类成员的访问权限
二、父类与子类之间的相互赋值
三、继承的作用域
如果是父类和子类构成隐藏呢?
四、子类的成员函数怎么写
1.default构造函数
2.析构函数
所以析构函数不需要我们显式调用。
五、继承与友元函数
六、继承与静态成员
七、菱形继承
八、虚基表
继承是c++对于类复用的重要特性,与之前写过的对于函数代码的复用相似。
一、继承的表现
class A
{
public:
int _a;
};
class B:public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
在这个关系中,类B和类C都继承了类A,且均为public继承。
子类对父类成员的访问权限
无论是哪种继承,子类均能够完全继承父类的成员,但是访问权限不同。
继承方式影响的是类外通过子类访问父类的权限
public:子类可以访问,类外也可以访问
protected:子类可以访问,但是类外不能访问
private:子类和类外均不能访问
二、父类与子类之间的相互赋值
因为子类是从父类得到东西,并且加上了自己的东西,那么子类的成员是要比父类的多的,所以可得:子类可以赋值给父类,并且子类多的那一部分东西不会给父类,这个操作叫做切片。
class A
{
public:
int _a;
};
class B:public A
{
public:
B(const int& b = 0)
:_b(b)
{}
int _b;
};
在这里我没有给类A实现他的默认构造函数。
创建了一个b对象,b中有两个成员,一个是类A,一个是_b,类A中有成员_a, 相当于b中有_a和_b。
可以看到,b中的成员在给a进行赋值的时候并不会出错,只是把b的父类有的东西赋值给了a。
三、继承的作用域
在父类和子类中是允许出现两个相同名字的成员的,这种关系叫做隐藏,也叫重定义。
class Parent
{
public:
Parent()
:_num(0)
,_parent(0)
{
cout << "Parent()" << endl;
}
~Parent()
{
_num = _parent = 0;
cout << "~Parent()" << endl;
}
int _num;
int _parent;
};
class Child1:public Parent
{
public:
int _child;
};
class Child2 :public Parent
{
public:
int _child;
};
在这个关系中,类child1和类child2都有共同的成员变量_child,构成隐藏关系。
在进行访问时,互不干扰。
这是位于同一层的关系,他们都是父类的子类。(辈分相同)
如果是父类和子类构成隐藏呢?
可知是由于就近原则的关系,直接对于c3的同名成员变量进行操作是在给c3的成员继续赋值。
如果要访问父类的同名成员需要加上作用域限定符:
四、子类的成员函数怎么写
1.default构造函数
子类包含有父类的成员,在子类进行构造的时候,先调用父类的构造函数,然后才会调用自己的构造函数。
在子类进行析构的时候,先调用自己的析构函数后才会调用父类的析构函数。(后创建的先销毁)
显示调用的原因是我没有写Parent类的默认构造函数。
2.析构函数
他会先调用自己的析构函数后去调用父类的析构函数。
没写父类析构函数,也没有显示调用:
没写父类析构函数,显示调用:
写析构,显示调用:
因为在显示调用时候父类资源已经被释放,但是在子类的析构函数结束时,它会自动再调用一次父类的析构函数,所以出现内存问题。
所以析构函数不需要我们显式调用。
五、继承与友元函数
父类的友元函数并不是子类的友元函数,友元函数一向如此。
六、继承与静态成员
静态成员在子类中也存在,和父类的是同一个东西。
七、菱形继承
继承关系呈现菱形的继承是菱形继承,也即:一个父类有多个子类,一个孙子类继承了多于两个的子类,此时就构成了菱形继承。
菱形继承会造成很多不必要的麻烦:比如数据冗余和二义性。
数据冗余是指在父类和子类中都会存在相同的一个父类。
二义性是指在孙子类中访问父类的成员时不知道访问的是谁的。
因为两个子类中都存在这个父类的成员。
需要加上作用域限定符:
但其实我们想要的是一块空间存储类A就行了,所以加上关键字virtual使其成为虚继承:
这样就解决了数据冗余的问题,也解决了二义性的问题:
八、虚基表
观察内存窗口我们可以发现父类A的成员存在了类D的后面,不属于任何类。
由继承关系可以知道,在d的内存空间中,是严格按照继承顺序来排列的,先是b的空间,再是c的空间,再是d自己的空间,最后是公共的空间用来存放父类。
由内存窗口可以知道,分别有两个字节的空间不是存放子类的数据的,这两个字节的数据就是虚基表,用来存放当前地址到父类(公共空间)的距离,不然会找不到这块地址的。
由于虚基表的存在,每个类也都不是本来类型之和的大小了。
不加virtual关键字的情况:
sizeof(D) = (A的大小)sizeof(int)*2 + (B的大小)sizeof(int)*2 + (D的一个成员)sizeof(int) = 20
所以多出来的12个字节用来存放虚基表。