目录
前言
大家好,这一篇博文主要是详细介绍c++中继承的概念,其中在菱形继承之前的内容都是比较重点的,关于菱形虚继承部分的原理其实有些东西比较复杂,可以简单了解一下,不用过多纠结。实际当中也不咋用,希望大家多多点赞收藏评论!
继承的概念及定义
首先,面向对象编程的三大特性是封装、继承、多态,而今天我们要学习的就是继承
继承是代码复用的一种手段,为什么这么说呢?先看看下面这个案例
有一天学校给张三布置了一个作业,就是完成一个图书管理系统,而在写的过程中,张三先考虑的是图书管理系统中存储的对象,这些对象有学生、有教师、有保安等。于是张三把这些类都实现出来了,当实现出来了以后,张三仔细看了一下代码,这些实现出来的类都有一些共同点,例如名字这些类全部都有,再比如性别、年龄、电话号码等。那么考虑到代码的复用,就去想有没有什么办法解决上述的问题,把这些类共有的数据抽取出来,再把一些类特有的数据分别实现?
而继承就完美解决了上述的问题!
如下:
#include <iostream>
class Person
{
public:
void Print()
{
std::cout << "Person" << std::endl;
}
protected:
//年龄、性别、电话号码都是老师和同学共有的
int _age=0;
int _sex=0;
int _number=0;
//....
};
class Teacher : public Person //Teacher 以公有的方式继承了 Person
{
public:
void TeaPrint()
{
std::cout << "age of teacher is " << _age << std::endl;
}
protected:
size_t _jobid;
};
class Student : public Person //Student 以公有的方式继承了 Person
{
public:
void StuPrint()
{
std::cout << "age of student is " << _age << std::endl;
}
protected:
size_t _stuid;
};
int main()
{
Teacher t;
t.Print();//输出Person
Student s;
s.Print();//输出Person
t.TeaPrint();//输出age of teacher is 0
s.StuPrint();//输出age of student is 0
return 0;
}
代码解析:上述代码把学生和老师类中的共有数据,例如年龄、名字等信息以及成员函数抽离出来变成一个新的类Person,此时当学生和老师继承这个Person类时,就可以访问这个Person类的成员以及方法
其中,我们把这个抽离出来的Person类叫做父类(也可以叫基类)
我们把继承父类(基类)的类称为子类(也可以叫派生类)
类名旁边的public我们称为继承方式,public是继承方式的一种,还有其他的继承方式后面说
继承方式与访问限定符
在c++中有3种继承方式,分别是public继承、protected继承、private继承,如下表
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
对于上述表,我总结出以下规律:
1.基类的private成员不管什么继承方式都不可见,不可见的意思是虽然这个成员被继承下来了,但是无法通过派生类自己来访问,只能间接通过基类的方法进行访问。
就好比张三的爸爸藏了有私房钱,当张三去问他爸爸拿钱的时候,他爸说没有,实际上钱并不是不存在,而是张三没有权利访问。
2.我们认为在访问权限上,public >protected >private,那么我就能得出以下结论:派生类的成员在子类的访问方式就是成员在基类中的访问限定符和继承方式之间的权限较小值。
例如:基类中的public成员被protected继承方式继承下来,那么取它们中的权限较小值就是protected,那么继承下来以后在派生类中就是protected成员。再比如protected成员被public继承方式继承下来,那么取它们中的权限较小值就是protected,那么继承下来以后在派生类中就是protected成员
补充:继承方式可以不写,若用class定义的类,默认继承方式是private,若用struct定义的类,默认继承方式是public
并且我们一般不用protected继承和private继承,在接下来的内容中也用的是public继承
基类和派生类对象赋值转换
派生类可以赋值给基类对象/引用/指针
它的赋值规则如下
在上图中,Person是一个基类,而Student是一个派生类,当派生类赋值给基类时会把派生类中继承下来的基类成员赋值给这个基类,这个过程我们一般通俗的叫做切割或者切片,也叫做对象的赋值兼容转换
需要注意的是,这里的赋值比较特殊,以前我们没有接触继承时的不同类型之间赋值是通过中间产生临时变量,临时变量赋值的方式完成赋值的,但基类与派生类的赋值之间不存在临时变量,这是一个特殊规定
为了证明上述特殊规定,如下代码:
class Person
{
public:
int _age = 20;
//...
};
class Student : public Person
{
public:
int _stuid = 10;
};
int main()
{
//非继承赋值
int a = 0;
double d1 = a;//正常运行
//double& d2 = a;//编译错误
const double& d3 = a;//正常运行
Student s;
Person& p = s;//正常运行
return 0;
}
代码解析:
上述代码中的double d1 = a,实际上并不是直接把a赋值给d1的,而是在赋值的过程中用a的值生成了一个临时变量,由于临时变量具有常性,这个临时变量是const double类型
而对于double& d2 = a,也是在赋值的过程中用a的值生成了一个临时变量,这个临时变量也是const double类型,但double类型不能作为const double类型的引用(权限放大),故编译报错
同理,const double&与临时变量类型是匹配的,故正常运行
通过如上叙述,得出结论,在非继承下不同类型的赋值确实会在赋值的过程中生成临时变量
而对于派生类Student与基类Person,·p是可以作为s的引用的,如果中间生成了临时变量ÿ