一、首先来了解一下继承的基本概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持
原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。
继承可以在原有的基础上进行增添,不至于每一次使用都要重新写一遍,避免了不必要的麻烦。
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
cout << "B()" << endl;
}
~Base()
{
cout << "~B()" << endl;
}
void ShowBase()
{
cout << "_pri =" << _pri << endl;
cout << "_pro =" << _pro << endl;
cout << "_pub =" << _pub << endl;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived :public Base
{
public:
Derived()
{
cout << "D()" << endl;
}
~Derived()
{
cout << "~D()" << endl;
}
void ShowDerived()
{
cout << "_d_pri" << _d_pri << endl;
cout << "_d_pri" << _d_pri << endl;
cout << "_d_pub" << _d_pub << endl;
}
private:
int _d_pri;
protected:
int _d_pro;
public:
int _d_pub;
};
int main()
{
Base b;
Derived d;
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
system("pause");
return 0;
}
运行结果为:
在以上代码中Base是基类,Derived是继承Base产生的派生类,继承定义格式是:
class DeriveClassName(派生类):acess-label(继承类型{public,protected,private})BaseClassName(基类名称)
在主函数中,我分别创了一个基类的对象和一个派生类的对象。并将两个类的大小进行计算,由此可以看出继承关系中构造函数的调用顺序:
基类构造函数(按照继承列表中的顺序调用)->派生类中对象构造函数(按照在派生类中成员对象声明顺序调用)->派生类构造函数体
继承方式一共三种:
公有继承,保护继承,私有继承
三种继承方式中成员的变化:
继承方式 |
基类的公有成员 |
基类的保护成员 |
基类的私有成员 |
关系变化概括 |
public继承 |
仍为公有成员 |
仍为保护成员 |
不可见 |
基类的非私有成员在子类的访问属性都不变 |
protected继承 |
变为保护成员 |
变为保护成员 |
不可见 |
基类的非私有成员都成为子类的保护成员 |
private继承 |
变为私有成员 |
变为私有成员 |
不可见 |
基类的非私有成员都变成子类的私有成员 |
2.public继承是一个接口继承,保持is-a原则,每个父类可用的成员子类也可以用,因为每个子类对象也都是一个父类对象。
3.protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是has-a的关系,所以非特殊情况下不会使用这两种继承方式,绝大多数情况都是用公有继承。私有继承以为这(是根据什么实现的)。当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的。
4.不管哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
5.最好写出继承方式,class默认private,struct默认public。
6.一般情况下都使用公有继承。
在派生类中如果没有定义这六个成员函数编译系统会默认合成:
构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载、用const修饰的取地址操作符重载
说明:
1、基类没有缺省构造函数,派生类必须在初始化列表中显式给出基类名和参数列表。
2、基类没有定义构造函数,派生类也可不定义,全部使用缺省构造函数。
3、基类定义了带有形参表构造函数,派生类就一定定义构造函数。
[继承关系中析构函数调用过程]
派生类析构函数->派生类包含成员对象析构函数(调用顺序和成员对象在类中声明的顺序相反)->基类析构函数(调用顺序和基类在派生列表中声明顺序相反)
多继承定义两个基类Base1,Base2,和派生类Derive
则派生类的定义格式为:
class Derive:public Base1,public Base2
{};
继承体系中的作用域:
1.继承体系中基类和派生类是两个不同的作用域
2.子类和父类中有同名成员,子类成员将屏蔽父类成员的直接访问。(在子类成员函数中,可以使用
基类::基类成员 访问)--隐藏--重定义
3.注意在实际中在继承体系中最好不要定义同名的成员
#include<iostream>
using namespace std;
class Person
{
public:
Person(const char* name = "", int id = 0)
:_name(name), _num(id)
{
}
private:
string _name;//姓名
int _num;//身份证号
};
class Student:public Person
{
public:
Student(const char * name, int id, int stuNum)
:Person(name, id),
_num(stuNum)
{}
void DisplayNum()
{
cout << "身份证号:" << Person::_num << endl;
cout << "学号" << _num << endl;
}
protected:
int _num;
};
int main()
{
system("pause");
return 0;
}
如图所示,该程序无法通过编译,基类和派生类有同名成员,基类不可访问被子类屏蔽。
[继承与转换]
--赋值兼容性规则--public继承
1.子类对象可以赋值给父类对象
2.父类对象不能赋值给子类对象
3.父类的指针/引用可以指向子类成员
4.子类的指针/引用不能指向父类对象(可以通过强制类型转换实现)
[友元与继承]
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
#include<iostream>
using namespace std;
class Person
{
friend void Display(Person &p, Student &s);
protected:
string _name;//姓名
};
class Student :public Person
{
protected:
int _stuNum;//学号
};
void Display(Person &p, Student &s)
{
cout << p._name << endl;
cout << s._name << endl;
cout << p._stuNum << endl;
}
void TestPerson1()
{
Person p;
Student s;
Display(p,s);
}
int main()
{
TestPerson1();
system("pause");
return 0;
}
之后出现错误,不能访问保护和私有成员。
[继承与静态成员]
基类定义了static成员,则整个继承体系中只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
[单继承&多继承&菱形继承]
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个及以上直接父类时称这个继承关系为多继承
菱形继承:
如图所示:
菱形继承对象模型
D的对象中有两份A的成员,菱形继承存在二义性和数据冗余的问题
为了解决这个问题,提出了虚拟继承的方式
1.虚继承解决了在菱形继承体系中子类对象包含多份父类对象的数据冗余和浪费空间的问题。
2.虚继承体系看起来很复杂,在实际应用中我们通常不会定义如此复杂的继承体系,一般不到万不得已都不要定义菱形结构的虚拟继承体系结构,因为使用虚拟继承解决数据冗余问题也带来了性能上的损耗。
具体操作就是在两个单继承的继承方式public前加上关键字virtual