继承
1.释义
- 继承是两个类之间进行的一种操作;
- 由字面意思可知继承就是一个类获得另一个类的所有数据,可以类比fork()创建子进程;
- 被继承的类称为父类或者基类,继承的类称为子类或者派生类;
- 继承的主要目的是实现代码复用。
2.继承的三种形式
公有继承(public),保护继承(protected),私有继承(private)
- 父类的私有成员无论子类以何种方式继承在类内外都不能访问;
- 父类的保护成员只能在子类访问,因此保护成员限定符是因继承才出现的;
- 父类的公有成员在类外访问,子类的继承方式须是公有继承;
- 所以一般情况下使用公有继承扩展性更强,但私有继承保护了父类的接口,使封装性更好。
3.继承和组合
- public继承是一种is-a的关系。也就是说每个子类对象都是一个基类对象;
- 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象;
- 继承使得耦合性增高;移植效果不好,有时你只需要一棵树,最后发现你获得的是整片森林;
- 组合耦合性低;功能模块化,移植效果好;
- 所以优先使用对象组合,而不是类继承 。
4.基类和派生类的赋值转换
- 子类对象可以赋值给父类的对象、父类的指针、父类的引用;
- 父类的指针可以通过强制类型转换赋值给子类的指针。但是必须是子类的指针是指向父类对象时才是安全的。父类对象强转成子类指针也可以,但会发生越界访问的问题。
5.继承中的作用域
- 子类和父类分别拥有独立的作用域;
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义;
- 子类和父类的同名成员函数可以通过作用域限定符来指定调用函数,不加限定符则总是调用子类的同名成员函数;
- 子类成员函数只要和父类成员函数只要同名就会构成隐藏,与参数无关。
6.子类的默认成员函数
- 子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函 数,则必须在子类构造函数的初始化列表阶段显示调用;
- 子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化;
- 子类的operator=必须要调用父类的operator=完成父类的复制;
- 子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序;
- 子类对象初始化先调用父类构造再调派生类构造;
- 子类对象析构清理先调用派生类析构再调父类的析构。
7.友元、静态成员变量
- 友元关系不能继承,也就是说父类友元不能访问子类私有和保护成员 ;
- 父类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一 个static成员实例。
8.菱形继承与菱形虚拟继承
- 单继承:一个子类只有一个直接父类时称这个继承关系为单继承
- 多继承:一个子类有多个直接父类时称这个继承关系为多继承
菱形继承如下:
学生为父类,小学生和中学生分别继承学生这个父类,而大学生又分别继承小学生和中学生,此时大学生保存了两份学生类的数据,造成数据的冗余及二义性,解决这个问题的办法就是菱形虚拟继承。虚拟继承不能随意使用。
代码:
#include<iostream>
#include<string>
using namespace std;
class student
{
public:
string _name;
};
class babyStudent : public student
{
protected:
string number;
};
class middleStudent : public student
{
protected:
string _subject;
};
class collegeStudent : public babyStudent, middleStudent
{
protected:
string _major;
};
int main()
{
collegeStudent su;
su._name = "yhx";//赋值对象不明确
return 0;
}
菱形虚拟继承使用指针和虚基表和解决这个问题,指针指向虚基表,虚基表存放的偏移量表示公共基类中成员变量相对于派生类中成员变量的偏移;也就是指针指向虚基表,虚基表存放偏移量,从而找到公共基类的成员变量,公共成员变量位于派生类对象存储空间的底部。
注意虚基表和虚表的不同。