目录
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
继承是类设计层次的复用。
对其理解:
当我们的多个类中,有很多数据在每个类中都会存在时,我们可以考虑将公共数据提出来,单独为其设计一个类,叫做基类。一些继承了基类的类叫做派生类,派生类拥有基类的所有数据,且可以在自己的类域中增加其他数据,提高了代码的复用性。
派生类 :继承方式 基类
class Person { public: void Print() { cout << "name:" << _name << endl; cout << "age:" << _age << endl; } protected: string _name = "C++"; int _age = 18; }; class Student :public Person { protected: int _stuid; }; class Teacher :public Person { protected: int _jobid; };派生类拥有基类的所有数据:
也拥有着其成员函数:
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected成 员 | 派生类的private成 员 |
基类的protected成 员 | 派生类的protected成 员 | 派生类的protected成 员 | 派生类的private成 员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
上述举例为public继承,还有protected继承和private继承
通过这些继承方式继承下来的基类成员的权限,由继承方式和在原基类中的权限决定,取小。
权限大小:public > protected > private
注意:
基类的私有成员在子类中都是不可见的;
protected / private 成员在基类中,类外面不能访问,类里面可以访问;
protected / private 成员在派生类中,private在类中不可用,protected在类中可用。
基类对象不能赋值给派生类对象;
基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的(了解);
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用;
会将派生类中从基类继承下来的那些成员赋给基类,这里有个形象的说法叫切片或者切割。
注意:上述三种赋值间不存在类型转换,这是一种天生的行为方式。
从赋值给基类引用就可以发现:
若存在类型转换,那么中间必然会产生一个临时变量,临时变量有常性,类前需要加const 。
这里要注意,引用部分是子类中继承基类的那一部分:
地址也仅仅是看继承下来的那些数据:
1.基类和派生类的作用域都是独立的;
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,优先访问子类中的成员,这种情况叫隐藏,也叫重定义。若要访问基类成员,可以显示访问--基类::基类成员。
3. 对于成员函数,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。
子类对象调用构造函数时,继承自基类的成员先自动调用基类的构造函数,对继承成员初始化,之后子类成员再初始化自己的成员:
依旧遵循:对内置类型不做处理,自定义类型调其用自身的拷贝构造
这里可以理解为将继承自基类的成员看作是自定义类型去调用基类构造函数进行初始化
遵循与上述相同的规则,不能在初始化列表中对基类成员进行初识化,要显示调用:
目的在于,将基类成员传递给基类的拷贝构造进行构造,如何传递对应部分:
注意:
在显示调用重载函数部分,若不显示调用,会有隐藏现象,造成函数递归,栈溢出,因此要格外注意
父类和子类的析构函数默认构成隐藏:
想要显示调用,可以在析构函数前指定作用域:
原因:
为了保证析构顺序,先子后父
子类析构函数完成后会自动调用父类析构函数,不用我们主动去调用
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
基类中定义的static静态成员,在整个继承体系中仅此一个,派生类的基类这个静态成员:
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况
菱形继承有数据冗余和二义性的问题。在school的对象中person成员会有两份:
解决数据冗余和二义性的问题,可以使用虚拟继承:
在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。虚拟继承不要在其他地方使用。
数据冗余很容易理解,因为相同数据存储了两份:
存储两份占用两个不同空间,造成二义性;
如果存储的数据过大,无疑菱形继承的问题会被成倍放大,那么虚拟继承是如何消除二义性和数据冗余的,来康康虚拟继承下的内存:
由上图我们可以看到,与正常的继承相比,虚拟继承将 _a 放在了同一块空间,不在B和C里面,这应当是减少了内存的,但是从图中看出虚拟继承还多出来了两个地址,令内存看起来还比原来大了些:
指针存储的偏移量上面有什么作用:
当有b=d、c=d,子类对象赋给基类,会发生切片;或者直接定义B、C对象后,通过对象改变_a时,都要靠指针位置存储的偏移量去找到继承下来的_a 的位置。
在虚拟继承中,基类A有了新的名字叫做虚基类,上图存储两个指针的内存表叫做虚基表,这两个找A的指针叫做虚基表指针。