为了支持代码的复用,继承与派生在C++中就显得十分重要。
继承与派生:
当定义一个新的类 B 时,如果发现类 B 拥有某个已写好的类 A 的全部特点,此外还有类 A 没有的特点,那么就不必从头重写类 B,而是可以把类 A 作为一个“基类”(也称“父类”),把类 B 写为基类 A 的一个“派生类”(也称“子类”)。这样,就可以说从类 A “派生”出了类 B,也可以说类 B “继承”了类 A。
基类的所有成员都自动称为派生类的成员。派生类可以在其基础上添加和修改
举个栗子:学生类作为基类,成员函数中打印函数可以打印姓名、性别。本科生类作为派生类,成员函数中要多打印一个专业。那么可以在本科生类中写一个与学生类中打印函数同名的函数,先调用学生类中的打印函数,在单独打印专业这一信息。通过派生类对象访问同名成员,除非特别指明,否则访问的就是派生类的成员,这种情况叫“覆盖”,即派生类的成员覆盖基类的同名成员。
派生类对象占用的存储空间大小 = 基类对象占用存储空间大小 + 派生类对象自身成员变量占用存储空间大小。
类与类之间的关系有两种:组合和继承。组合指的是A是B的一部分。继承指的是A是B的一种。
protected:
之前一直使用的是public与private。protected修饰的是保护成员,它的可访问范围介于public与private之间。
设置protected的目的:无论派生类是用何种方式继承的基类,基类中的私有成员在派生类中都是无法访问的。但是派生类的成员函数又确实需要经常访问基类成员。所以我们将它设置为保护成员,既可以起到隐藏的目的,又可是让派生类访问它。(保护成员可以在派生类访问,但不能在其他地方访问。)
如果不写继承方式:最好还是写上!
派生类用struct定义,默认继承方式就是public。派生类用class定义,默认继承方式就是private。
派生类的构造函数和析构函数:
由于派生类中包含基类成员,所以在执行派生类构造函数的时候,都会先执行基类的构造函数(在构造函数的参数列表中,如果不在初始化列表中,写在内部,它的意思就先构造派生类了,肯定是错的)。如果对此不做说明,编译器会调用基类的无参构造函数,如果没有无参构造函数,就会报错。
析构函数的调用依旧满足:先构造的后析构,后构造的先析构的特点。
多层次的派生:
A类 派生 B类,B类 派生 C类。
则 A类 是 B类 的直接基类,B类 是 C类 的直接基类,A类 是 C类 的间接基类。
在 C类 写派生关系的时候,只写直接基类 B类,不写间接基类 A类。
调用构造函数的时候会先构造最上面的基类,之后依次往下。
当组合与派生同时使用:
先调用基类的构造函数,再按照成员对象的定义顺序执行各自的构造函数。
基类与派生类赋值(初始化)规则:
- 派生类对象可以赋值给基类对象。
- 派生类对象可以用来初始化基类引用。
- 派生类的指针可以赋值给基类的指针。
结论:继承结构中,默认支持从下到上的转换,反过来不行的。
理解一下:派生类包含基类,派生类是个大圈里面有个小圈是基类。大的(派生类)给小的(基类)赋值可以,你小的(基类)给大的(派生类)只能赋一部分,剩下的就浪费了所以不合适。 虽然说大的(派生类指针)可以给小的(基类指针)赋值,但不属于小的(基类指针)那一部分,小的(基类指针)也是无法访问的。(也就是说不能通过基类指针访问基类没有而派生类中有的成员。)
还有一点:虽然小的(基类指针)不能赋值给大的(派生类指针),但你要强制类型转换,编译器当然也会认可。但这种情况下需要保证那个基类指针本身就指向一个派生类对象。不然会出错。慎用!!!!
基类与派生类同名成员的访问:
同名成员方法的关系:重载、隐藏、覆盖
重载
在基类或者派生类同一个类作用域当中的,函数名相同,参数列表不同。
隐藏
方法分布在基类和派生类中
只要方法名字相同(无论参数个数或参数类型是否相同),就说派生类的同名方法把基类的同名方法给隐藏了。
覆盖
方法分布在基类和派生类中
基类的方法是虚函数,派生类的方法和基类的虚函数,返回值相同,函数名相同,参数列表也相同。那么就会用派生类的函数覆盖掉基类的虚函数。在虚函数表中进行覆盖!