【C++】 55_经典问题分析 四

关于动态内存分配

new 和 malloc 的区别是什么?
delete 和 free 的区别是什么?

  • new 关键字和 malloc 函数的区别

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

编程实验: new、delete 与 malloc、free

#include <iostream>
#include <cstdlib>

using namespace std;

class Test
{
private:
    int* mp;
public:
    Test()
    {
        cout << "Test::Test()" << endl;
        
        mp = new int(100);
    }
    ~Test()
    {
        delete mp;
        
        cout << "~Test::Test()" << endl;
    }
};

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

    delete pn;
    free (pm);

    return 0;
}
输出:
Test::Test()
~Test::Test()

分析:

new Test;                    ==> 在堆上创建一个对象,构造函数被调用
(Test*)malloc(sizeof(Test)); ==> 在堆上申请 sizeof(Test) 大小内存,构造函数未被调用,对象未正常创建

delete pn; ==> 销毁对象, 归还内存,析构函数被调用
free(pm);  ==> 仅归还内存,析构函数未被调用

当 delete 与 free 混用,会发生什么呢?

delete --> free :

int main()
{
    Test* pn = new Test;

    free (pn);          // 注意这里!

    return 0;
}
输出:
Test::Test()

分析:
free 可以释放 new 申请的堆空间,但析构函数未被调用,对象未正常销毁(实例中,导致系统资源泄漏!!)

free --> delete :

int main()
{
    Test* pm = (Test*)malloc(sizeof(Test));
      
    delete pm;         // 注意这里!

    return 0;
}
输出:
~Test::Test()

分析:
delete 可以释放 malloc 申请的堆空间,不合法对象的析构函数被调用!运行结果将是不确定的!(示例中,将delete一个野指针指向的内存空间)

结论: C++ 中杜绝 malloc、 free 的使用

  • new 和 malloc 的区别

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

  • delete 和 free 的区别

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

关于虚函数

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

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

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

    • 析构函数在对象销毁之前被调用,意味着虚函数表指针仍然正确的指向虚函数表
    • 建议在设计类时将析构函数声明为虚函数

编程实验: 构造,析构,虚函数

test_1.cpp

#include <iostream>

using namespace std;

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

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

int main()
{
    Base* p = new Derived();    // 注意这里!
    
    // ...
    
    delete p;

    return 0;
}
输出:
Base()
Derived()
~Base()

分析: 
为什么 子类 的析构函数没有被调用呢?

Base* p = new Derived(); ==> 因为赋值兼容性,编译通过。
delete p;                ==> 编译器考虑安全性,根据指针类型进行对象销毁

析构函数声明为虚函数的意义 1:test_2.cpp

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual void func()
    {
        cout << "Base::func()" << endl;
    }
    virtual ~Base()               // 注意这里!
    {
        cout << "~Base()" << endl;
    }
};

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

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

    return 0;
}
输出:
Base()
Derived()
~Derived()
~Base()

分析:
当析构函数为虚函数时, delete p; 将根据 p 指向的实际对象决定如何调用析构函数。

析构函数发生多态行为,保证系统资源尽可能得到释放!

当声明构造函数为虚函数时,g++ 报错: virtual Base() { }
error: constructors cannot be declared virtual   

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

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

    • 在构造函数执行时,虚函数表指针未正确初始化
  • 析构函数中不可能发生多态行为

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

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

关于继承中的强制类型转换

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

  • dynamic_cast 是与继承相关的类型转换关键字
  • dynamic_cast 要求相关的类中必须有虚函数
  • 用于直接或间接继承关系的指针(引用)之间

    • 指针:

      • 转换成功: 得到目标类型指针
      • 转换失败: 得到一个空指针
    • 引用:

      • 转换成功: 得到目标类型引用
      • 转换失败: 得到一个异常操作信息

  • 编译器会检查 dynamic_cast 的使用是否正确
  • 类型转换的结果只可能在运行阶段才能得到

编程实验: dynamic_cast 的使用

#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }
    virtual ~Base()
    {
        cout << "~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;
}
输出:
Base()
Cast error!
~Base()

析构函数声明为虚函数的意义 2 :
析构函数被声明为虚函数,保证 dynamic_cast 关键字可以被支持,而无需单独刻意定义其它虚成员函数

小结

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

以上内容参考狄泰软件学院系列课程,请大家保护原创!

阅读 487

推荐阅读

C语言进阶;进入C++的世界;数据结构编写高质量代码。

26 人关注
216 篇文章
专栏主页