面向对象的编程有三大特点:
本节来学习继承。
之前定义过一个Person类如下:
- class Person {
- private:
- static int cnt;
- char *name;
- int age;
-
- public:
- ...
- };
如果现在要再定义一个Student类,Student也是Person,难道之前在Person类中定义过的成员在Student类中都要再定义一次吗?
答:不需要,可以使用继承。Student类继承Person类,这样Student类就会有Person类的所有属性。
Student类定义如下:
- class Student : public Person {
- };
在Student类中,我们没有额外定义成员,在main函数中,测试一下Student类是否可以使用Person类的成员。

测试结果如下:

可以看到,Student类使用了Person类的成员,也就是说,Student类成功的继承了Person类的成员定义。
为了讲述继承的控制关系,我们重新创建了一个father_son.cpp。
在father_son.cpp中,我们重新创建了一个Father类,然后创建了一个继承Father类的Son类,代码如下:

然后写出main函数,先测试一下继承是否有问题。

测试结果如下,可以看到,继承成功了。

接下来需要测试一下继承的权限,主要有以下几点:
依次测试。
在派生类中直接操作基类的Private成员,代码如下:

编译报错,money是Father类的私有成员,派生类Son无法直接访问。

派生类无法直接访问基类的私有成员,但是可以通过基类的protected/public成员函数间接访问基类的私有成员。

测试结果如下:

如果将get_money和set_money都定义为public类型,那么Father的钱就很难保护了,但是设置为Private类的话,Son又获取不了了。
这时候,可以将它们设置为protected类型,这样派生类Son可以访问,但是其他代码就不可以了。

这时候,在main函数中调用set_money和get_money就会报错。


但是,在派生类Son中调用仍然是正常的。
将main函数中set_money和get_money的调用删掉。

在Son类中调用。

编译代码就没有问题了,测试结果也符合预期。

总结一下类的成员类型:
有时候,基类的成员类型并不满足派生类的需要,那么可能就需要修改基类的成员类型。
在Father类中添加一个protected类型的成员room_key,此时在派生类son中可以自由使用room_key,但是在别的函数体中不能自由使用。

那么,是否有办法让其他函数体也能自由使用呢?
答:有,在Son类中将room_key成员变为public类型就可以了。
代码如下,在public类型中使用using Father::room_key,此时再编译就没有报错了。



既然可以将room_key设置为public,那么是否可以将room_key设置为private?
调整代码如下:

此时编译会报错,认为这是一个protected类型的变量。
派生类可以调整基类protected类型变量的类型,但是不可以调整基类private类型变量的类型,因为基类protected类型的变量对派生类来说是可见的,但是private类型的变量则不可见。

报错,因为不能调整基类private类型变量的类型。

总结一下,就是派生类可以使用using调整它自己可见的基类成员的类型,但是对于它不可见的基类成员,是不能修改的。
接下来看一下,派生类继承的控制关系。
基类成员在派生类中的访问控制属性如下:
| 继承类型\基类访问属性 | public | protected | private |
| public | public | protected | 隔离 |
| protected | protected | protected | 隔离 |
| private | private | private | 隔离 |
表格中,第一列是继承的类型,第一行是基类的访问属性。
可以看到,public方式继承的话,是不会改变基类成员的控制属性的。而无论使用哪种继承方式,private类型的基类成员,都是处于隔离状态的。
但是,无论使用哪种继承方式,在派生类内部使用父类时无差别;不同的继承方式,会影响的是这两个方面:
修改代码,分别使用三种类型继承。

他们继承的基类Father类定义如下。

修改代码,在main函数中创建三个对象,然后分别调用基类Father的public类型成员——it_skill函数。

根据表格的描述,只有public方式继承的Son_pub可以在类外调用,其余的都会报错。
编译结果如下,符合预期。

但是如果不在main函数中调用,只在派生类内部调用,那么就不会有问题。
修改代码,在main函数中调用Son的play_game函数,在play_game函数中,会调用基类的protected类型成员(派生类内部调用)。

编译测试,没有问题,结果也符合预期。

也就是说,无论使用哪种继承方式,在派生类内部使用父类时无差别。
在基类中有一个it_skill函数,如果在派生类中也有一个同名函数,那么会发生什么事情呢?
在Son_pub中创建一个it_skill函数,然后在main函数调用it_skill函数。


编译测试,可以看到调用的是派生类的it_skill函数,而不是父类的it_skill函数,也就是发生了覆写。
如果派生类没有it_skill函数,才会调用基类的it_skill函数。
假设有一个基类Person,一个派生类Student。


那么,他们的成员的空间分布如下:

基类Person类有三个成员,派生类Student除了有继承自基类的三个成员外,还有自己定义的grade。
同时,在派生类中,还有一个和基类同名的函数printfInfo。
基类的printfIndo函数如下:

再写一个测试函数test_func,传入一个Person类的引用,函数中只是调用了一下对应的printfInfo函数。

main函数如下。

由于发生了覆写,所以s.printfInfo其实调用的是派生类自己的printfInfo函数而不是父类的。
需要注意的是,test_func函数传入的是Person类的引用,如果传入的是Person类的派生类Student,那么是否可以正常运行?
答:是可以的。根据前面的空间分布分析,Student类的分布,一部分是继承自基类,一部分则是来源于自己。
当调用test_func(s)时,传入的部分是属于基类的那部分,所以在test_func函数中调用的printfInfo函数,是基类的printfInfo函数,而不是派生类覆写的printfInfo函数。
只有通过s.printfInfo调用的,才是覆写的派生类自己的printfInfo函数。
测试结果如下:
