1.1继承的概念
继承本质上是面向对象程序,使代码可以复用的手段,保持原有类特性的基础上进行扩展,新增的(继承类的)类叫派生类(子类),被继承的类叫基类或者父类。
下图中,person作为一个父类,被student和teacher所继承,所以student的对象s中,既有student类中的_stuid,也有person的_name和_age。teacer类的t同理;
1.2 继承定义
1.2.1定义格式
Person是父类,也称作基类。Student是子类,也称作派生类。
继承方式有:public继承、protected继承、private继承。
在类中有访问限定符:public访问、protected访问、private访问。
用不同方式继承父类,类中不同访问限定符让派生类成员访问产生一些变化。
总的来说:
1、基类的private成员虽然被继承,但却不可见,语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2、基类protected成员不能被类外直接访问,但可以在派生类中直接访问。
3、基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
4、使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过
最好显示的写出继承方式。
2.基类和派生类对象赋值转换
1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
2.基类对象不能赋值给派生类对象。
下程序中,s除了继承的基类成员,还有student本身的成员_stuid,p = s中,s只讲基类的成员值赋值给p,p中并没有_stuid的成员。这个行为就像是切片。
切片
rp是s的引用,类型是person,改动rp,也会改动s,pp是s地址,虽然类型是person,改动pp,依然会改动s.
3.继承中的作用域
1. 在继承体系中基类和派生类都有独立的作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)。
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。
在父类和子类中,都有一个_num成员,定义一个student的对象,打印_num不是父类的_num,而是student中的_num。程序在发现_num后,会先到对象定义的类中寻找,如果没有找到,才会去找继承下来的父类。
也可以使用person::_num直接访问父类的成员,无视隐藏。
函数与成员一样,B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。使用A::fun()直接访问父类的成员函数,无视隐藏。
B中的fun和A中的fun不是构成重载,因为不是在同一作用域
4.派生类的默认成员函数
子类构造函数原则:a.调用父类构造函数初始化继承自父类的成员,自己再初始化自己的成员。
析构、拷贝构造、复制重载也类似。
下图中,张三是子类继承自父类的成员,100 子类自己的成员,初始化时,先调用父类的构造函数初始化,在调用子类的构造函数初始化。析构时,是先调用子类的析构函数,最后调用父类的析构函数。
子类析构函数调用后,会自动调用父类的析构函数,不用显示调用。显示调用反而会在子类析构调用前,调用父类析构,造成重复调用父类的析构函数。
如下图中只初始化一个对象,但却出现了三次person的析构函数。
5.继承与友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
6. 继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 ,子类都能访问那个唯一的静态成员。
7.复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况。
下图中,如果有Assistant a的成员函数,因为继承,student和teacher中各有一份继承自person的成员,而Assistant中,有两份person中的成员,他们分别继承自student和teacher。这时如果要访问Assistant中person里的成员,系统就不知道,是访问student中的还是teacher中的。
可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
通过指定想要访问的类可以解决二义性问题,如a.student::_name; a.teacher::_name;
虚拟继承可以解决数据冗余的问题,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
非虚拟继承,d中有两个_a。
虚拟继承
D的内存存储中少了一个_a,多了两个地址,这两个地址存储的是距离A存储位置的偏移量,通过偏移量可以找到A中成员_a的值。