第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是与继承相关的专用转换关键字。

时间: 2024-08-04 08:45:35

第55课 经典问题解析(四)的相关文章

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

一.new和malloc.delete和free之间的区别 1.new和malloc (1).new关键字是c++的一部分             malloc是c库提供的函数 (2).new是以具体类型为单位分配内存             malloc是以字节为单位分配内存 (3).new在申请内存空间时可进行初始化 malloc仅根据需要申请定量的内存空间 (4).new在所有c++编译器中都被支持             malloc在某些系统开发中是不能调用的 (5).new能触发构造

第67课 经典问题解析五

1. 指针的判别 (1)拾遗 ①C++中仍然支持C语言中的可变参数函数 ②C++编译器的匹配调用优先级:重载函数>函数模板>变参函数 (2)思路 ①将变量分为两类:指针 VS 非指针 ②编写函数: 指针变量调用时回true 非指针变量调用时返回false (3)函数模板与变参函数的化学反应 template<typename T> //优先匹配函数模板 bool IsPtr(T* v) // match pointer { return true; } //变参函数 //再匹配变参

经典问题解析四

new关键字与malloc函数的区别 new关键字是c++的一部分 malloc是由c库提供的函数 new以具体类型为单位进行内存分配 malloc以字节为单位进行内存分配 new在申请内存空间时可进行初始化 malloc仅根据需要申请定量的内存空间 构造函数不可能成为虚函数 在构造函数执行结束后,虚函数表指针才会正确的初始化 析构函数可以成为虚函数 建议在设计类时将析构函数声明为虚函数 构造函数中不可能发生多态行为 在构造函数执行时,虚函数表指针未被正确初始化 析构函数中不可能发生多态行为 在

第24课 经典问题解析二

关于析构的疑问: 当程序中存在多个对象的时候,如何确定这些对象的析构顺序? 多个对象析构时,析构顺序与构造顺序相反. 假设构造三个对象a.b.c,则程序结束时,析构顺序为c.b.a. 程序示例: 1 #include <stdio.h> 2 3 class Member 4 { 5 const char* ms; 6 public: 7 Member(const char* s) 8 { 9 printf("Member(const char* s): %s\n", s);

C++--第24课 - 专题四经典问题解析

第24课 - 专题四经典问题解析 1. 历史的痕迹 #include <cstdlib> #include <iostream> using namespace std; template<class T>  //以前是用typename定义,现在是用class定义 T Minus(T a, T b) { return a - b; } template<class T>  //类模板 class Add { public: T add(T a, T b)

C++--第14课 - 专题二经典问题解析

第14课 - 专题二经典问题解析 1. malloc与free和new与delete有什么区别? malloc和free是函数,new和delete是关键字. #include <cstdlib> #include <iostream> using namespace std; class Test { private: int i; public: Test() { cout<<"Test()"<<endl; i = 0; } Test

马程序员学习笔记——红黑树解析四

---------------------- ASP.Net+Unity开发..Net培训.期待与您交流! ---------------------- 本篇是将上面三篇的理论知识转化成代码,java实现 首先,看一下算法导论里的伪代码 一.左旋 The pseudocode for LEFT-ROTATE assumes that right[x] ≠ nil[T] and that the root's parent is nil[T].(伪代码的左旋方法中假设X的右孩子不为空) LEFT-

销售圣经的55句经典语

相信你的公司,相信你的产品,相信你自己,否则销售不会成功 1.人们不喜欢被推销,但却热衷于购买. 2.成交之前,一切为零. 3.永远都会有销售发生,不是你通过“是”把什么卖给了顾客,就是顾客通过“不”把什么卖给了你. 4.条件一样,人们想和朋友做生意,条件不一样,人们还是想要和朋友做生意.成功. 7.从拒绝你的人那里学到东西,每一个反对意见最后都会变成他必定要购买的理由. 5.相信你的公司,相信你的产品,相信你自己,否则你的销售不会成功. 6.问题之于销售,如同呼吸之于生命.如果没有提出问题,你

Android Bitmap 全面解析(四)图片处理效果对比 ...

对比对象: UIL Volley 官方教程中的方法(此系列教程一里介绍的,ImageLoader的处理方法和官方的差不多) ------------------------------------------------------------------------ 首先单张图片的压缩处理,也是分析重点 专门撸了一个小demo(结尾会放出下载连接)将对应计算方法copy了出来,然后计算了几十组数据,进行了对比 原图宽高都是一个10000以内的随机整数,限定大小是400 200,然后进行压缩处理