继承中的构造函数
当通过一个子类创建一个新的对象时,编译器会根据子类在初始化表中指明的基类的初始化方式去调用基类相应的构造函数。如果子类的初始化表中,并没有指明基类的初始化方式,编译器将会调用基类的无参构造函数。由于子类在声明的时候,会写明自己的继承表,所以编译器执行的时候,总是先看到基类,所以编译器最先初始化的就是其基类,然后再是子类的其他成员。最后执行子类构造函数的函数体。
所以,不能将基类的初始化放在函数体中。其理由和成员子对象类似。

继承中的析构函数
当一个子类对象到生命周期尽头的时候,它是先调用子类自身的析构函数,然后调用成员子对象的析构函数,最后调用基类的析构函数。无论子类的析构函数,是否是自定义的,编译器都会自动的调用基类的析构函数。
注意:如果使用new创建了一个子类对象,然后delete指向这个子类对象的基类指针,很有可能造成内存泄漏。指向子类对象的基类指针,只能访问子类对象的一部分,那么使用它去delete,也只能delete子类对象的一部分。

#include <iostream>
using namespace std;
class Base{
public:Base(void):m_i(0){cout << "基类的无参构造" << endl;}Base(int i):m_i(i){cout << "带有整型参数的构造" << endl;}~Base(void){cout << "Base的析构函数" << endl;}int m_i;
};
class Member{
public:Member(void):m_i(0){cout << "me的无参构造" << endl;}Member(int i):m_i(i){cout << "me有参" << endl;}~Member(void){cout << "Member的析构函数" << endl; }int m_i;
};
class Drived:public Member,public Base{
public:Drived(void){}//指明基类子对象初始化方式Drived(int i):Member(i),Base(i){}~Drived(void){cout << "Drived的析构函数" << endl;}
};
int main(void){/*Drived d1;cout << d1.Base::m_i << endl;Drived d2(100);cout << d2.Member::m_i << endl;*///指向子类对象的基类指针Base *pb = new Drived;delete (Drived *)pb;pd = NULL;return 0;
}

继承中的拷贝构造函数
如果子类没有定义拷贝构造函数,在使用子类对象进行拷贝构造的时候,编译器会调用其缺省的拷贝构造函数,然后自动调用基类的拷贝构造函数,对子类对象中的基类子对象部分进行拷贝构造。
如果子类自定义了拷贝构造函数,必须在拷贝构造函数的初始化表中,调用基类的拷贝构造函数,为子类对象中的积累子对象部分进行拷贝构造。

继承中的拷贝赋值函数
如果子类没有定义拷贝赋值函数,在使用子类对象进行拷贝赋值的时候,编译器会调用子类的缺省拷贝赋值函数,然后自动调用基类子对象的拷贝构造函数。
如果子类自定义了拷贝赋值函数,在使用子类对象进行拷贝赋值的时候,需要在子类的拷贝赋值函数中显式的调用基类子对象的拷贝赋值函。

#include <iostream>
using namespace std;
class Base{
public:Base(void):m_i(0){}Base(int i):m_i(i){}Base(const Base &that):m_i(that.m_i){cout << "Base(const Base &that)" << endl;}Base &operator=(const Base &that){cout << "Base &operator=(const Base &that)" << endl;if(this != &that){this -> m_i = that.m_i;}return *this;}int m_i;
};
class Derived:public Base{
public:Derived(void):m_i(0){}Derived(int i,int m):Base(i),m_i(m){}Derived(const Derived &that)//指明基类要用拷贝的方式进行初始化:Base(that),m_i(that.m_i){}Derived &operator=(const Derived &that){cout << "Derived &operator=(const Derived &that)" << endl;if(this != &that){//(Base)*this = that;(*this).Base::operator=(that);m_i = that.m_i;}return *this;}int m_i;
};
int main(void){Derived d1(100,200);cout << "d1.m_i =" << d1.m_i << ",d1.Base::m_i = " << d1.Base::m_i << endl;Derived d2(d1);cout << "d2.m_i =" << d2.m_i << ",d2.Base::m_i = " << d2.Base::m_i << endl;Derived d3;d3 = d1;cout << "d3.m_i =" << d3.m_i << ",d3.Base::m_i = " << d3.Base::m_i << endl;return 0;
}

多重继承
一个子类同时继承多个基类。
多重继承中的向上造型
多重继承产生的子类创建对象后。根据向上造型的原理,子类所有的基类类型指针都可以直接指向子类对象的内存空间,编译器也不会报错。编译器会根据指针的类型将其偏移,将对应的基类类型指针指向子类对象内存空间和该类型匹配的那片区域。所以,当子类的多个不同的基类类型指针指向子类对象的时候,指针的值会不一样,存在偏移。

#include <iostream>
using namespace std;
class Phone{
public:Phone(const string &number):m_number(number){}void call(const string &number){cout << m_number << "打给" << number << endl;}
private:string m_number;
};class Player{
public:Player(const string &media):m_media(media){}void play(const string &music){cout << "播放" << music << endl; }
private:string m_media;
};class Computer{
public:Computer(const string &os):m_os(os){}void run(const string &app){cout << "在" << m_os << "上运行一个" << app << endl;}
private:string m_os;
};class SmartPhone:public Phone,public Player,public Computer{
public:SmartPhone(const string &number,const string &media,\const string &os):Phone(number),Player(media),Computer(os){}
private:
};
int main(void){SmartPhone chuizi("15168281470","Mp4","锤子");chuizi.call("13777415260");chuizi.play("炫个脑袋");chuizi.run("王者荣耀");SmartPhone *p_S = &chuizi;Phone *p_P = p_S;Player *p_Pl = p_S;Computer *p_C = p_S;cout << "p_P = " << p_P << ",p_Pl = " << p_Pl<<",p_C = " << p_C << endl;return 0;
}

多重继承中的标识符冲突问题
一个子类的多个基类中,可能存在相同的标识符,比方说,A基类中的成员变量a,和B基类中的成员函数a,重复了,当一个子类同时继承它们之后,通过子类对象调用a的时候,编译器就会报歧义错误,编译器不知道程序员想要的是成员变量A::a,还是成员函数B::a。
解决方法就是,对重名的标识符,再调用的时候通过类名限定符来显式的标识”A::a”.

#include <iostream>
using namespace std;
class Base1{
public:void func(void){cout << "this is Base1" << endl;}
};
class Base2{
public:void func(int i){cout << "this is Base2" << endl;}
};
class Derived:public Base1,public Base2{//通过using生命让它们在子类中形成重载//using Base1::func;//using Base2::func;//不建议这么使用,如果是一个函数,一个变量重名,就不能用。};
int main(void){Derived d;d.Base1::func();return 0;
}

钻石继承问题
A
/ \
B C
\ /
D
A类是B类和C类的基类,B类和C类又是D的基类。通过以下例子先来知晓问题所在。

#include <iostream>
using namespace std;
class A{
public:A(int data):m_data(data){}
protected:int m_data;
};class B:public A{
public:B(int data):A(data){}void set(int data){m_data = data;}
};class C:public A{
public:C(int data):A(data){}int get(void){return m_data;}
};class D:public B,public C{
public:D(int data):B(data),C(data){}
};int main(void){D d(100);cout << "d.get() = " << d.get() << endl;d.set(200);cout << "d.get() = " << d.get() << endl;return 0;
}

程序运行结束后发现两次答应的结果一致,d.set(200)好像并没有起效果。
D类继承了B类和C类,而B类和C类,都继承了一次A类。
在D的内存空间中存在一份通过B类继承的m_ data,和一份通过C类继承m_data。
set修改的是D通过B类继承的m_ data,而get得到的是D类通过C继承的m_data
这就是钻石继承中的问题,汇聚子类所创建的对象中保持着多份基类的实例。

虚继承
C++中提供了虚继承的手段来保证,在汇聚子类创建的对象中只有一份实例。
1)在中间类的继承表中用virtual声明
2)在汇聚子类中,再次继承定义实例的基类。

#include <iostream>
using namespace std;
class A{
public:A(int data):m_data(data){cout << "A:addr = " << this << ",A:sizeof " << sizeof(A) << endl;}
protected:int m_data;
};class B:virtual public A{
public:B(int data):A(data){cout << "B:addr = " << this << ",B:sizeof " << sizeof(B) << endl;}void set(int data){m_data = data;}
};class C:virtual public A{
public:C(int data):A(data){cout << "C:addr = " << this << ",C:sizeof " << sizeof(C) << endl;}int get(void){return m_data;}
};class D:public B,public C{
public://虚继承时,末端汇聚子类负责构造公共基类(A)子对象D(int data):B(data),C(data),A(data){cout << "D:addr = " << this << ",D:sizeof " << sizeof(D) << endl;}
};int main(void){D d(100);cout << "d.get() = " << d.get() << endl;d.set(200);cout << "d.get() = " << d.get() << endl;//B b(100);return 0;
}

题外话
虚基类的原理大致如下:
一个子类在继承基类的时候,被vritual修饰之后,会生成一个虚表指针,这个指针,指向一张表的中间位置,表的前半部分记录了子类对象地址的起始位置到从基类中继承的数据的起始位置偏移量。可以理解成,当使用vritual修饰之后,子类对象不像传统的单继承一样,将基类的成员变量放在自己的内部(虽然在内存上还是连在一起),而是通过一个虚表指针寻址。
这样的机制,在钻石继承中,就可以使B,C两个基类不需要再自己创建m_data,仅仅只需要用自己的虚标指针找到D所维护的m _data即可。
注意,虚标指针产生有两种情况:
第一种,类中声明了虚函数,会产生一个虚表指针,这个时可以被继承的。
第二种,子类通过virtual的方式继承一个基类,编译器会为子类生成一个虚标指针,这个指针,是不会继承的,而是绑定在每个子类对象里的。
注意,第二种情况下,如果基类中压根就没有成员变量,则子类就没有必要也不会生成一个虚表指针。

—————————————————
二十一、继承()
5、子类的构造函数
1) 如果子类的构造函数,没有指明基类子对象的初始化方式,那么编译器会自动的调用基类的无参构造函数来初始化该子对象。
2) 如果希望基类子对象以有参的方式被初始化,则必须是在子类对象的初始化表中指明基类的初始化方式。
(不能写在子类的构造函数的函数体中,和成员子对象一样,基类子对象的初始化也是在执行子类的构造函数体之前完成的)

如果一个子类中包含基类子对象和成员子对象,那么会先初始化基类子对象,这个是符合之前所说的谁先声明就初始化谁的原则,因为在子类的头部,是标明了继承了哪些基类,因为编译器的执行顺序,所以是先看到继承表,根据基类的声明顺序来决定初始化的顺序。经过验证,如果一个子类继承了两个类,哪个类先声明就先初始化哪个类。

6、子类的析构函数
1) 子类的析构函数,无论自己实现还是缺省提供的,都会自动调用基类的析构函数,析构继承子对象.
2) 子类对象的销毁过程
–> 执行析构函数
–> 析构成员子对象
–> 析构基类子对象
–> 释放内存
3) 基类的析构函数不能调用子类的析构函数,delete一个指向子类对象的基类指针,实际被调用的仅仅只是基类的析构函数,子类的析构函数不会被执行,有内存泄露的风险。

7、子类的拷贝构造和拷贝赋值
1) 子类的拷贝构造
–>如果子类没有定义拷贝构造函数,那么编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,初始化基类子对象。
–>如果子类自己定义了拷贝构造函数,那么必须要使用初始化表指明基类子对象也以拷贝的方式进行初始化。
2) 拷贝赋值操作附函数
–>如果子类没有定义拷贝赋值函数,编译器会为子类提供缺省的拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,赋值基类子对象
–>如果子类自己定义了拷贝赋值函数,需要显式的调用基类的拷贝赋值函数,完成对基类子对象的复制。

8、多重继承
1) 一个子类同时继承多个基类,这样的继承方式成为多重继承。

    电话  计算机  播放器\    |     /智能手机

2) 多重继承在向上造型
编译器会根据各个基类子对象的内存布局,自动的进行适当的偏移计算,保证指针的类型和所指向的目标类型一致。

3) 名字冲突问题
–>一个子类的多个基类如果存在相同的标识符,当通过子类访问这些标识符的时候,编译器会报歧义错误—名字冲突。
–>解决名字冲突的常规做法就是显式通过”类名::”,指明所访问的名字属于哪个基类。
–>如果产生冲突的标识符是两个函数,而且满足参数不同的重载条件,也可以通过using声明的方式,让它们在子类中形成重载,通过重载匹配来解决。//不推荐

9、钻石继承
1) 概念:一个子类的多个基类源自共同的基类祖先,这样的继承结构成为钻石结构。
A
/ \
B C
\ /
D
2) 公共基类(A)子对象在汇聚子类(D)对象中,存在多个实例,通过汇聚子类访问公共基类的成员时,会因为继承路径不同,导致结果不一致。

3) 通过虚继承,可以让公共基类子对象在汇聚子对象中实例唯一,并为所有的中间类共享。即使沿着不同的继承路径所访问到的公共基类成员也是一致的。

10、虚继承的语法

1) 在继承表中使用virtual修饰
2) 位于继承链的末端子类,负责构造公共基类子对象

当子类继承基类的时候,基类使用virtual修饰后,子类会生成一个虚表指针,这个指针指向了一张表的中间位置,在这张表的前半部分位置,记录了子类对象中子类对象的起始地址到 从基类继承的数据的地址的起始地址的偏移量。
这就是为什么子类对象的尺寸变大了4个字节。也通过这样的方式解决了钻石继承的问题。

C++学习笔记day47-----C++98-继承中的构造函数,析构函数,拷贝构造函数,拷贝赋值函数,多重继承,虚继承相关推荐

  1. JavaScript学习笔记06【高级——JavaScript中的事件】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

  2. Sharepoint学习笔记---如何在Sharepoint2010网站中整合Crystal Report水晶报表(显示数据 二)...

    在Sharepoint学习笔记---如何在Sharepoint2010网站中整合Crystal Report水晶报表(显示数据一)中,解释了如何把Crystal Report整合到Sharepoint ...

  3. MySQL学习笔记(六)-MySQL中库和表的管理

    MySQL学习笔记(六)-MySQL中库和表的管理 作者:就叫易易好了 日期:2020/11/23 1 2 DDL即数据定义语言 创建:create 修改:alter 删除:drop 库和表的管理: ...

  4. Android学习笔记---22_访问通信录中的联系人和添加联系人,使用事物添加联系人...

    Android学习笔记---22_访问通信录中的联系人和添加联系

  5. Hadoop学习笔记—13.分布式集群中节点的动态添加与下架

    Hadoop学习笔记-13.分布式集群中节点的动态添加与下架 开篇:在本笔记系列的第一篇中,我们介绍了如何搭建伪分布与分布模式的Hadoop集群.现在,我们来了解一下在一个Hadoop分布式集群中,如 ...

  6. PhalAPI学习笔记拓展篇 ———ADM模式中NotORM实现简单CURD

    PhalAPI学习笔记拓展篇 ---ADM模式中NotORM实现简单CURD 前言 内容 ADM模式 ADM简单介绍 准备工作 PhalAPI提供的CURD操作方法 业务实现 结束语 前言 公司业务需 ...

  7. 假装认真的LaTeX学习笔记(1)—— Sublime中自动补全LaTeX命令(LaTeX-cwl安装教程)

    假装认真的LaTeX学习笔记(1)-- Sublime中自动补全LaTeX命令 简介 使用环境 如何在Sublime中获得LaTeX自动补全功能 安装Sublime插件--LaTeX-cwl 方法一: ...

  8. oracle复制另一个字段,【学习笔记】Oracle存储过程 表中列不同时动态复制表中数据到另一个表中...

    天萃荷净 分享一篇关于Oracle存储过程实现表之间数据复制功能.两表中列不同,动态的将一表中的数据复制到另一个表中案例 因为要用到回收站功能,删除一条记录,要先放到一个delete表中,以便以后恢复 ...

  9. Zemax学习笔记(13)- ZEMAX 中的玻璃库波长范围的使用范围

    Zemax学习笔记(13)- ZEMAX 中的玻璃库波长范围的使用范围 有时候在使用Zemax的时候,会出现提示"某种玻璃的波长超出了玻璃库中的允许使用范围",这是为什么呢? 实在 ...

  10. Zemax学习笔记(3)- Zemax中的序列模式和非序列模式

    Zemax学习笔记(3)- Zemax中的序列模式和非序列模式 序列模式与非序列模式 序列模式 窗口浮动或者固定 主要分析 非序列模式 混合模式 光源建模 创建复杂的几何体 CAD 导入 布尔命令 光 ...

最新文章

  1. 车载安卓导航一键root_听说比Carplay牛 率先体验华为HiCar车载互联
  2. vue 圆形百分比进度条_uniapp Vue 圆环进度条
  3. kubernetes 实战 使用 nfs 作为动态 storageClass 存储
  4. 读梁宁《一次失控引发的信任评估---我看胡紫薇事件》
  5. python 数学公式显示_ipython jupyter notebook中显示图像和数学公式实例
  6. 在Emacs24下的Java环境(Cedet+Elib+JDEE+ECB)
  7. 做下一个互联网时代的“水电公司”——融云的通信云视野与蓝图
  8. 日常提醒(delphi源码)
  9. chrome浏览器性能分析
  10. JAVA实现简单电话簿功能
  11. 《哲学100问》读书感想:为什么要做一个道德的人
  12. Git命令的使用(进阶版)
  13. php 检测分辨率,浏览器分辨率检测,屏幕分辨率检测
  14. Rust编程语言入门教程(一)-什么是Rust
  15. 设置Ajax为同步请求
  16. [ESP32][esp-idf] AP+STA实现无线桥接 中转wifi信号 路由器
  17. 使用openssl制作证书和进行CMS格式数字签名
  18. 中华英才网三年之痒 接受增持还是坚持独立IPO
  19. KENALLRYLLDKDD|359821-54-8
  20. 无法安装.Net4.0 《已在此计算机上安装相同或更高版本的 .NET Framework 4》解决方案

热门文章

  1. clion三角形运行键是灰的_越升级越卡顿?教你两种方法,软件轻松加速,大大提升运行速度...
  2. 如何拥有(建)一个自己的网站-服务器建站
  3. go vs java基准测试_你如何使用go(golang)gocheck测试框架的基准标志?
  4. matlab实验求不定积分函数,实验四用matlab计算积分
  5. 细讲前端设置cookie, 储存用户登录信息
  6. python屏幕截图并保存_用Python保存屏幕截图(不使用PIL)
  7. 100 道 JavaScript 面试题及答案
  8. (附源码)SSM学习和分享做菜web系统JAVA计算机毕业设计项目
  9. JSP-Servlet实现网上BBS项目小案例
  10. 严澜:创业公司如何实施敏捷开发