C++ 继承解析

 

继承

 

1、概念:

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

一个新类从已有的类中获得其已有的特性称为继承,被继承的称为父类(Base class)或基类,新产生的类称为派生类或子类。

 

 

2、访问限定符与继承关系

 

类的成员有三种访问限定符:public(公有)、protected(保护)、

private(私有)

 

继承的方式:public(公有继承)、protected(保护继承)、

Private(私有继承)

 

3、继承的方式:

继承权限规则表

继承方式

基类的public成员

基类的protected成员

基类的private成员

继承引起的访问控制变化

Public

Public

Protected

不可见

基类成员在派生类中访问权限不变

Protected

Protected

Protected

不可见

基类的非私有成员成为子类的保护成员

Private

Private

Private

不可见

基类的所有成员成为子类的私有成员

 

 

基类:

class Base

{

public:

      Base()

           :pub(1)

           ,pro(2)

           ,pri(3)

      {}

 

public:

      int pub;

 

protected:

      int pro;

 

private:

      int pri;

 

};

 

(1)公有继承:采用公有继承时,基类成员的访问权限在派生类中不变

class Derived1:public Base

{

public:

       Derived1()

              :d_pub(4)

              ,d_pro(5)

              ,d_pri(6)

       {}

       void SetData()

       {

              pub = 0;

              pro = 0;

              pri = 0;   //子类无法访问基类的私有成员

       }

 

public:

       int d_pub;

protected:

       int d_pro;

private:

       int d_pri;

};

 

void FunTest()

{

       Base b;

       b.pub = 1;

       b.pro = 2;   

       b.pri = 3;        //类外无法访问类的保护、私有成员

 

       Derived1 d1;

       d1.pub = 1;

       d1.pro = 2;

       d1.pri = 3;   //子类对象无法访问基类的保护、私有成员

 

       d1.d_pub = 4;

       d1.d_pro = 4;

       d1.d_pri = 4;     //类外无法访问类的保护、私有成员

}

 

(2)保护继承:基类的公有、保护成员成为派生类的保护成员。

 

class Derived2:protected Base

{

public:

       Derived2()

              :d_pub(4)

              ,d_pro(5)

              ,d_pri(6)

       {}

       void SetData()

       {

              pub = 0;  //成为子类的保护成员

              pro = 0;

              pri = 0;   //子类无法访问基类的私有成员

       }

 

public:

       int d_pub;

protected:

       int d_pro;

private:

       int d_pri;

};

 

void FunTest()

{

       Base b;

       b.pub = 1;

       b.pro = 2;   

       b.pri = 3;        //类外无法访问类的保护、私有成员

 

       Derived2 d2;

       d2.pub = 1;   //成为子类的保护成员,无法访问

       d2.pro = 2;

       d2.pri = 3;  //子类对象无法访问基类的保护、私有成员

 

       d2.d_pub = 4;

       d2.d_pro = 4;

       d2.d_pri = 4;     //类外无法访问类的保护、私有成员

}

(3)私有继承

class Derived3:private Base

{

public:

       Derived3()

              :d_pub(4)

              ,d_pro(5)

              ,d_pri(6)

       {}

       void SetData()

       {

              pub = 0;  //成为子类的私有成员

              pro = 0;

              pri = 0;   //子类无法访问基类的私有成员

       }

 

public:

       int d_pub;

protected:

       int d_pro;

private:

       int d_pri;

};

 

void FunTest()

{

       Base b;

       b.pub = 1;

       b.pro = 2;   

       b.pri = 3;        //类外无法访问类的保护、私有成员

 

       Derived3 d3;

       d3.pub = 1;   //成为子类的私有成员,无法访问

       d3.pro = 2;

       d3.pri = 3;   //子类对象无法访问基类的保护、私有成员

 

       d3.d_pub = 4;

       d3.d_pro = 4;

       d3.d_pri = 4;     //类外无法访问类的保护、私有成员

}

 

 

 

2、 总结:

 

(1)      类的保护成员和私有成员在类外部都不可访问,基类的私有成员在派生类都不可访问。

 

 

(2)   public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。

 

例如:有一个Horse类可以保存关于马的所有信息,身高体重等等,那么我们就可以从Horse类中派生出白马类,白马类包含所有Horse类的成员,在白马类中可以新增关于白马的成员,这个成员通常不用于Horse类。

           class Horse

{

public:

    int Tall;

    int Weight;

};

classWhiteHorse:public Horse

{

public:

    int Color;

};

WhiteHorseHorse的一个子类,包括了Horse类的所有成员,is_a是一种关系

 

 

(3)   protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。

 

实现继承的主要目标是代码重用,我们发现类B和类C存在同样的代码,因此我们设计了一个类 A,用于存放通用的代码,基于这种思路的继承称为实现继承。 

基类的成员在派生类中是私有的,基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们,基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。

 

 class Banana

{..};

class Lauch

{

private:

classBanana; ......

};

 

(4)   基类的 private 成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected 。可以看出保护成员限定符是因继承才出现的。

 

(5)   使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

 

3、 派生类的默认成员函数



在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认

的成员函数。

 

(1)   构造函数调用顺序:

进入子类的构造函数——>调用基类构造函数——>执行子类构造函数

说明:

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

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

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

 

(2)      析构函数调用顺序:

派生类析构函数——>派生类包含成员对象的析构函数——>基类析构函数

class Base

{

public:

       Base()

       {

               cout<<"Base()"<<endl;

       }

       Base(int b, int r, int i)

              :pub(1)

              ,pro(2)

              ,pri(3)

       {

              cout<<"Base(int , int , int)"<<endl;

       }

       ~Base()

       {

              cout<<"~Base()"<<endl;

       }

public:

       int pub;

protected:

       int pro;

private:

       int pri;

};

class Derived3:public Base

{

public:

       Derived3()

       {

                cout<<"Derived3()"<<endl;

       }

       Derived3(int d1, int d2, int d3)

              :Base(d1, d2, d3)

       {

              d_pub = d1;

              cout<<"Derived3()   ----"<<"d_pub="<<d_pub<<endl;

       }

       ~ Derived3()

       {

              cout<<"~Derived3()"<<endl;

       }

public:

       int d_pub;

protected:

       int d_pro;

private:

       int d_pri;

};

void FunTest()

{

       Derived3 d0;

       Derived3 d3(1, 2, 3);

}

 

 

 

4、即成体系中的作用域:

(1)在继承体系中基类和派生类是两个不同作用域。

(2) 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 访问)--隐藏 --重定义

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

 

5、继承与转换--赋值兼容规则:(public继承)

(1)子类对象可以赋值给父类对象(切割/切片)

(2)父类对象不能赋值给子类对象

(3)父类的指针/引用可以指向子类对象

(4)子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

class Base

{

public:

       Base()

       {

               cout<<"Base()"<<endl;

       }

       Base(int b, int r, int i)

              :pub(1)

              ,pro(2)

              ,pri(3)

       { cout<<"Base(int , int , int)"<<endl; }

       ~Base()

       {cout<<"~Base()"<<endl;}

 

public:

       int pub;

 

protected:

       int pro;

 

private:

       int pri;

};

class Derived:public Base

{

public:

       Derived()

       { cout<<"Derived3()"<<endl; }

       Derived(int d1, int d2, int d3)

              :Base(d1, d2, d3)

       {

              d_pub = d1;

              cout<<"Derived()"<<endl;

       }

       ~ Derived()

       { cout<<"~Derived()"<<endl; }

public:

       int d_pub;

protected:

       int d_pro;

private:

       int d_pri;

};

 

void FunTest()

{

       Base b0;

       Base b1(1, 2, 3);

       Base* pBase;

      

 

       Derived d0;

       Derived d1(4, 5, 6);

        Derived* pDerived;

 

       b0 = d0; //子类对象可以赋值给父类对象

       b1 = d1;

 

       d0 = bo; //  父类对象不能赋值给子类对象

       d1 = b1;

 

       pBase = &d0;     //父类的指针/引用可以指向子类对象

       pBase = &d1;

 

       Base& _b0 = d0;

       Base& _b1 = d1; //父类的指针/引用可以指向子类对象

 

       pDerived = &b0;

       Derived& = b1;//子类的指针/引用不能指向父类对象

 

       pDerived = (Derived *)&b1;       //可以使用的强制类型转换使子类的指针/引用指向父类对象

 

}

 

6、友元与继承

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

class Base

{

public:

       Base()

       {

              pub = 10;

              pro = 10;

              pri = 10;

               cout<<"Base()"<<endl;

       }

       ~Base()

       {

              cout<<"~Base()"<<endl;

       }

public:

       int pub;

protected:

       int pro;

private:

       int pri;

 friend void       show();

};

class Derived:public Base

{

public:

       Derived()

       {

              d_pri= 10 ;

              cout<<"Derived3()"<<endl;

       }

private:

       int d_pri;

};

void       show()

{

       Base b;

       cout<<b.pri<<endl; //友元函数可以访问基类的私有成员

       Derived d;

       cout<<d.d_pri<<endl;   //友元函数不能继承,基类的友元函数不可以访问子类的私有成员

}

 

7、继承的类型

(1)单继承:

class A

{

public:

       int data1;

} ;

class B  :public A

{

public:

       int data2;

} ;


(2)多继承:

class A

{

public:

       int data1;

} ;

class B 

{

public:

       int data2;

}  ;

 class C :public A ,public B

{

public:

       int data3;

} ;

 

 

(3)   菱形继承:

        class A

{

public:

       int data1;

} ;

class B: public A      

{

public:

       int data2;

}  ;

 class C:public A

{

public:

       int data3;

} ;

class D:public B,public C

{

public:

       int data4;

} ;

 

 

 

 

 

D类的对象中存在两份A对象成员,菱形继承存在二义性和数据冗余问题

 

 

8、虚继承--解决菱形继承的二义性和数据冗余的问题

 

 

 

class A

{

public:

    int data1;

 

} ;

 

class B: virtual public A  

{

public:

    int data2;

}  ;

 class C: virtual public A

{

public:

    int data3;

} ;

 

 class D:public B,public C

{

public:

    int data4;

} ;

 

 void FunTest()

 {

     D d;

     d.data1 = 1;

     d.data2 = 2;

     d.data3 = 3;

     d.data4 = 4;

     cout<<sizeof(B)<<endl;//12

     cout<<sizeof(D)<<endl;//24

//0x0098F73C 000ecc88 00000002 000ecc94 00000003 00000004 00000001      

 

 }


 



(1)虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。

(2)虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,

因为使用虚继承解决数据冗余问题也带来了性能上的损耗。



 就写到这了,有什么不对的大家指正哈!!!

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[傅里叶变换及其应用学习笔记] 九. 继续卷积的讨论

这份是本人的学习笔记&#xff0c;课程为网易公开课上的斯坦福大学公开课&#xff1a;傅里叶变换及其应用。 卷积在滤波中的应用 浑浊度&#xff08;Turbidity&#xff09;研究是关于测量水的清澈度的研究。大致方法是把光传感器放置到深水区域&#xff0c;然后测量光线的昏暗程…

C++多态相关关问题及虚表剖析

关于C多态的问题&#xff1a;&#xff08;基于Visual Studio 2012编译器&#xff09; 一、多态引入 1、对象的类型&#xff1a; &#xff08;1&#xff09; 静态的类型&#xff1a;对象声明时的类型&#xff0c;在编译的时候确定 &#xff08;2&#xff09; 动态的类型&…

C++调用约定

<div class"markdown_views"><p>有一定C开发经验的人一定对”__cdecl、__stdcall、__fastcall”肯定不陌生吧&#xff01;但你真正理解了吗&#xff1f;是的&#xff0c;我曾在这采了无数个坑&#xff0c;栽了无数个跟头&#xff0c;终于忍无可忍要把它总…

C++动态绑定及返回类型协变

C多态之动态绑定&#xff1a; 1、概念&#xff1a;在程序执行期间(非编译期)判断所引用对象的实际类型&#xff0c;根据其实际类型调用相应的方法。 使用virtual关键字修饰类的成员函数时&#xff0c;指明该函数为虚函数&#xff0c;派生类需要重新实现&#xff0c;编译器将实…

使用ucontext组件实现的coroutine代码分析

coroutine一般翻译过来就是协程&#xff0c;类似于线程可以切换&#xff0c;而跟线程是由操作系统调度器来实现切换不一样&#xff0c;协程由用户程序自己调度进行切换。我以前也看过协程相关的内容&#xff0c;但没有自己去实现过。最近搞OpenStack&#xff0c;OpenStack各个模…

C++模板剖析:函数模板、类模板解析

C中关于模板&泛型编程问题&#xff1a; 问题引入&#xff1a;何编写一个通用加法函数&#xff1f; &#xff08;1&#xff09;使用函数重载&#xff0c;针对每个所需相同行为的不同类型重新实现它 int Add(const int &_iLeft, const int&_iRight) { return (_iL…

Android Studio 1.1的安装和遇到的坑

Google的Android Studio 出1.0稳定版本也有很久的时间了&#xff0c;一直喜欢Jetbrains公司的IDE&#xff0c;不同语言的IDE操作习惯都比较统一。 而Android Studio 是基于IntelliJ IDEA的社区版开发的 &#xff0c;怎么也要尝尝鲜才行。 今天安装了下&#xff0c;被几个小坑卡…

BestCoder Round #39 解题报告

现场只做出前三题w 不过不管怎样这既是第一次认真打BC 又是第一次体验用在线编译器调代码 订正最后一题花了今天一整个下午&#xff08;呜呜 收获还是比较大的^_^ Delete wld有n个数(a1,a2,...,an)&#xff0c;他希望进行k次删除一个数的操作&#xff0c;使得最后剩下的n−k个数…

linux :vim 实现命令行下输出进度条

1、 进度条原理&#xff1a; 进度条的的动态增长是利用人的视觉短暂停留效果的&#xff0c;不断从输出缓冲区刷新出相同的内容&#xff0c;在肉眼看来进度条在不断的增长。 在显示器上先输出[# ][%1] 刷新一次之后&#xff0c; …

***jquery选择器 之 获取父级元素、同级元素、子元素

一、获取父级元素1、 parent([expr]): 获取指定元素的所有父级元素 <div id"par_div"><a id"href_fir" href"#">href_fir</a><a id"href_sec" href"#">href_sec</a><a id"href_thr&q…

Sql Server字符串函数

字符串函数用于对字符和二进制字符进行各种操作 1.ASCII()函数 ASCII(character_expression)函数用于返回字符串表达式中最左侧的ASCII代码值。参数character_expression必须是一个char或varchar类型的字符串表达式。 eg: select ASCII(s),ASCII(sql),ASCII(1);执行结果如图:字…

linux 编辑器vim配置

1、 基本配置 对vim进行配置的目的&#xff1a; 进行vim配置&#xff0c;可以让我们在后续敲代码更加方便。按F5可以直接编译并执行C、C代码以及执行shell脚本&#xff1b;按“F8”可进行C、C代码的调试&#xff1b;“Ctrl A”为全选并复制快捷键&#xff0c;方便复…

centos 7上ambari安装试用

2019独角兽企业重金招聘Python工程师标准>>> 1、有三台centos7&#xff0c;名字分别是ws11.localdomain, ws12.localdomain, ws13.localdomain。配置epel源(安装epel-release)。 2、配置root用户ssh无密码访问。 3、安装ntp对时服务。 4、关闭防火墙。centos7下使…

进程管理—进程描述符(task_struct)

本文章转载自&#xff1a;http://blog.csdn.net/qq_26768741/article/details/54348586?locationNum4&fps1 前言 当把一个程序加载到内存当中&#xff0c;此时&#xff0c;这个时候就有了进程&#xff0c;关于进程&#xff0c;有一个相关的叫做进程控制块&#xff08;PCB&…

如何使用emacs编写c语言程序,并编译运行

vi和emacs被分别被称为编辑器之神和神之编辑器。vi的入门精通都很难&#xff0c;emacs入门容易&#xff0c;精通难&#xff1b;vi使用起来不停地切换模式&#xff0c;而emacs则不停地ctrl&#xff0c;meta等组合键。因此&#xff0c;高德纳大师说操作Emacs&#xff0c;就像弹奏…

操作系统中常见的进程调度算法

一、调度与调度算法 调度&#xff1a;操作系统管理了系统的有限资源&#xff0c;当有多个进程&#xff08;或多个进程发出的请求&#xff09;要使用这些资源时&#xff0c;因为资源的有限性&#xff0c;必须按照一定的原则选择进程&#xff08;请求&#xff09;来占用资源。这…

粘滞位 File文件内容

t权限&#xff08;粘滞位)&#xff1a; 是‘不可删除’权限&#xff0c;就是说即使某用户拥有这个文件的rwx权限&#xff0c;可以随意修改文件内容&#xff0c;但是就是不能删除&#xff0c;甚至不能修改文件名&#xff0c;只有root才行。t权限也可以直接用 chmod ot/at fil…

QQuickRenderControl

2019独角兽企业重金招聘Python工程师标准>>> http://doc.qt.io/qt-5/qquickrendercontrol.html http://translate.google.com.hk/translate?hlzh-CN&slauto&tlen&uhttp%3A%2F%2Fhabrahabr.ru%2Fpost%2F247477%2F http://www.kdab.com/overview-qt3d-2-…

线程的控制(创建、等待、终止)、分离线程

一、线程控制 1、线程&#xff1a;线程是资源调度的基本单位&#xff0c;线程是进程内部的一个执行流&#xff0c;在进程的地址空间内运行。在Linux 下没有真正意义上的线程&#xff0c;线程是用进程模拟的&#xff0c;又被称为轻量级进程。 2、由于同⼀一进程的多个线程共享同…

从netty-example分析Netty组件

分析netty从源码开始 准备工作&#xff1a; 1.下载源代码&#xff1a;https://github.com/netty/netty.git 我下载的版本为4.1 2. eclipse导入maven工程。 netty提供了一个netty-example工程&#xff0c; 分类如下&#xff1a; Fundamental Echo ‐ the very basic client and …