首先,我们得知道,面向对象的三大语言:封装 继承 多态
今天我们主要谈谈继承
1.什么是继承
——子类(派生类)可以访问和使用父类(基类)的成员
比如:有两个类,A和B,我们在定义时,使得B 可以访问 A 的成员,我们叫做 B继承了A。
B为子类,(派生类);A为父类(基类)
2.为什么要用继承呢 ?
代码分析:
#include<iostream>
using namespace std;
class A
{
public:
void fun1(){}
void fun2(){}
};
class B :public A
{
public:
void fun3(){}
void fun4(){}
};
int main()
{
B b;
b.fun1();
b.fun2();
b.fun3();
b.fun4();
return ;
}
继承是为了提高程序的复用性。
如上述代码,不需要额外定义fun1( )和fun2(),直接从父类继承即可。
3.三种继承关系
- 成员访问限定符&继承关系:

- 三种继承关系下基类成员在派生类的访问关系变化:
- 问题1:为什么已经有了私有成员还要有保护成员?
答:基类的私有成员在派生类中是无法被访问的,但如果一些基类成员,不想被基类对象直接访问,需要在派生类中访问,就定义为保护成员。保护成员限定符是为了继承而生的。
- 问题2:public / protected / private继承之间的区别是什么?
public继承是一个接口继承,保持 is-a 原则,(我是一个你)
每个父类可用的对象对子类也可用,每个子类对象也是一个父类对象。
protected / private 继承是一个实现继承,是 has -a 的关系原则,(我有一个你)
基类的部分成员,并未完全成为子类接口的一部分。
-
问题3:什么是成员不可见?
答:不管是哪种继承方式,在派生类内部都可以访问基类的共有成员和保护成员,基类的私有成员存在,但是在派生类中无法访问。(不可见)
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
4.继承规则
- A B两个类不相关,不能继承,能发生继承关系的两个类必须具有相关性。
- 从逻辑上看,B是A 的一种,可以继承(比如:男人是人的一种,男人可以继承人的特性)
- 从逻辑上看,B是A 的一种,并且 A 的功能和属性对B 有意义,才可以继承。(比如:鸵鸟是鸟的一种,但是鸵鸟不会飞,鸵鸟将这个功能继承过来毫无意义)
5.继承与转换——赋值兼容规则——public继承
- 子类对象可以赋值给父类对象(切割/切片)
- 父类对象不可以赋值给子类对象
- 父类的指针/ 引用可以指向子类对象
- 子类的指针/ 引用不能指向父类对象(可以通过强制类型转换实现)
代码分析:
class Person
{
public:
void Show()
{
cout<<_name << endl;
}
protected:
string _name;
private:
int _age;
};
//class Student :private Person
//class Student :protected Person
class Student :public Person
{
public:int _num;
};
int main()
{
Person p;
Student s;
p = s;//1.子类对象可以直接赋值给父类对象(切割/切片)
//s = p;//2.父类对象不能直接赋值给子类对象(空间不足)
//3.父类的指针/引用,可以指向子类对象
Person *p1 = &s;
Person&r = s;//不加const也能引用,——天然支持
//4.子类的指针/引用不能指向父类的对象(但可以通过强制转换实现)
Student*p2 = (Student*)&p;
Student&r2 = (Student&)p;
//const Student &r2 = p;//编不过:天然不支持
system("pause");
return ;
}
6.继承体系中的作用域:
- 在继承体系中,基类和派生类都有独立的作用域
-
子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。
(在子类成员函数中,可使用基类::基类成员 )访问 ——隐藏(重定义)
- 注意:在实际中最好不要定义同名成员。
7.单继承&&多继承&&菱形继承
- 单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
- 多继承:一个子类有两个或两个以上直接父类称这个继承关系为多继承。
C++——继承(单继承、多继承、菱形继承)&&虚继承&&虚基类 - 菱形继承
C++——继承(单继承、多继承、菱形继承)&&虚继承&&虚基类
菱形继承存在问题:二义性和 数据冗余(虚继承解决)
**注:
1.虚基表中存的是一个偏移量(相对地址,而不是实际地址)**
2.同类型对象共享一个虚基表(偏移量相同)
3.虚基表的使用:节省空间
8.虚继承
代码分析:
//不是虚继承时
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B:public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
class D:public B,public C
{
public :
int _d;
};
int main()
{
D dd;
cout << sizeof(dd) << endl;
dd.B::_a = ;
dd._b= ;
dd.B::_a = ;
dd._c = ;
dd._d = ;
B bb;
C cc;
system("pause");
return ;
}
运行结果:
结果分析:
D中一共只有四个变量,但此时sizeof 的结果为20,也就是说有5个值,那这五个值是从哪来的呢?
——实际上D继承了两个_a, B继承的_a 和 A继承的_a是不一样的,无法确定哪一个值应该是_a,所以系统默认有两个_a。(有歧义)
在监视窗口上看到,程序的执行步骤为:
- B中的_a 变成 1
- D中的_b 变成 3
- C中的_a 变成 2
- D中的_c 变成 4
- D中的_d 变成 5
当继承方式为虚继承时:
class B:virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
当继承方式为虚继承时,程序的执行过程为:
- B中的_a 和 C中的 _a 同时变为1
- D中的_b 变成 3
- B中的_a 和 C中的 _a 同时变为2
- D中的_c 变成 4
- D中的_d 变成 5
注:此时A 叫做虚基类,当子类对父类的继承方式为虚继承时,父类就叫做虚基类。
虚基类的作用:当一个基类被声明为虚基类后,即便它被多次继承,最后的派生类中也只有它的一个备份
参照上述代码,A被声明为虚基类,D 既被B继承,又被 C继承 。但最后的派生类D中只有一个_a
——如此便解决了数据冗余和二义性的问题