C++中继承的理解与应用
1.继承的理解
一.继承的定义
二.继承的结构
三.基类与派生类的转化
四.继承中的作用域
五.派生类中的默认成员
2.继承的使用
一.实现一个不能被继承的基类
二.继承与友元
3.多继承及其菱形继承问题
一.继承模型
二.虚继承
三.继承与组合
1.继承的理解
一.继承的定义
继承是一个类对一个类的复用,这里的复用的意思把一些类共有的部分提取出来,将共有的部分放在一个类里面,这个类叫做父类,而其他部分不同的类叫做子类。相对于子类而言,可以使用父类中的内容,也可以说是继承,如下图所示,是一个继承的格式,这里的perator是父类,son是子类。
二.继承的结构
如下图所示,继承的结构如下图所示,这里的son是派生类,public是继承的方式,perator是一个基类。
这里对于父类来说它的访问限定符有三种方式,对于派生类中的继承方式也有三种方式,所以其共有9种组合,如下图所示。
这里的规律是1.如果基类中的成员是private则在派生类中不可见(即不可使用).如下图所示。
2. 对于整体来说,在派生类中的成员会去两个组成中选权限最小的一个(这里的关系有(public>protected>privte).3.对于派生类的protected成员来说,我们在派生类中可以使用父类中的成员,对于类外不可以使用,对于派生类的public成员来说在类内(派生类)外也可以使用父类中的成员,对于派生类中的private成员在类内外都不可以访问
三.基类与派生类的转换
对于不同类型的变量之间的转换是创立一个临时变量,通过临时变量来完成不同类型变量之间的转换。如下图所示,因为临时变量具有常性,所以在使用引用是需加是const.
但是对于基类与派生类之间的转换,这之间没有临时变量,它是通过切割的方法实现的,将要转换的指针或引用指向派生类中基类的部分。如下图所示,将在student 中perator的部分分割出来,使operator的对象指向这一部分。
但是如果是基类向派生类的转换是不可以的,如下图所示。
四.继承中的作用域
与局部类,全局类等一样,继承也有自己的域,其中基类与派生类中都有自己独立的域。如果基类与局部类都有同名成员,当我们派生类中的使用同名对象时,会优先使用派生类中的对象,屏蔽基类中的对象,这个现象叫隐藏。如下图所示
如果我们要想使用基类中的同名成员,就要指定类域,如下图所示。
五.派生类的默认成员
1.构造函数
对于派生类中的构造函数,它会先去调用基类中的构造函数初始化派生类中基类的部分,再调用派生类中的构造函数初始化派生类的部分。
2.operator=()函数
对于基类与派生类中的operator=()来说,他们构成隐藏,所以我们想要实现operator=()函数,就要在派生类中通过指定基类中的作用域来实现对派生类中基类部分的操作。
3.析构函数
对于派生类与基类来说,析构函数是先调用派生类中的析构函数,再调用基类中的析构函数。
2.继承的使用
一.实现一个不能被继承的基类
如果问我们想要使一个基类不能被派生类继承,一般有两种方式。方式一:将基类中的构造函数设为私有,并且显示的表示出来,如果我们不显示的表示出基类中构造函数,就不会报错,如下图所示。
二.通过添加final来实现基类不能被派生类继承,如下图所示,当我们在基类后面加上final后,基类就会被修饰成为最终类,最终类不能被其他类继承。
二.继承与友元
对于一个基类的友元来说,它不能访问基类派生类中的成员和成员函数, 如下图所示,Diay函数是perent类的友元,student是perent的派生类,对于perent来说,Diay可以访问到perent中的成员,但是它不可以访问到student中的成员。
三.继承与静态成员
对于继承中的静态成员,当我们在基类中定义了一个静态成员后,在整个继承体系中这个静态成员指向相同的空间,无论我们在多个派生类中调用这个静态成员都是对一个空间的调用。如下图中对于非静态的对象,我们调用同一对象时是指向不同的空间,对于静态对象我们调用同一对象是是指向同一空间。
四.继承模型
1.单继承
一个派生类只有一个基类的继承为单继承。
2.多继承
一个派生类有两个以上的基类继承为多继承,多继承对象的空间模型是,先继承的基类在前面,后继承的基类在后面,派生类在最后,如下图中s中的成员空间大小为s._age<s.cout<s._num,所以对应了前面的先继承在前面,后继承的在后面,派生类在最后。
3.菱形继承
菱形继承是多继承的一种特殊的形式,如下图是一个菱形继承的图。但是对于菱形继承是不推荐使用的,因为菱形继承有数据冗余和二义性,数据冗余是指在sune对象中student和techer都有相同的perent中的成员变量或成员函数,此时就相当于在sune对象中有两份相同的数据,造成数据冗余。
对于数据的二义性是指的是数据不是唯一的,如下面代码所示,当我们在main函数中调用perent中的成员函数或成员变量时,编译器就会分不清我们调用的是student中的perent还是techar中的perent.
#include<iostream>
using namespace std;
class perent
{
public:
int _num;};
class student :public perent
{
protected:
int _age;
};
class techer:public perent
{
protected:
int _cass;
};
class sune :public student, public techer
{protected:
int _name;
};
int main()
{
return 0;
}
就会有下面的错误。
我们要指定类去访问,如下图所示。
二.虚继承
对于菱形继承中数据冗余和二义性可以加上virtual来解决,如下面代码中我们可以在要继承的基类在下面位置中加上virtual就可以解决数据冗余和二义性,这里必须对的第一个继承的基类加上virtual其他的基类可以不用加上。
#include<iostream>
using namespace std;
class perent
{
public:
int _num;};
class student :virtual public perent
{
protected:
int _age;
};
class techer: virtual public perent
{
protected:
int _cass;
};
class sune :public student, public techer
{protected:
int _name;
};
int main()
{
sune s;
s._num;
return 0;
}
虚继承解决菱形继承中数据冗余和二义性的方法是,只生成一份student和techer中共同的基类供student和techer共同的派生类使用,派生类在初始化时,只会调用派生类中的student和techer的共同基类,在这期间不会调用student和techer中的基类,使用解决了数据冗余和二义性。
3.继承与组合
继承 | 组合 | |
关系本质 | 是一个(is-a),比如学生是一个人 | 有一个(has -a),比如汽车有一个发动机 |
语法表现 | 通过class 子类 :继承方式 父类的方法实现 | 在类的成员变量中声明另一个类的对象 |
耦合性 | 强(子类与父类的关系密切,若父类修改可能会 影响子类。 | 弱(一个类只是另一个lei的接口,这个类内容被 修改时,只要接口不变,就不会影响另一个类 |
复用灵活性 | 低(复用的是整个父类的身份和行为,子类必须继承父类的全部(私有部分除外) | 高(复用是被包含类的功能,一个类可以灵活包含多个类的功能) |
菱形继承问题 | 有 | 无 |