目录
一、继承的概念和定义
1.继承的概念
2.继承的定义
2.1继承的格式
class 子类的名称? : 继承权限 父类名称{?? ?//子类新增加的成员};
2.2继承关系和访问限定符
??2.3继承基类访问权限的变化
总结:
二、基类和派生类对象的赋值转换
一定是在public的继承方式下才满足下面的规则:
三、继承中的作用域
四、派生类的默认成员函数
五、继承与友元
六、继承与静态成员
七、复杂的菱形继承及菱形虚拟继承
1.C++中不同的继承体系
2.菱形继承
一、继承的概念和定义
1.继承的概念
面试官:介绍下你所认识的继承?
答:继承的概念:继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类型的基础之上进行扩展,增加功能,这样产生的新类,称作派生类。继承呈现了面向对象设计的层次结构,体现了由简单到复杂的认知过程,以前我们接触的复用都是函数复用,继承是类设计层次的复用。
举例:例如动物就是一个类,而狗就是建立在动物这个类的基础之上创建的类,它继承了属于动物所共有的特点,又有自己所独有的特点,狗和动物就属于继承的关系,动物就是基类(父类),而狗就是派生类(子类)。
2.继承的定义
2.1继承的格式
要想创建子类,则必须先要有一个基类(父类)
class 子类的名称? : 继承权限 父类名称 { ?? ?//子类新增加的成员 };
2.2继承关系和访问限定符
?2.3继承基类访问权限的变化

总结:
- 基类的私有成员(private)无论以什么权限被继承到子类中,在子类中和类外都是不可以访问的,但是确实是被集成到了子类当中的。
- 如果需要基类的成员被继承之后在子类中可以访问但是在类外不能进行访问,则将其在基类中的类型给为protected,这就是protected限定符出现的原因。
- 基类的私有成员在子类都是不可见的。基类的其他成员在子类的访问方式==Min(成员在基类的访问限定符,继承方式),public>protected>private.
- 使用关键字class 时默认的继承方式时private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 在实际的运用中一般都是使用public继承,另外两种很少使用。?
二、基类和派生类对象的赋值转换
一定是在public的继承方式下才满足下面的规则:
1.可以直接用子类对象给基类对象赋值,但是反过来不可以(不能使用基类对象给子类对象赋值)
原因:1.public继承方式,子类和基类是is-a的关系,可以将子类对象看作是一个基类对象(狗一定是动物,动物不一定是狗)
? ? ? ? ? ?2.对象模型上来谈:对象中成员变量在内存中的布局形式上将其成为对象模型。


2.可以使用基类的指针指向的子类的对象,但是反过来不可以(不能直接使用子类的指针指向基类的对象)
??
3.可以使用基类的引用去引用子类的对象,但是反过来不可以(不能使用子类的引用去引用基类的对象)
?引用的底层也是指针,所以存在相同的问题,而且引用还不能强转。
三、继承中的作用域
1.在继承体系中基类和派生类都有独立的作用域
2.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
3.如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4.注意在实际中在继承体系里面最好不要定义同名的成员。
四、派生类的默认成员函数
注意:子类在继承的时候不会继承基类的构造和析构函数
1.如果基类显示定义了构造函数,但是基类的构造函数不是无参的或者全缺省的构造函数,此时子类必须要定义自己的构造函数,并且需要在其子类构造函数初始化列表位置显示调用基类的构造函数,目的:完成从基类中继承下来成员的初始化工作。
2.派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化。(建立在基类有拷贝构造函数的前提下),如果没有拷贝构造函数,则子类的拷贝构造函数可以定义也可以不定义。
3.派生类的operator=(赋值运算符的重载)必须调用基类的operator=完成基类的复制。
4.编译器将子类的析构函数编译完成之后,会在子类析构函数最后一条语句之后插入一条调用基类析构函数的汇编语句。

?5.创建哪个类的对象,编译器就会调用该类的构造函数,析构哪个类的对象,编译器就会调用该类的析构函数。(子类创建的时候会在初始化列表中调用基类的构造函数,所以基类先被创造,但实则是先调用的子类的构造函数)
五、继承与友元
友元关系不能继承,也就是说基类不能访问子类私有和保护成员。(你爸爸的朋友不一定是你的朋友!!)
class Base
{
friend void TestFriend();
public:
Base(int b)
: _b(b)
{}
protected:
int _b;
};
class Derived1 : public Base
{
public:
Derived1(int b, int d)
: Base(b)
, _d(d)
{}
protected:
int _d;
};
void TestFriend()
{
Base b(1);
b._b = 10;
Derived1 d(1, 2);
d._b = 12;//基类的友元可以访问基类的成员
d._d = 13;//不能访问子类的私有和保护成员
}
六、继承与静态成员
基类哪些成员被继承了?
1.成员变量
普通成员变量:基类所有的普通成员变量都被子类继承了
静态成员变量:被继承了(多个子类中拥有的是同一份静态成员变量,在整个继承体系中,静态成员只有一份)
2.成员函数
普通成员函数:基类所有的成员函数都被子类继承了(构造和析构除外)
静态成员函数:被继承了
默认成员函数:构造 拷贝构造 赋值运算符重载 析构函数(说法不一)
继承了,理由:在子类的中可以显示调用
没继承,理由:在类外无法调用
七、复杂的菱形继承及菱形虚拟继承
1.C++中不同的继承体系
单继承:一个子类只有一个父类
多继承:一个子类有用多个父亲

?基类部分在上,子类部分在下,多个基类部分在子类对象中的次序和继承列表中基类出现的先后次序一致
注意:继承列表中每个基类前必须要加继承权限,否则就是默认的继承权限。
2.菱形继承
面试中高频出现的问题:
什么是菱形继承?菱形继承存在什么问题?如何解决?
菱形继承:单继承+多继承

?零星继承存在的问题:二义性问题+数据冗余
class B
{
public:
void f()
{}
public:
int _b;
};
class C1 : public B
{
public:
int _c1;
};
class C2 : public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
// 菱形继承二义性问题
d._b = 1;
d.f();
return 0;
}
//报错:错误 1 error C2385: 对“_b”的访问不明确
// 错误 2 error C2385: 对“f”的访问不明确
对象模型:
?
?最顶层基类B中成员在最下面D类中存在两份:一份是从C1中继承下来的,一份是从C2中继承下来的就导致:D类中将最顶层B中的成员出现了两份,如果直接通过D的对象,去访问最顶层基类中成员时,会出现访问不明确。
解决方法:
方式一:让访问明确化(加类名和作用域限定符的方式)
?
方式一并没有从根本上解决问题,只是让编译器知道访问哪个基类中的成员,最顶层基类中的成员在D中还是存在两份,比较浪费空间而且二义性问题本质还存在。

?方式二:采用菱形虚拟继承,让最顶层基类中成员在D中只存在一份(解决数据冗余和根本上解决二义性)
先了解虚拟继承
虚拟继承和单继承区别?
1.对象模型是倒立的
2.对象多了4个字节
3.如果D没有显示定义构造函数,则编译器一定会生成,如果D定义了构造函数,则编译器一定会对构造函数进行修改
生成||修改的目的:往对象的前4个字节中填充数据。

虚拟继承真正的作用是用在菱形继承中解决菱形继承中存在的二义性问题
菱形虚拟继承解决菱形继承中二义性问题:让最顶层B类中的成员在D中只存储一份。
菱形继承代码+内存示意图:
class B
{
public:
int _b;
};
class C1 : virtual public B
{
public:
int _c1;
};
class C2 : virtual public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
return 0;
}

|