今天收到盛大的面试,问我一个问题,关于派生类中如何初始化基类对象,我在想派生类对于构造函数不都是先构造基类对象,然后在构造子类对象,但是如果我们在成员初始化列表先初始化派生类的私有成员,在函数内去调用基类的构造函数,能编译通过吗?或者当我们定义了基类的默认构造函数,而没有去在派生类的构造函数中显示的去调用基类的构造函数,会出现什么状况,我想派生类肯定会自动去调用基类的默认构造函数,那么析构函数又怎么样呢?我们都知道派生类的析构函数会先被调用,然后基类的析构函数后被调用,但是我不知道我们是否需要在派生类的析构函数中显示的去调用基类的析构函数吗?这个有待我去验证。

代码一:在派生类中成员初始化列表先初始化派生类的私有成员,不显示的调用基类的构造函数

#include <iostream>
using namespace std; class Base
{
private:
int n; public:
Base(int m):n(m){ cout<<"constructor is called\n";}
~Base(){}
}; class Derive:public Base
{
private:
int n; public:
Derive(int m):n(m)
{
}
~Derive(){}
}; int main()
{
Derive* a = new Derive(10);return 0;
}

结果:编译错误,error C2512: “Base”: 没有合适的默认构造函数可用

代码二:在派生类中成员初始化列表先初始化派生类的私有成员,显示的调用基类的构造函数

#include <iostream>
using namespace std; class Base
{
private:
int n; public:
Base(){ cout<<"default constructor is called\n"; n = 8;}
Base(int m):n(m){ cout<<"constructor is called\n";}
~Base(){}
}; class Derive:public Base
{
private:
int n; public:
Derive(int m):Base(m),n(m)
{
}
~Derive(){}
}; int main()
{
Derive* a = new Derive(10);return 0;
}

运行结果:

代码三:在派生类中成员初始化列表先初始化派生类的私有成员,不显示的调用基类的构造函数,则会调用默认的构造函数

#include <iostream>
using namespace std; class Base
{
private:
int n; public:
Base(){ cout<<"default constructor is called\n"; n = 8;}
Base(int m):n(m){ cout<<"constructor is called\n";}
~Base(){}
}; class Derive:public Base
{
private:
int n; public:
Derive(int m):n(m)
{
}
~Derive(){}
}; int main()
{
Derive* a = new Derive(10);
return 0;
}

运行结果:

代码四:派生类析构函数的调用过程中会不会自动去调用基类的析构函数呢?答案是,肯定的,所以千万不要在派生类的析构函数中再去调用基类的析构函数,这种去释放已经释放的内存,系统是不允许的。

#include <iostream>
using namespace std; class Base
{
private:
int n; public:
Base(){ cout<<"default constructor is called\n"; n = 8;}
Base(int m):n(m){ cout<<"constructor is called\n";}
~Base(){ cout<<"Base distructor is called\n"; }
}; class Derive:public Base
{
private:
int n; public:
Derive(int m):Base(m),n(m)
{
}
~Derive(){ cout<<"Derive distructor is called\n"; }
}; int main()
{
Derive* a = new Derive(10);
delete a;
return 0;
}

运行结果:

代码5:如果我们去试试在派生类的析构函数中去调用基类的析构函数,看看结果如何?当我想这么做的时候,我突然发现这个代码我写出来,因为我们都知道,对于C++的一个对象要么将对象分配在栈中,要么将对象分配在堆中,而对于分配在栈中的对象我们过程结束后,自动调用类的析构函数,而分配在堆中的对象,得我们去delete,但是你必须拿到指向对象在堆中内存的句柄,也就是指针,但是我发现不可能拿得到,除非你在派生类的构造函数中去new基类对象,但是又有个问题,在派生类构造函数中去new出这个基类对象,那么基类对象是派生类的局部变量,还是派生类继承而来的呢?我发现肯定是派生类的局部变量,那么也就是说,如果new出一个派生类对象,那么派生类本身的私有成员是在堆中,而继承而来的属性,也就是基类的东西分配的栈中,好吧,这样,难道派生对象在内存上竟然不是连续的?

#include <iostream>
using namespace std; class Base
{
private:
int n; public:
Base(){ cout<<"default constructor is called\n"; n = 8;}
Base(int m):n(m){ cout<<"constructor is called\n";}
~Base(){ cout<<"Base distructor is called\n"; }
}; class Derive:public Base
{
private:
int n; public:
Derive(int m):Base(m),n(m) // 在这里构造继承属性,即派生类的基类部分
{
new Base(m); //这个仅仅在派生类中创建了一个基类的变量而已
}
~Derive(){ cout<<"Derive distructor is called\n"; }
}; int main()
{
Derive* a = new Derive(10);
delete a;
return 0;
}

运行结果如下:

构造两次基类,一次构造派生类中的基类成分,还有一次仅仅是派生类的变量而已。同时析构派生类,自动调用基类析构函数。

代码六:在派生类构造函数中用new分配一个基类对象,然后析构掉,在main函数中去调用基类的成员函数,发现仍可调度,说明在派生类构造函数中用new分配基类对象,不是派生类的基类成分。

#include <iostream>
using namespace std; class Base
{
private:
int n; public:
Base(){ cout<<"default constructor is called\n"; n = 8;}
Base(int m):n(m){ cout<<"constructor is called\n";}
void get(){ cout<<"get() is called\n"; }
~Base(){ cout<<"Base distructor is called\n"; }
}; class Derive:public Base
{
private:
int n; public:
Derive(int m):Base(m),n(m) // 在这里构造继承属性,即派生类的基类部分
{
Base* a =new Base(m); //这个仅仅在派生类中创建了一个基类的变量而已
delete a;
}
~Derive(){ cout<<"Derive distructor is called\n"; }
}; int main()
{
Derive* a = new Derive(10);
a->get();
delete a;
return 0;
}

运行如下:

下面我就有一个疑问了,派生类中只能将基类成分分配在栈中吗?而如果去new出派生类对象,那么岂不内存不连续了,这是个问题?我验证了一下,与我的想法并非一致,内存还是连续的。只不过在派生类构造基类的过程中(new出派生类方式),只是在堆中去分配基类的成分,虽然使用非new来创建基类对象。

代码如下:

#include <iostream>
using namespace std; class Base
{
public:
int n; public:
Base(){ cout<<"default constructor is called\n"; n = 8;}
Base(int m):n(m){ cout<<"constructor is called\n";}
void get(){ cout<<"get() is called\n"; }
~Base(){ cout<<"Base distructor is called\n"; }
}; class Derive:public Base
{
private:
int n; public:
Derive(int m):Base(m),n(m) // 在这里构造继承属性,即派生类的基类部分
{
cout<<"Base address: "<<&(Base::n)<<endl; //地址
cout<<"Derive address: "<<&n<<endl; //地址
}
~Derive(){ cout<<"Derive distructor is called\n"; }
}; int main()
{
Derive* a = new Derive(10);
delete a;
return 0;
}

运行结果如下:

以上可以看出地址连续,说明派生类中基类成分和派生类成分地址是连续的。

--------------------- 本文来自 rongwenbin 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/rongwenbin/article/details/19480425?utm_source=copy

C++派生类中如何初始化基类对象(五段代码)的更多相关文章

  1. 关于在C#中对类中的隐藏基类方法和重写方法的理解

    最近在学习C#,在C#中的类看到重写和隐藏基类的方法这些概念.才开始感觉自己不是很理解这些概念.也区分不开这些概念.通过自己的查找资料和练习后.慢慢的理解了类中的隐藏和重写这个概念.在C#中只有在基类 ...

  2. 正确理解Widget::Widget(QWidget *parent) :QWidget(parent)这句话(初始化列表中无法直接初始化基类的数据成员,所以你需要在列表中指定基类的构造函数)

    最近有点忙,先发一篇我公众号的文章,以下是原文. /********原文********/ 最近很多学习Qt的小伙伴在我的微信公众号私信我,该如何理解下面段代码的第二行QWidget(parent) ...

  3. C++中 引入虚基类的作用

    当某类的部分或全部直接基类是从另一个基类共同派生而来时,这直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝,同一个函数名有多个映射.可以使用作用 ...

  4. 【转载】 C++多继承中重写不同基类中相同原型的虚函数

    本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...

  5. 编写高质量代码改善C#程序的157个建议——建议128:考虑让派生类的名字以基类名字作为后缀

    建议128:考虑让派生类的名字以基类名字作为后缀 派生类的名字可以考虑以基类名字作为后缀.这带来的好处是,从类型的名字上我们就知道它包含在哪一个继承体系中. Exception及其子类就是这样一个典型 ...

  6. 实现Square类,让其继承自Rectangle类,并在Square类增添新属性和方法,在2的基础上,在Square类中重写Rectangle类中的初始化和打印方法

    实现Square类,让其继承自Rectangle类,并在Square类增添新属性和方法,在2的基础上,在Square类中重写Rectangle类中的初始化和打印方法 #import <Found ...

  7. 四、spring集成ibatis进行项目中dao层基类封装

    Apache iBatis(现已迁至Google Code下发展,更名为MyBatis)是当前IT项目中使用很广泛的一个半自动ORM框架,区别于Hibernate之类的全自动框架,iBatis对数据库 ...

  8. moviepy音视频剪辑:moviepy中的剪辑基类Clip的属性和方法详解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一. ...

  9. WPF 之 创建继承自Window 基类的自定义窗口基类

    开发项目时,按照美工的设计其外边框(包括最大化,最小化,关闭等按钮)自然不同于 Window 自身的,但窗口的外边框及窗口移动.最小化等标题栏操作基本都是一样的.所以通过查看资料,可按如下方法创建继承 ...

随机推荐

  1. 牛客练习赛19 -E-托米的饮料

    题目描述 好了,现在是小托米的故事啦~~~ 可爱的小托米得到了n瓶饮料. 但他不小心把开盖的工具弄丢了,所以他只能利用饮料瓶来开盖. 已知第i个瓶子的品牌为ai,且其能打开bi品牌的瓶子. 问有几瓶饮 ...

  2. Java基础12一IO流

    1.IO流的原理 利用数据通道实现程序和数据源之间数据的的读写操作.  2.IO流分类 输入流.输出流.字节流.字符流.节点流.过滤流  3.InputStream 字节输入流 实现类FileInpu ...

  3. 通过getSystemServices获取手机管理大全

    getSystemService是Android很重要的一个API,它是Activity的一个方法,根据传入的NAME来取得对应的Object,然后转换成相应的服务对象.以下介绍系统相应的服务. 传入 ...

  4. layer自定义弹窗样式

    1.下载并引用js, 官网http://layer.layui.com/ 文档http://www.layui.com/doc/modules/layer.html <link href=&qu ...

  5. vue系列---identify(生成图片验证码)插件

    identify 这是一个vue的插件,使用canvas来生成图形验证码. 具体参数如下: identify.vue组件(主要用于定义参数和方法) <template> <div c ...

  6. Mysql [Err] 1118 - Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535.

    对于越来越多的数据,数据库的容量越来越大,压缩也就越来越常见了.在我的实际工作中进行过多次压缩工作,也遇到多次问题,在此和大家分享一下. 首先,我们先说说怎么使用innodb的压缩. 第一,mysql ...

  7. 【剑指Offer】50、数组中重复的数字

      题目描述:   在一个长度为n的数组里的所有数字都在0到n-1的范围内. 数组中某些数字是重复的,但不知道有几个数字是重复的.也不知道每个数字重复几次.请找出数组中任意一个重复的数字. 例如,如果 ...

  8. 51nod1134 最长递增子序列【动态规划】

    给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递增的) 例如:5 1 6 8 2 4 5 10,最长递增子序列是1 2 4 5 10. Input 第1行:1个数N ...

  9. 搭建rsync服务(端口号873)

    rsync详细参数 1.-v,--verbose 详细模式输出,传输是的进度信息 2.-z,--compress 传输是进行压缩以提高传输效率,--comperess -level = NUM可以按级 ...

  10. 爬虫系列(七) requests的基本使用

    一.requests 简介 requests 是一个功能强大.简单易用的 HTTP 请求库,可以使用 pip install requests 命令进行安装 下面我们将会介绍 requests 中常用 ...