您的位置:首页 > 编程语言 > C语言/C++

c++ 继承剖析

2016-10-24 23:10 162 查看

c++ 之继承剖析:

大家都知道C++有三大特性 ,它们分别是:封装性 ; 继承性 ;与多态性 。。。。

这篇文章主要向大家主要介绍一下继承在c++中的使用以及各种规定。

本文使用的编译环境是vs2010

一般情况下 、我们称父类为 基类
,子类为派生类

1、继承的概念:

继承是:面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

上面这段话的主要描述的就是以下三点(继承的优势):

1、代码的复用 ;

2、对于类对象的扩展;

3、简单--->复杂;

2、继承的使用:

(1).三种继承的方式

>1、public 公有继承 ;

>2、protected 保护继承 ;

>3、private 私有继承 ;

>继承的定义格式为



当派生类继承基类 的结果就是:

将基类的成员全部继承下来 ,包括 成员变量 还有成员函数。。。

但是在基类中成员的访问权限可能是不同的,

派生类在继承 基类的同时 ,也将这些成员的访问权限继承了下来,但是还是对此作出了一些调整。

基于继承方式的不同,访问的权限也产生了不同



public继承(公有继承):

public继承可以说是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类

对象也都是一个父类对象。

(可以用一句话来说就是 ,可以讲一个派生类对象当成是一个基类对象使用 )

(这个概念在类的赋值兼容规则中会得到成分的应用)

protected、private继承(私有与保护的继承):

protectd/private继承是一个实现继承,是 has-a 的关系原则,基类的部分成员并非完全成为子类接口的一部分(因为会有权限的变化),所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的。

(这个也可以 ,用一句话来判断就是 可以将 基类看成是派生类的一个成员 ,这个概念 和类的子对象有点相似,但还是有一些的区别的 )

(2).继承关系中构造函数与析构函数的调用顺序

【构造函数】

派生类的构造函数 ——> 在派生类的构造函数的初始化列表调用基类的构造函数 ——>派生类的构造函数体

【析构函数】

派生类的析构函数 ——> 派生类所含成员的析构函数 ——> 基类的析构函数

【说明】

1、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。

2、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。

3、基类定义了带有形参的构造函数,派生类就一定定义构造函数。

4、基类有显示定义缺省的基类构造函数,那么编译器 会默认为 派生类生成一个构造函数。

(3).继承过程中的作用域变换

1. 在继承过程中中基类和派生类是两个不同作用域。

2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以

使用 基类::基类成员 访问)--隐藏 --重定义

3. 注意在实际中在继承体系里面最好不要定义同名的成员。

(4).继承过程中派生类结构剖析

派生类的中的内存分布为



派生类在调用构造函数后 要在初始化列表中先调用 基类的构造函数 初始化一个基类对象,然后 在函数 主体中 为派生类自己成员 赋值 。。。。。

所以 ,派生类的内存分布 为 先分配 一个 基类对象 ,之后再为 派生类成员分配空间 。。。

(5).继承衍生的赋值兼容规则

派生类对象可以赋值给基类

基类对象不可以赋值给派生类

基类的指针引用可以指向派生类对象

派生类的指针引用不可以指向基类对象】(可以通过强制类型转换完成)

因为 根据 is -a 规则 可以将 一个派生类看成是 一个基类

根据派生类的内存分布 , ,可以 将 派生类的前一部分看成一个 基类 ,将之赋值给基类对象

但是将一个 基类对象 看成是一个派生类对象 ,内存数据是不完整的。

形象图显示






(6).继承会继承友元关系、还有静态成员???

》》1、友元关系

类的继承性 继承的是类的成员 包括成员变量和成员函数,但是并不包括类的友元关系。。。。

举个例子来说明一下吧!!!!

class Date
{
friend void  fun();
public :
private:
int _year;
int _month;
int _day;
};

class Time :public Date
{

public:
private:
int _hour;
int _minute;
int _second;
};

void fun()
{
Date D;
D._day = 1;
D._month = 1;
D._year = 2016;
Time T;
T._hour = 1;
}


代码的错误报告

error C2248: “Time::_hour”: 无法访问 private 成员(在“Time”类中声明)
这说明Date类中友元函数fun()并没有在派生类Time中得到继承。。。

》》2、静态成员

基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有

一个static成员实例。
一句话说就是 继承的派生类任然可以在堆空间的静态区中访问。

3、单继承&&多继承&&菱形继承




菱形继承的对象模型



菱形继承继承代码:

#pragma once

//基类B
class B
{
public :
B()
:_b(0)
{}
int _b;
};

//中间类 C1 、C2
class  C1 :public B
{
public:
C1()
:_c1(1)
{}

int _c1;
};
class  C2 :public B
{
public:
C2()
:_c2(2)
{}

int _c2;
};

//最终类D
class  D :public C1,public C2
{
public:
D()
:_d(3)
{}

int _d;
};

void Test1()
{
D d;
int a = sizeof(d);
}


在这段代码中

C1 、C2 都继承了 基类B

最终类D 则同时继承继承了 C1、C2类。

生成的监视图:



4、虚拟继承

在上面的菱形继承中,我们看到得到的最终类对象中含有两个 B类对象。

如果这样看起来就显得有点冗余了,造成了二义性。

因此,为了解决这种问题 ,特地引进了虚拟继承这一思想。

下面是一段使用的虚拟继承 解决上面的菱形继承的问题的代码:

#pragma once

//基类B
class B
{
public :
B()
:_b(0)
{}
int _b;
};
//中间类C1、C2虚拟继承了B类
class  C1 :virtual public B
{
public:
C1()
:_c1(1)
{}

int _c1;
};
class  C2 :virtual public B
{
public:
C2()
:_c2(2)
{}

int _c2;
};

//最终类D继承C1、C2类
class  D :public C1,public C2
{
public:
D()
:_d(3)
{}

int _d;
};

void Test2()
{
D dd;
B bb;
C1 cc1;
C2 cc2;
int b = sizeof(bb);
int c1 = sizeof(cc1);
int c2 = sizeof(cc2);
int d = sizeof(dd);
}
这段代码与上面的代码的不同之处在于

class  C1 :virtual public B
class  C2 :virtual public B

代码生成 查看监视



看了这个图之后,和上面的没有什么区别????,,难道代码错误了吗???

同学们不要使用表面来 理解内部构造。。。

看到下面的内存中的值,大家都懂了。。



但是到底有什么变化呢???

我们在这向大家来细细诉说:

虚拟继承实现过程

虚拟继承,相对于一般的继承的不同点在于 :
【执行顺序不同】

首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承的顺序构造;

执行基类的构造函数,多个基类的构造函数按照被继承的顺序构造;

执行成员对象的构造函数,多个成员对象的构造函数按照申明的顺序构造;

执行派生类自己的构造函数;

析构以与构造相反的顺序执行;

所谓的虚基类,就是说的是 虚拟继承了基类的类。。。。

虚基类的构造函数在开始的时候会在其他的空间生成一个表格(该位置保存表格地址),,,

表格内部保存的两个值是

1、自己到对象首部的偏移;

2、继承的基类对象到这个地址的偏移;

现在,我们可以好好的解释上面的6个值了》》》

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  继承