继承:利用已有的数据类型来定义新的数据类型。
通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。 我们称已存在的用来继承的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
class Person//父类
{};
class Student : public Person//子类继承
{};
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。
继承方式和访问方式都有三种:
所谓的继承方式和访问限定符,只影响父类继承下来的成员,原子类的成员还是按照自己类的访问限定符。
既然不能访问,继承下来有什么意义?
这是因为对于编译器来说,让部分成员不继承下去比让全部成员都继承下去更难,所以干脆就全部成员都继承下去,但是不想让你访问到的,就设置为私有就行了;
也就是说,protected和private对于父类来说,是没有区别的,该成员都是无法被访问;而对于子类来说,protected成员可以访问,private成员不可以访问。
那public对于子类来说又意味着什么?
protected和public对于子类来说没有区别,但是它们的区别是非派生类能否访问到该成员:保护的不可以,公有的可以。
可以比喻成木桶原理:一个木桶能装多少水取决于最短的那根木板。在这里就是说即使在父类中该成员是公有的,但是由于你的继承方式是private或者protected,那么继承下来的也就只能是保护或者私有,相当于最短的那根木板。而不存在私有成员被公有形式继承下来后就变成了公有成员的情况。
使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式;
在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
定义子类和父类:
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};
子类对象可以赋值给父类对象,这个过程会发生切片:
Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
这个很好理解,子类继承了父类,那当然是会比父类多成员,因为子类既有父类继承下来的那部分,又有自己原来的那部分。
我们知道不同类型变量之间的赋值存在隐式类型转换,但是切片的过程并不存在类型转换;就比如传引用,如果存在类型转换,那必定会有临时变量的出现,那么左值必须得是const常量才可以被赋值,但是Person& rp 很明显不是const常量(主页的引用章节有提到这个知识点)。
例如父类有一个公有成员tmp,子类通过私有继承,得到了父类的tmp,对于子类来说是私有的,但是赋值给了父类以后(发生切片),在父类看来tmp还是公有的,那么就会把它当成公有来使用,这当然是不合理的。
父类对象的成员比子类对象少,切片就会越界,所以这种行为是不允许的,即使是强制类型转换为子类,也不允许。
但是这对于指针和引用却是可以的,只是会有越界的风险: 强转后赋值给指针/引用,它们都会认为自己占用了某段空间,但是实际上已经超出了自己的范围,因为它们的实际内存范围是父类的空间范围,即使强转后,父类的内存范围里也没有子类的某些成员,但是它们会以为自己有,那么访问就会导致非法访问。