概念
概念: 什么是继承呢? 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
本质:形象一点子承父业 ,继承家业。那么C++中的继承如何定义的呢?同时这里说一下:在C++中我们管父类称为:基类 ,子类称为:派生类 来看以下代码:
#基类
class person
{
public:
void Print()
{
cout << _name << " " << _age << endl;;
}
string _name = "peter";
int _age = 18;
};
//派生类
class Student : public person // Student 继承 我们的 person 类
{
protected:
int _stuid = 1024;
};
语法格式如下:
class
+子类名(派生类)
+:(冒号)
+继承方式
+父类名(基类)
继承方式我们稍后来说,先来看一下我们Student(学生类)
是否继承了我们的 person类
呢?
可以看到,student
类成功继承了 person
类。
继承方式
再来谈一下继承方式。我们学类和对象的时候,知道在我们类中会有三种访问限定符:public
,protected
,private
。那么对应的继承方式也有三种:public
,protected
,private
。
看如下图表:
子类中的public 继承方式 | 子类中的protected 继承方式 | 子类中的private 继承方式 | |
---|---|---|---|
父类中的public 成员 | 在子类的访问权限为public | 在子类的访问权限为protected | 在子类的访问权限为private |
父类中的protected 成员 | 在子类的访问权限为protected | -在子类的访问权限为protected | 子在子类的访问权限为private |
父类中的private 成员 | 子类不可用 | 子类不可用 | 子类不可用 |
我们父类中的private
成员在子类中都是不可用的,这里只是访问不了(也继承了下来),而其他类型的父类成员在子类中的访问权限 ==
M
i
n
(
父类的成员权限,子类的继承方式
)
Min(父类的成员权限,子类的继承方式)
Min(父类的成员权限,子类的继承方式);。
那能否不写继承方式呢? 如果我们类定义的为 class
那么默认继承方式为 private
,如果是 struct
默认继承方式为 public
。
class person
{
public:
void Print()
{
cout << _name << " " << _age << endl;
}
protected:
string _name = "peter";
int _age = 18;
};
//派生类
class student : public person
{
public:
void fun()
{
cout << _name << " " << _age << endl;
}
protected:
int _stuid = 1024;
};
这里我们先来看一下这个 protected
访问权限,之前我们都是把他看做跟private
一样,但是在继承中不是,我们private
是在类外和子类中都不能访问,而如果我们想要在类外中不能访问而在子类中可以访问 那我们可以把成员定义在 protected
访问符之下。
通过 protected
我们在子类中可以访问父类的成员。
赋值兼容拷贝
int main()
{
int a = 0;
char i = 'x';
i = a; //产生临时变量赋值给 i
student b;
b._name = "张三";
person p;
p = b; //无临时变量产生
return 0;
}
在我们把一个整形的值赋值给字符类型的时候发生截断,而截断的值我们会存储到一个临时变量中,然后才赋值给i。如果我们想要把一个子类赋值给父类呢?
结论: 不会产生临时变量,引用和指针和对象都可以。这是规定。看如下图方便理解:
隐藏
在继承体系中,我们派生类使用变量或者函数的时候查找规则如下:
- 派生类
- 父类
- 全局
所以当我们使用变量或者函数的时候,如果子类当中有,就不会去父类当中查找。
如上图,当我们使用子类调用
f
u
n
(
)
fun()
fun()函数的时候,由于子类中存在,所以不回去调用父类的,这种情况我们叫做隐藏
。
同时隐藏只要求函数名相同,即使我们参数不同和返回值不同也可以。
多继承
菱形继承
我们一个子类可以继承多个父类。如上图,我们西红柿即是蔬菜也是水果,很合理。那么再看下图。
如上图的关系图,老师和学生都有人的属性没问题,助教即使老师也是学生,这么一看似乎很合理,那么我们再来看代码层面上:
class Person
{
public:
string _name; //姓名
int _tel; //电话
};
class Teacher : public Person
{
public:
int _TID = 1; //教师编号
};
class Student : public Person
{
public:
int _STID = 2; //学生学号
};
class Assistant : public Teacher, public Student
{
public:
void print()
{
cout << "姓名" << _name <<
" 电话" << _tel <<
"教师编号" << _TID <<
"学生学号" << _STID << endl;
}
};
int main()
{
Assistant assist;
assist.print();
return 0;
}
如上图,当我们为Assistant(助教)
的时候,如果我们要访问我们的名字和电话,我们对象里面有两份,那我们访问哪一个呢?所以这里会有二义性
和数据冗余
。直接报错。那么如何解决呢?
菱形虚拟继承
继承中的默认成员函数
这里我们主要讨论一下四种默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值重载
默认构造函数
当我们呢有默认构造函数的时候,我们子类构造的时候会默认去调用父类的构造,这里可以看作每一个子类都会在最开始的部分声明一个父类对象,所以每一次都要先去构造父类。
那如果父类没有默认构造呢?
如上图,当我们父类没有默认构造的时候,我们需要手动调用父类的构造函数,并且规定必须在初始化列表调用。
拷贝构造和复制重载
这里有几个注意事项:
- 对于拷贝构造和复制重载我们都需要先调用父类对应的成员函数
- 子类中调用父类的拷贝构造使用初始化列表的方式调用父类构造,同时直接传入子类的引用即可(赋值兼容拷贝)。
析构函数
如上图,我们发现当我们在派生类中不可以调用父类的析构函数,这是为什么呢?
由于多态的原因,编译器会把析构函数名都看为destructor(),所以此时父类和子类的析构构成隐藏,所以如果想要调用父类的析构需要指定类域
如上图,但是此时我们发现当我们指定类域调用父类的析构之后这很明显不对啊?
而当我们不显示调用父类的析构的时候反而对了??
如上图,因为继承体系中,子类析构不让我们显示调用父类的析构,由于父类先构造,子类后构造,编译器会在子类析构之后默认调用父类的析构。
继承与友元
友元无法继承。
继承与静态成员
无论定义有多少个派生类,静态成员只有一份。