C++继承的赋值转换与菱形虚拟继承深入详解_第1页
C++继承的赋值转换与菱形虚拟继承深入详解_第2页
C++继承的赋值转换与菱形虚拟继承深入详解_第3页
C++继承的赋值转换与菱形虚拟继承深入详解_第4页
C++继承的赋值转换与菱形虚拟继承深入详解_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第C++继承的赋值转换与菱形虚拟继承深入详解目录一、继承的概念及定义1.1、继承的概念1.2、继承的定义二、基类和派生类对象赋值转换三、继承中的作用域3.1、继承同名成员处理方式3.2、继承同名静态成员处理方式3.3、继承与友元3.4、继承与静态成员四、派生类的默认成员函数五、复杂菱形继承及菱形虚拟继承5.1、继承分类5.2、虚拟继承解决菱形继承问题原理

一、继承的概念及定义

继承是面向对象三大特性之一。

1.1、继承的概念

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

1.2、继承的定义

继承的语法:class子类:继承方式父类

继承方式:

共有继承私有继承保护继承

基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

二、基类和派生类对象赋值转换

派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。基类对象不能赋值给派生类对象。基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

classperson{

protected:

string_name;

int_age;

classstudent:publicperson

public:

int_No;

voidtest01()

studentsobj;

//1.子类对象可以赋值给父类对象/指针/引用

personpobj=sobj;

person*pp=sobj;

personrp=sobj;

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

//sobj=pobj;

//3.基类的指针可以通过强制类型转换赋值给派生类的指针

pp=sobj;

student*ps1=(student*)pp;//这种情况是可以的

ps1-_No=10;

pp=pobj;

student*ps2=(student*)pp;//这种情况转换时虽然可以,但存在越界访问的问题

ps2-_No=10;

三、继承中的作用域

3.1、继承同名成员处理方式

⚠️问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

classBase{

public:

Base(){

m_A=100;

voidfunc(){

cout"Base-func()调用"endl;

voidfunc(inta){

cout"Base-func(inta)调用"endl;

public:

intm_A;

classSon:publicBase{

public:

Son(){

m_A=200;

//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数

//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域

voidfunc()

cout"Son-func()调用"endl;

public:

intm_A;

voidtest01()

Sons;

cout"Son下的m_A="s.m_Aendl;

cout"Base下的m_A="s.Base::m_Aendl;

s.func();

s.Base::func();

s.Base::func(10);

}

⭐️⭐️⭐️总结:

子类对象可以直接访问到子类中同名成员子类对象加作用域可以访问到父类同名成员当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。当然父类对象随便调用父类成员。

注:子类和父类中有同名成员时构成隐藏关系,也叫重定义。需要注意的是,如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

3.2、继承同名静态成员处理方式

⚠️:问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致:

子类对象访问子类同名成员直接访问即可子类对象访问父类同名成员需要加作用域

classBase{

public:

staticvoidfunc()

cout"Base-staticvoidfunc()"endl;

staticvoidfunc(inta)

cout"Base-staticvoidfunc(inta)"endl;

staticintm_A;

intBase::m_A=100;

classSon:publicBase{

public:

staticvoidfunc()

cout"Son-staticvoidfunc()"endl;

staticintm_A;

intSon::m_A=200;

//同名成员属性

voidtest01()

//通过对象访问

cout"通过对象访问:"endl;

Sons;

cout"Son下m_A="s.m_Aendl;

cout"Base下m_A="s.Base::m_Aendl;

//通过类名访问

cout"通过类名访问:"endl;

cout"Son下m_A="Son::m_Aendl;

cout"Base下m_A="Son::Base::m_Aendl;

//同名成员函数

voidtest02()

//通过对象访问

cout"通过对象访问:"endl;

Sons;

s.func();

s.Base::func();

cout"通过类名访问:"endl;

Son::func();

Son::Base::func();

//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问

Son::Base::func(100);

}

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)

3.3、继承与友元

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

(就好比说爸爸的朋友不一定是我的朋友)

3.4、继承与静态成员

基类定义了static静态成员,则整个继承体系只有这一个成员(我们知道静态成员是整个类共享的),无论派生出多少个子类,都只有这么一个static成员。

classperson

public:

person()

_count++;

protected:

string_name;

public:

staticint_count;//统计人数

intperson::_count=0;

classstudent:publicperson

protected:

int_stuNum;

classgraduate:publicstudent

protected:

stringcourse;

voidtest()

students1;

students2;

students3;

graduates4;

cout"人数"person::_countendl;

student::_count=0;

cout"人数"person::_countendl;

人数4

人数0

请按任意键继续..

代码解释:因为子类对象构造是会调用基类的构造函数,所以每实例化一个子类对象都会调用一次基类构造,从而_count++,并且静态成员是整个类共享的,所以无论哪个子类都可修改!!!

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

6个默认成员函数,默认的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。派生类的operator=必须要调用基类的operator=完成基类的复制。派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。派生类对象初始化先调用基类构造再调派生类构造。派生类对象析构清理先调用派生类析构再调基类的析构。

classperson

public:

person(constchar*name="pxl")

:_name(name)

person(constpersonp)

:_name(p._name)

personoperator=(constpersonp)

if(this!=*p){

_name=p._name;

return*this;

~person()

protected:

string_name;

classstudent:publicperson

public:

student(constchar*name,intnum)

:person(name)//显示调用基类的构造函数初始化基类成员

,_num(num)

student(conststudents)

:person(s)//注意这里有个隐式的切片操作personp=s;

,_num(s._num)

studentoperator=(conststudents)

if(this!=s){

person::operator=(s);//调用基类的operator=完成基类的赋值

_num=s._num;

return*this;

~student()

cout"~student()"endl;

//注意这里会自动调用父类析构

protected:

int_num;

voidtest()

students1("ppp",20);

students2(s1);

students3("xxx",30);

s1=s3;

}

⚠️留意代码中注释部分!

五、复杂菱形继承及菱形虚拟继承

5.1、继承分类

单继承:一个子类只有一个直接父类时称为单继承

多继承:一个子类有两个或者两个以上直接父类时称这个继承关系为多继承

菱形继承:两个派生类继承同一个基类,又有某个类同时继承者两个派生类。菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。

利用虚继承可以解决菱形继承问题

⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

对于菱形继承的二义性问题,我们可以在访问的时候加上类域,这样是可以解决的,但是数据冗余无法解决。所以下面引入虚拟继承!

5.2、虚拟继承解决菱形继承问题原理

为了研究虚拟继承原理,我们给出一个简单的菱形继承体系,再借助内存窗口观察对象成员模型。

classA{

public:

int_a;

classB:publicA

public:

int_b;

classC:publicA

public:

int_c;

classD:publicB,publicC

public:

int_d;

intmain()

Dd;

d.B::_a=1;

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论