C++解析(25):关于动态内存分配、虚函数和继承中强制类型转换的疑问

0.目录

1.动态内存分配

  • 1.1 new和malloc的区别
  • 1.2 delete和free的区别

2.虚函数

  • 2.1 构造函数与析构函数是否可以成为虚函数?
  • 2.2 构造函数与析构函数是否可以发生多态?

3.继承中的强制类型转换

4.小结

1.动态内存分配

1.1 new和malloc的区别

new关键字与malloc函数的区别:

  • new关键字是C++的一部分
  • malloc是由C库提供的函数
  • new以具体类型为单位进行内存分配
  • malloc以字节为单位进行内存分配
  • new在申请内存空间时可进行初始化
  • malloc仅根据需要申请定量的内存空间

下面的代码输出什么?为什么?

示例——new和malloc的区别:

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test::Test()" << endl;
    }
};

int main()
{
    Test* pn = new Test;
    Test* pm = (Test*)malloc(sizeof(Test));

    return 0;
}

运行结果为:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Test::Test()

new和malloc的区别:

  • new在所有C++编译器中都被支持
  • malloc在某些系统开发中是不能调用
  • new能够触发构造函数的调用
  • malloc仅分配需要的内存空间
  • 对象的创建只能使用new
  • malloc不适合面向对象开发

1.2 delete和free的区别

下面的代码输出什么?为什么?

示例——delete和free的区别:

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
    int* mp;
public:
    Test()
    {
        cout << "Test::Test()" << endl;

        mp = new int(100);

        cout << *mp << endl;
    }
    ~Test()
    {
        delete mp;

        cout << "~Test::Test()" << endl;
    }
};

int main()
{
    Test* pn = new Test;
    Test* pm = (Test*)malloc(sizeof(Test));

    free(pn);
    free(pm);

    return 0;
}

运行结果为:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Test::Test()
100

可以看到,free不会触发析构函数,会造成内存泄漏!

delete和free的区别:

  • delete在所有C++编译器中都被支持
  • free在某些系统开发中是不能调用
  • delete能够触发析构函数的调用
  • free仅归还之前分配的内存空间
  • 对象的销毁只能使用delete
  • free发不适合面向对象开

2.虚函数

2.1 构造函数与析构函数是否可以成为虚函数?

构造函数是否可以成为虚函数?析构函数是否可以成为虚函数?

构造函数不可能成为虚函数:

  • 在构造函数执行结束后,虚函数表指针才会被正确的初始化

析构函数可以成为虚函数:

  • 建议在设计类时将析构函数声明为虚函数

示例——不把析构声明为虚函数:

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }

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

class Derived : public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
    }

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

int main()
{
    Base* p = new Derived();

    cout << endl;

    delete p;

    return 0;
}

运行结果为:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Base()
Derived()

~Base()

由于没有把析构声明为虚函数,因此,编译器直接根据指针p的类型来决定调用哪一个析构函数,又由于指针p的类型是父类的,所以编译器认为直接调用父类的析构函数就可以了。

示例——把析构声明为虚函数:

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }

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

class Derived : public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
    }

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

int main()
{
    Base* p = new Derived();

    cout << endl;

    delete p;

    return 0;
}

运行结果为:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Base()
Derived()

~Derived()
~Base()

如果将构造函数声明为虚函数,报错信息如下:

error: constructors cannot be declared virtual

2.2 构造函数与析构函数是否可以发生多态?

构造函数中是否可以发生多态?析构函数中是否可以发生多态?

构造函数中不可能发生多态行为:

  • 在构造函数执行时,虚函数表指针未被正确初始化

析构函数中不可能发生多态行为:

  • 在析构函数执行时,虚函数表指针已经被销毁

构造函数和析构函数中不能发生多态行为,只调用当前类中定义的函数版本!!

示例——构造函数和析构函数中不可能发生多态:

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
        func();
    }

    virtual void func()
    {
        cout << "Base::func()" << endl;
    }

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

class Derived : public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
        func();
    }

    virtual void func()
    {
        cout << "Derived::func()" << endl;
    }

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

int main()
{
    Base* p = new Derived();

    cout << endl;

    delete p;

    return 0;
}

运行结果为:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Base()
Base::func()
Derived()
Derived::func()

Derived::func()
~Derived()
Base::func()
~Base()

3.继承中的强制类型转换

继承中如何正确的使用强制类型转换?

dynamic_cast是与继承相关的类型转换关键字

dynamic_cast要求相关的类中必须有虚函数

用于有直接或者间接继承关系的指针(引用)之间

  • 指针:

    1. 转换成功:得到目标类型的指针
    2. 转换失败:得到一个空指针
  • 引用:
    1. 转换成功:得到目标类型的引用
    2. 转换失败:得到一个异常操作信息

编译器会检查dynamic_cast的使用是否正确

类型转换的结果只可能在运行阶段才能得到

示例——dynamic_cast的使用:

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }

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

class Derived : public Base
{
};

int main()
{
    Base* p = new Base;

    Derived* pd = dynamic_cast<Derived*>(p);

    if( pd != NULL )
    {
        cout << "pd = " << pd << endl;
    }
    else
    {
        cout << "Cast error!" << endl;
    }

    delete p;

    return 0;
}

运行结果为:

[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Base::Base()
Cast error!
Base::~Base()

4.小结

  • new / delete会触发构造函数或者析构函数的调用
  • 构造函数不能成为虚函数
  • 析构函数可以成为虚函数
  • 构造函数和析构函数中都无法产生多态行为
  • dynamic_cast是与继承相关的专用转换关键字

原文地址:https://www.cnblogs.com/PyLearn/p/10092188.html

时间: 2024-10-03 22:54:08

C++解析(25):关于动态内存分配、虚函数和继承中强制类型转换的疑问的相关文章

c++ 内存分配 虚函数实现多态等

看到了很好的解释,先mark下,有空整理. 虚函数实现多态与多重继承乌索普的回答 很有收获,毕竟我觉得多态才是面向对象的灵魂. 这样的设计也让人得以理解为什么虚函数会像成员变量一样与对象绑定. C++成员函数在内存中的存储方式 原文地址:https://www.cnblogs.com/zsl96/p/8732662.html

重拾c语言之动态内存分配

动态内存分配 传统数组的缺点: 1数组长度必须事先制定,且仅仅能是长整数不能是变量 2传统形式定义的数组该数组的内存程序无法手动释放 3数组一旦定义,系统就会为该数组分配的存储空间就会一直存在直到该函数执行结束 4数组的长度不能再函数的执行的过程中动态的扩充或者缩小 5不能跨函数使用 为什么须要动态分配内存 动态内存分配内存举例--动态数组的构造 静态内存分配和动态内存分配 跨函数使用内存的问题 #include<stdio.h> void f(void) { int a[5]={0,1,2,

Java静态内存与动态内存分配的解析

1. 静态内存 静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源. 程序中的各种变量,在编译时系统已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会 自动释放所占用的内存空间. 变量的分配与释放,都无须程序员自行考虑. eg:基本类型,数组 2. 动态内存 用户无法确定空间大小,或者空间太大,栈上无法分配时,会采用动态内存分配. 3. 区别 a) 静态内存分配在编译时完成,不占用CPU资源; 动态内存分配在运行时,分配与释放都占用

继承和动态内存分配

假设基类使用了动态内存分配,而且定义了析构函数.复制构造函数和赋值函数,但是在派生类中没有使用动态内存分配,那么在派生类中不需要显示定义析构函数.复制构造函数和赋值函数. 当基类和派生类采用动态内存分配时,派生类的析构函数.复制构造函数.赋值运算符都必须使用相应的基类方法来处理基类元素.这种要求是通过三种不同的方式来满足的.对于析构函数.这是自动完成的,也就是说在派生类的析构函数中无需显示调用基类的析构函数.对于构造函数,这是通过在初始化成员类别中调用基类的复制构造函数来完成的,如果不这样做,将

C++动态内存分配

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解>电子工业出版社等. CSDN视频网址:http://edu.csdn.net/lecturer/144 C / C ++中的动态内存分配是指程序员手动执行内存分配, 动态分配的内存分配给堆,非静态和局部变量获取在Stack上分配的内存.详情查看上篇博文:C程序的内存布局. 什么是应用程序? 动态分配的

SQLite剖析之动态内存分配

SQLite通过动态内存分配来获取各种对象(例如数据库连接和SQL预处理语句)所需内存.建立数据库文件的内存Cache.以及保存查询结果.我们做了很多努力来让SQLite的动态内存分配子系统可靠.可预测.健壮并且高效.本文概述SQLite的动态内存分配,软件开发人员在使用SQLite时可以据此获得最佳性能. 1.特性    SQLite内核和它的内存分配子系统提供以下特性:    (1)对内存分配失败的健壮处理.如果一个内存分配请求失败(即malloc()或realloc()返回NULL),SQ

28._动态内存分配

动态内存分配 传统数组的缺点(静态内存分配):   1.数组长度必须事先指定,且只能是常整数,不能是变量    例子:     int a[5]; //OK     int len = 5; int a[len]; //error   2.传统形式定义的数组,该数组的内存程序员无法手动编    程释放,在一个函数运行期间,系统为该函数中数组所     分配的空间会一直存在,直到该函数运行完毕时,数组    元素所占存储空间才会被系统释放 3.数组的长度一旦定义,长度就能被改变     4.A函数

C++二维数组动态内存分配

对于二维数组和二维指针的内存的分配 这里首选说一下一维指针和一维数组的内存分配情况. 一维: 数组:形如int  a[5];这里定义了一个一维数组a,并且数组的元素个数是5,这里的a是这五个元素的整体表示,也就是通过a我们能找到这五个元素.注意:a是代表数组第一个元素的首地址.&a是代表数组的地址,虽然它们的值相同. 指针: int *p = NULL:这里p是一个指针,它指向的是计算 机内一块存储int类型的内存.P = a;就是让p等于刚才申请的数组的第一个元素的地址.所以通过p我们也能找到

动态内存分配类实现

今天学习了C++语言的内存动态分配,并借助所学的知识实现了一个动态内存分配类. 问题的背景为:在已经实现了一个点类的基础上,实现一个动态内存分配类,这个类 的功能为:实现根据输入的数目size,动态的生成size个点类对象:并在提供一个借口 可以对pos位置的对象进行操作:在对象生存期结束时,可以自动释放分配的内存空间. 根据描述的问题描述的需求,规划了如下的类图: 写出了一个包含三个方法的对点动态分配内存的类: 1: #ifndef _ARRAYOFPOINTS_H 2: #define _A