第55课 经典问题解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第55课 经典问题解析相关的知识,希望对你有一定的参考价值。

1. 关于动态内存分配

(1)new和malloc的区别

区别

new

malloc

是否是关键字

关键字,是C++的一部分。被所有的C++编译器支持。

由C库提供的函数,注意是函数,而不是关键字,要依赖C库(cstdlib),在某些系统开发(如嵌入式)中可能不能调用。

分配单位

具体类型为单位

字节为单位

内存初始化

申请时可进行初始化

仅根据需要申请定量的内存,但并不进行初始化

是否触发构造函数的调用

会触发。对象的创建只能使用new

不会,仅分配需要的内存空间,因此不适合面向对象开发

(2)delete/free的区别

区别

delete

free

是否是关键字

关键字,是C++的一部分。被所有的C++编译器支持。

由C库提供的函数。在某些系统开发(如嵌入式)中可能不能调用。

是否触发析构函数的调用

会触发.对象的销毁只能使用delete

不会,仅归之前分配的内存空间,因此不适合面向对象开发

【编程实验】new和malloc的区别

#include <iostream>
#include <string>
#include <cstdlib> //for malloc\free函数。同时说明这两者都是函数,而不是关键字!
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)); //不会调用构造函数
    
    delete pn; //会调用析构函数。如果这里混用free,则会造成内存泄漏!
    
    free(pm);  //如果这里误用delete来释放pm所指的空间时,会同时调用
               //析构函数,但因mp指针所指空间是未知,出现误删除的bug。
   
    return 0;
}

2. 关于虚函数

(1)构造函数:

  ①构造函数本身不可能成为虚函数,因为在构造函数执行结束后,虚函数表指针才被正确的初始化。

  ②在构造函数中调用其他虚函数时,不会发生多态。因为在构造函数执行期间,虚函数表指针未被正确初始化。所以当调用虚函数时,只会调用本类中定义的那个函数版本。

(2)析构函数:

  ①析构函数本身可以成为虚函数,建议在设计类时将析构函数声明为虚函数,特别是要作为父类的类。

  ②析构函数中调用其他虚函数时,不会发生多态,因为析构阶段虚函数表指针己经被销毁。所以当调用虚函数时,只会调用本类中定义的那个函数版本,即静态绑定了。

【编程实验】构造、析构和虚函数

#include <iostream>
#include <string>

using namespace std;

class Base
{
public:
    Base()  //不能成为虚函数,哪怕加了virtual;
    {
        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(); //注意是父类指针,如果这里直接声明为Derived* p = new  Derived()
                             //则delete p直接调用子类的构造函数。但我们本例的目的之一是为了演示
                             //析构函数的多态,所以声明为父类的针。
    
    //...
    cout << endl;
    
    delete p; //delete会调用析构函数。从这行代码看,如果父类中析构函数没被声明为虚函数的话,
              //delete一个父类的指针,由于静态绑定,当然调用的是父类的析构函数,此时会造成
              //Derived的析构函数没被调用。当然,如果父类中析构函数被声明为虚函数,根据多态
              //原理,会调用子类的析构函数,又因析构的特点,会自动先调用父类析构,再调用子类
              //自己的析构函数,从而正确的释放内存。
   
    return 0;
}

/*输出结果
Base()
Base::func()     //注意,并没有发生多态
Derived()
Derived::func()  //注意,并没有发生多态

Derived::func()  //注意,并没有发生多态
~Derived()
Base::func()     //注意,并没有发生多态
~Base()
*/

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

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

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

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

 

指针间的转换

引用间的转换

转换成功

得到目标类型的指针

得到目标类型的引用

转换失败

得到一个空指针

得到一个异常操作信息

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

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

【编程实验】dynamic_cast的使用

#include <iostream>
#include <string>

using namespace std;

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

class Derived: public Base
{
public:

};

int main()
{
    Base* p = new Base;
    
    //将用父类指针来初始化子类指针(为演示之用,本身这样用就是不完全的!)
    //注意,这里也提醒我们,可以用dynamic_cast来判断一个类是不是另一个类父类的方法。
    //     具体方法Base* p = dynamic_cast(Base* p)(pDerived);即,子类能否转为父类。
    //     如果返回值不为NULL,表示有父子关系。否则没有。
    Derived* pd = dynamic_cast<Derived*>(p); //注意p所指的类中一定要有虚函数
                                             //本例中,析构函数声明为虚函数。
                                                                                
    if(pd != NULL) //转换成功。
    {           
        cout << "pd = " << pd << endl;
    }
    else           //转换失败,返回空指针NULL
    {
        cout << "Cast Error!" << endl;
    }
    
    delete p;
    
    return 0;
}

/*输出结果
Base::Base()
Cast Error!
Base::~Base()
*/

4. 小结

(1)new/delete会触发构造函数或析构函数的调用

(2)构造函数不能成为虚函数,析构函数可以成为虚函数。

(3)构造函数和析构函数中都无法产生多态行为

(4)dynamic_cast是与继承相关的专用转换关键字。

以上是关于第55课 经典问题解析的主要内容,如果未能解决你的问题,请参考以下文章

第五十五课经典问题解析四

第12课 经典问题解析一

第24课 经典问题解析二

第24课经典问题解析(下)--------类的成员函数和成员变量隶属某个具体对象吗

第六十七课经典问题解析五

第42课 内存操作经典问题分析二