文章目录
前言
一、继承的目的
简单来说“继承”的目的就是复用代码;
它允许程序员在保持原有类特性的基础上进行扩展,增加功能,(这样产生新的类,称派生类),是类设计层次的复用
下面我们用一段代码举一个简单的例子:
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
class Data1
{
public:
Data1(int a1,int a2)
:_a1(a1)
,_a2(a2)
{}
void Get_Message()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1;
int _a2;
};
class Data2 : public Data1
{
public:
Data2(int a1, int a2, int a3)
:Data1(a1, a2)
, _a3(a3)
{}
private:
int _a3;
};
int main()
{
Data2 m(1, 2, 3);
m.Get_Message();
return 0;
}
运行结果:
这就是一个派生类Data2复用基类Data1的函数==Get_Message()==的一个例子
二、使用方法
1.概念
还是拿我们举例用的代码分析:
class Data2 : public Data1
其中Data2叫做派生类,Data1称为基类,public称为继承方式,而继承方式主要分为3种,它们的特点可以参考下面的表格:
不可见是指基类的private成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
话是这么说,但是派生类还是可以调用基类的函数来访问基类的private成员,我们在文章开头举例用的代码就是这样一个例子
所以如果你打算让自己的类在被继承以后使用得更方便些,也就是达到“基类成员不能在类外访问,但可以在派生类中访问”的效果时,建议把这些成员定义为protect
但是大多数情况下我们使用的都是public继承,从实用角度考虑其他问题不需要深究
2.派生类的构造函数,拷贝构造函数和“operator=”
我们先从构造函数说起,它的规则是:
派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
这里请允许我偷个懒再次用第一段举例代码讲解(足以说明这段代码的含金量)
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
class Data1
{
public:
Data1(int a1,int a2)
:_a1(a1)
,_a2(a2)
{}
void Get_Message()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1;
int _a2;
};
class Data2 : public Data1
{
public:
Data2(int a1, int a2, int a3)//全体目光向我这行看起!看我看我!
:Data1(a1, a2)
, _a3(a3)
{}
private:
int _a3;
};
int main()
{
Data2 m(1, 2, 3);
m.Get_Message();
return 0;
}
我们主要看的就是派生类Data2构造函数的这一部分,其中a1,a2这两个基类的成员我们就是使用了基类的构造函数来初始化的;
由于基类Data1没有写默认的构造函数(即没有参数的构造函数),所以我们这里必须在初始化列表阶段就开始调用
接下来有两个需要注意的点是:
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
派生类的operator=必须要调用基类的operator=完成基类的复制。
可以看到派生类的构造函数,“operator==”和拷贝构造构造函数在实现时时都需要调用基类的对应函数的,这三个函数我们可以放在一起记忆,如果你把上面的代码举例读懂了,剩下的两位也就不是问题了
3.细节
1.基类对象与派生类对象之间的赋值转换
(1)派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。可以理解成把派生类中父类那部分切来赋值过去。
(2)基类对象不能赋值给派生类对象。
(3)基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的
2.重定义/隐藏
子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况就叫隐藏/重定义
在子类成员函数中,我们可以使用 基类::基类成员 显式访问被隐藏的对象,我们可以把这一点理解成基类和派生类都拥有独立的作用域
需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏,虽然我们已经知道“隐藏”的概念了,但是在继承体系里面最好不要定义同名的成员。
3.其他注意事项
使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员
派生类对象初始化先调用基类构造再调派生类构造
三、遗留问题与解决方案
如果要谈继承带来的“数据冗余”和“二义性”问题,就不得不说起菱形继承:
这里C++引入了虚拟继承来解决这个问题,但是virtual这个关键字还有不少可以讲的内容,为了防止产生割裂感,菱形继承以后的部分就放到下一篇博客里了,大家可以期待一下
总结
感谢各位的支持