【C++】浅谈三大特性之一继承(三)
四,派生类的六个默认成员函数
在继承关系里,如果我们没有显示的定义这六个成员函数,则编译系统会在适合场合为我们自动合成。
继承关系中构造函数和析构函数的调用顺序:
class B
{
public:
B()
{
cout<<"B()"<<endl;
}
~B()
{
cout<<"~B()"<<endl;
}
}; class D:public B
{
public:
D()
{
cout<<"D()"<<endl;
}
~D()
{
cout<<"~D()"<<endl;
}
}; void Funtest()
{
D d;
}
int main()
{
Funtest();
return 0;
}
非常简单的一段代码,你觉得会打印什么呢?一起来看看
有人看到这里,肯定会说,那明摆着嘛,先调用B类的构造函数再调用D类的构造函数,根据栈空间先进后出的原则,接着先析构B类自己,再析构从基类那继承来的部分,可是,事实真的这么简单吗?当然不。
你想想,你创建的是子类的对象,怎么可能先去调用基类的构造函数呢,既然这样,为什么打印结果显示的确是先调用父类的构造函数呢???这里牵扯到构造函数的调用次序和函数体的执行顺序的问题,注意不要混淆它们。
实际上,调用顺序是这样的:
你可能会有点疑惑,明明先调用了子类的构造函数,可是为什么会去先执行基类的构造函数体,这是因为我们显示定义了父类的缺省构造函数,你想想,既然显示定义了,就必须调用它对吧,如果我们不在子类的构造函数中调用它,那何时调用呢,但是由于我们创建的是子类对象,所以必须先调用子类的构造函数,思来想去,将基类的构造函数放在子类的初始化列表中调用似乎再合适不过了,所以,我们C++的设计者们就是这样做的,是不是很聪明呢?
在这里,还需要注意一点,如果基类的显示的定义了缺省的构造函数,那么基类的构造函数即使不显示定义,编译器也会为我们合成默认的构造函数用来调用基类的构造函数,组合也是如此。
五,继承体系中的作用域
1,继承体系中,子类的作用域和父类的作用域属于两个作用域。(在子类中不能访问父类的私有成员足以说明此点)
2,同名隐藏。如果子类中包含和父类相同名字的成员,则子类成员将屏蔽对父类成员的直接访问,如果想要在子类中访问父类的同名成员,就必须采用作用域限定符。
eg:
class B
{
protected:
int _a;
}; class D:public B
{
public:
void test()
{
_a = 10;
}
private:
int _a;
}; void Funtest()
{
D d;
d.test();
}
int main()
{
Funtest();
return 0;
}
运行结果:
从监视窗口中,我们清晰的看到了子类对象的_a被改为10,而父类的成员数据_a仍然是一个随机值。怎样做到在子类对象中改变的是父类对象的成员数据呢?
eg2:
class B
{
protected:
int _a;
}; class D:public B
{
public:
void test()
{
B::_a = 10;
}
private:
int _a;
}; void Funtest()
{
D d;
d.test();
}
int main()
{
Funtest();
return 0;
}
运行结果:
注意:尽量避免父类和子类使用同名成员,不要给自己挖坑哦!
六,赋值与转换----赋值兼容规则
1,子类对象可以直接赋值给父类对象(切片/割片)。
2,父类对象不能直接赋值给子类对象。
3,父类对象的引用或指针可以直接指向子类对象。
4,子类对象的引用或指针不可以直接指向父类对象。(强制类型转换可完成)
对象赋值:
引用或指针:
class B
{
protected:
int _b;
}; class D:public B
{
private:
int _d;
}; void Funtest()
{
D d;
B *b;
b = &d;//父类指针指向子类对象 D *d1;
B b1;
d1 = (D*)&b1;//子类对象可通过强制类型转换指向父类对象(尽量避免) D &d2 = d;
B b2;
b2 = d2;//父类引用指向子类对象 D d3;
B &b3 = b1;
d3 = (D&)b3;//父类引用指向子类对象
}
int main()
{
Funtest();
return 0;
}
七,单继承&多继承&菱形继承
<1>单继承:一个子类仅有一个直接的父类。
单继承中类中成员数据的分布与成员变量在类中的定义顺序有关。
<2>多继承:一个子类有两个或两个以上直接的父类。
多继承中派生类成员的分布与继承类的先后次序有关
<3>菱形继承(钻石继承)
菱形继承中成员的分布与最底层类继承的先后次序有关
上图中我们标出了菱形继承中各个类所占字节数,可是C1类和C2类中都继承了B类中的数据成员_b,那么如果我们通过D类的对象对_b进行访问,必然会产生二义性,
class B
{
int _b;
};
class C1:public B
{
int _c1;
};
class C2:public B
{
int _c2;
};
class D:public C1,public C2
{
int _d;
};
void Funtest()
{
D d;
d._b = 10;//错误,访问不明确
d.C1::_b = 10;//正确
d.C2::_b = 10;//正确
}
如何避免这种访问不明确呢,是否可以将重复部分_b只在D类中保存一份呢,这将引入虚拟继承的概念。
eg:
class B
{
int _b;
};
class C1:virtual public B
{
int _c1;
};
class C2:virtual public B
{
int _c2;
};
class D:public C1,public C2
{
int _d;
};
void Funtest()
{
B b;
C1 c1;
}
上面这段代码就是一个虚拟继承的例子,注意关键字virtual的位置不要写错哦
当创建好C1类的变量c1时,编译器会为C1合成一个默认的构造函数,这个合成的默认构造函数会做哪些事呢?
首先,如果基类有缺省构造函数,它会去调它,其次,它会将偏移量的地址指针(虚指针)放在c1对象的前4个字节处。
再来看一下如果是D类的对象,又是怎么存储的呢?
动脑筋想一下,如果B,C1,C2,D均为空类,每个类所占字节大小又是多少呢?
最后,需要注意的几点:
1、友元关系不能继承,因为友元关系不属于类的成员(就好比你朋友的女朋友并不是你的女朋友)。
2、如果类中包含静态成员,无论继承了多少派生类,静态成员都只保存一份。
3、析构函数和构造函数不能被继承下来。原因:派生类除了继承基类的成员外,还可以添加只属于自己的新成员,如果用继承来的构造函数初始化,只能初始化从基类继承来的那部分,而派生类本身新添加的那部分成员初始化不了。析构函数也是一样的,初始化不到派生类新添加的成员,导致内存泄漏。
【C++】浅谈三大特性之一继承(三)的更多相关文章
- 【C++】浅谈三大特性之一继承(二)
三,继承方式&访问限定符 派生类可以继承基类中除了构造函数和析构函数之外的所有成员,但是这些成员的访问属性是由继承方式决定的. 不同的继承方式下基类成员在派生类中的访问属性: 举例说明: (1 ...
- 【C++】浅谈三大特性之一继承(一)
一,为什么要引入继承? 继承是一个非常自然的概念,现实世界中的许多事物也都是具有继承性的. 例如,爸爸继承爷爷的特性,儿子又继承爸爸的特性等都属于继承的范畴.下面是一个简单的汽车分类图: 在这个分类图 ...
- 浅谈python的对象的三大特性之继承
前面我们定义了人的类,并用这个类实例化出两个人jack和lily,查看了它们的内存空间. 现在我们再来看看类中所存在的对向对象编程的三大特性之继承的一些特性. 前面定义了一个人的类,可是我们还知道,人 ...
- java提高篇(二)-----理解java的三大特性之继承
在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...
- java提高篇(二)-----理解java的三大特性之继承
在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...
- (转)java提高篇(二)-----理解java的三大特性之继承
在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...
- java基础(二)-----java的三大特性之继承
在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...
- 【转】java提高篇(二)-----理解java的三大特性之继承
[转]java提高篇(二)-----理解java的三大特性之继承 原文地址:http://www.cnblogs.com/chenssy/p/3354884.html 在<Think in ja ...
- 黑马程序员——OC语言 三大特性之继承
Java培训.Android培训.iOS培训..Net培训.期待与您交流! (以下内容是对黑马苹果入学视频的个人知识点总结) 三大特性之继承 (一)继承的基本用法 先建立个Animal再用Dog继承前 ...
随机推荐
- Java-8ATM
源代码: import java.util.Scanner;interface fangfa{ public void show();public void qukuan();public void ...
- asp.net core mvc剖析:路由
在mvc框架中,任何一个动作请求都会被映射到具体控制器中的方法上,那框架是如何完成这样一个过程的,现在我们就来简单分析下流程. 我们紧跟上面的主题,任何一个请求都会交给处理管道进行处理,那mvc处理的 ...
- BZOJ 1050: [HAOI2006]旅行comf (并查集 或 单调队列)
这是建空间后做的第一道题啊= =好水 排序,枚举最小边,然后并查集求出联通时的最大边 或者排次序,从小到大插边,如果插边时最小的边拿掉不会使s与t不联通,就删去。 code: #include< ...
- Linux安装 Mysql
背景 前不久开始学习部署ambari,在安装mysql的时候遇到了点问题,现在从网上找了点部署流程记录下来便于以后安装mysql. Centos 7.2 安装 Mysql 5.7.13 首先要安装yu ...
- YoMail 邮箱客户端的社会化之路,起于邮箱,不止于邮件
你还记不记得上一次用邮箱处理私人事务是什么时候?从什么时候开始邮箱于你而言,唯一功能沦为了收取各种网站的验证信息? 电子邮件实际上非常适合于工作上使用,比起其他通信工具,或者社会化媒体,电子邮件在工作 ...
- Laravel分页
Laravel使用的过程中,有些功能把前端页面的表达"写死了",比如分页的翻页按钮! 当然你会说Laravel的Bootstrap样式也很好看啊,但是实际项目中,翻页按钮常常需要满 ...
- 如何垂直居中<img>?
方法1: 父元素设置height=line-height,子元素设置vertical-align:middle; 方法2: 父元素display:table-cell;vertical-align:m ...
- 通过udev创建ASM共享磁盘(RAC)
OS:RedHat EL6.0 Oracle: Oracle 11gR2 在Oracle 11gR2,构建RAC时可以通过ASM创建asm disk,但是需要安装asmlib相关软件:对于RedH ...
- ASP.NET MVC 5 基本构成
MVC模式简介: MVC模式两种理解:一种是表现模式,另外一种是架构模式.它将应用程序分成三个主要组件即:视图(View)控件器(Controller)模型(Model) M: Model主要是存储或 ...
- ArrayList源码剖析
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...