• c++ 浅拷贝和深拷贝 指针和引用的区别 malloc(free)和new(delete)的区别 重载重写重定义


    4.malloc(free)和new(delete)的区别

      malloc()函数:

            1.1 malloc的全称是memory allocation,中文叫动态内存分配。

            原型:extern void *malloc(unsigned int num_bytes);

            说明:分配长度为num_bytes字节的内存块。如果分配成功则返回指向被分配内存的指针,分配失败返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。

            1.2 void *malloc(int size);

            说明:malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针

            备注:void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者…)

            1.3 free

            void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存。

            1.4注意事项

                  1)申请了内存空间后,必须检查是否分配成功。

                  2)当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

                  3)这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。

                  4)虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

            1.5 malloc()到底从哪里得到了内存空间?

                  答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

        new运算符:

            new的实现分两步:1.分配内存,2.调用构造对象

            下面是一个实现:

    class T{
    public:
        T(){
            cout << "构造函数。" << endl;
        }
    
        ~T(){
            cout << "析构函数。" << endl;
        }
    
        void * operator new(size_t sz){
    
            T * t = (T*)malloc(sizeof(T));
            cout << "内存分配。" << endl;
    
            return t;
        }
    
        void operator delete(void *p){
    
            free(p);
            cout << "内存释放。" << endl;
    
            return;
        }
    };
    
    int main()
    {
        T * t = new T(); // 先 内存分配 ,再 构造函数
    
        delete t; // 先 析构函数, 再 内存释放
    
        return 0;
    }

    new操作符调用一个函数来完成必需的内存分配,你可以重写或重载这个函数来改变它的行为。new操作符为分配内存所调用函数的名字是operator new。

    函数operator new 通常这样声明:

    void * operator new(size_t size);

    返回值类型是void*,由于这个函数返回一个未经处理(raw)的指针。未初始化的内存。(假设你喜欢。你能写一种operator new函数,在返回一个指针之前可以初始化内存以存储一些数值,可是一般不这么做。)參数size_t确定分配多少内存。你能添加额外的參数重载函数operator new,可是第一个參数类型必须是size_t。

            

            2.1 C++中,用new和delete动态创建和释放数组或单个对象。

              动态创建对象时,只需指定其数据类型,而不必为该对象命名,new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。    

    int *pi=new int; 

              这个new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi 。

            2.2 动态创建对象的初始化

              动态创建的对象可以用初始化变量的方式初始化。

    int *pi=new int(100); //指针pi所指向的对象初始化为100
    string *ps=new string(10,’9’);//*ps 为“9999999999”

              如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。

              也可以对动态创建的对象做值初始化:

    int *pi=new int( );//初始化为0
    int *pi=new int;//pi 指向一个没有初始化的int
    string *ps=new string( );//初始化为空字符串 (对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化)

            2.3 撤销动态创建的对象

              delete表达式释放指针指向的地址空间。

    delete pi ;// 释放单个对象
    delete [ ]pi;//释放数组

              如果指针指向的不是new分配的内存地址,则使用delete是不合法的。

            2.4 在delete之后,重设指针的值

              delete p; //执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所

              以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。一旦删除了

              指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *ip=0;)

      malloc和new的区别

             1.malloc和free是库函数,而new和delete是C++操作符

               2.new 返回指定类型的指针,并且可以自动计算所需要大小;而 malloc 则必须要由我们计算字节数,返回类型是 void* 类型,即未确定类型的指针,返回后需强行转换为实际类型的指针。      

    int* parr;   
    parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;
    
    int* p;   
    p = (int *) malloc (sizeof(int)*128);//分配128个(可根据实际需要替换该数值)整型存储单元,并将这128个连续的整型存储单元的首地址存储到指针变量p中 
    double *pd=(double *) malloc (sizeof(double)*12);//分配12个double型存储单元,并将首地址存储到指针变量pd中

            3. malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的

            4.分配失败:new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

            5.new在动态分配内存的时候可以初始化对象,调用其构造函数,delete在释放内存时调用对象的析构函数。而malloc只分配一段给定大小的内存,并返回该内存首地址指针

            6.C++允许重载new/delete操作符,而malloc不允许重载。

            7.内存区域:new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。

            8.对数组的处理

             C++提供了new[]与delete[]来专门处理数组类型:

              A * ptr = new A[10];//分配10个A对象

             new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。至于malloc,它并知道你在这块内存上要放的数组还是啥

             别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:

              int * ptr = (int *) malloc( sizeof(int) );//分配一个10个int元素的数组

            9.能否被重载:opeartor new /operator delete可以重载,而malloc不行

     

      下面是一个new调用构造函数和析构函数的例子:

    #include <iostream>
    using namespace std;
    
    class Player{
    public:
        Player(){
            cout << "call Player::ctor
    ";
        }
    
        ~Player(){
            cout << "call Player::dtor
    ";
        }
    
        void Log(){
            cout << "i am player
    ";
        }
    
    };
    
    int main(){
        cout << "Initiate by new
    ";
        Player* p1 = new Player();
        p1->Log();
        delete p1;
    
        cout << "Initiate by malloc
    ";
        Player* p2 = (Player*)malloc(sizeof(Player));
        p2->Log();
        free(p2);
    }
     
    
    输出结果为:
    
    Initiate by new
    
    call Player::ctor
    
    i am player
    
    call Player::dtor
    
    Initiate by malloc
    
    i am player

      有了malloc/free为什么还要new/delete?

            1) 对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

            2) 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

    http://blog.jobbole.com/109234/

    https://blog.csdn.net/nie19940803/article/details/76358673

    5.指针和引用的区别

    a.引用:引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字,因此内存不会为引用开辟新的空间存储这个引用,而是和目标变量共同指向目标变量的内存地址。

        引用使用方式:

            int a;

            int &ra=a;

        注意:

        1.引用的类型必须和其所绑定的变量的类型相同

        2.声明引用的同时必须对其初始化,否则系统会报错

        3.引用相当于变量或对象的别名,因此不能再将已有的引用名作为其他变量或对象的名字或别名

        引用变量的主要用途是用作函数的形参

        运用:

        1、引用作为参数

            使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,加const。

        2、常引用

        常引用声明方式:const  类型标识符  &引用名 = 目标变量名;

        用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。

        3、引用和多态

        引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。

        class  A;

        class  B:public  A{ ... ... }

        B  b;

        A  &Ref = b;//用派生类对象初始化基类对象的引用

        Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。

    b.指针和引用的区别

        指针:

           指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

        相同点:

            都是地址的概念,指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

        不同点:        

            1.指针是一个实体,而引用仅是个别名;

                2.引用必须被初始化,指针不必;引用只能在定义时被初始化一次,之后不可变;指针可以改变所指的对象

            3. 引用不可以初始化为空(NULL);但指针可以初始化为空。

            4. ++运算符的含义不一样。引用的++是值的增加;指针的++是地址的增加

            5.程序为指针变量分配内存区域,而引用不需要分配内存区域

            6.“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;

    6.浅拷贝和深拷贝

    浅拷贝:只增加了一个指针,指向已存在对象的内存

    深拷贝:增加了一个指针,同时开辟了一块空间,让指针指向这块新开辟的空间

    浅拷贝:在多个对象指向一个空间的时候,释放一个空间会导致其他对象所使用的空间也被释放掉

    https://blog.csdn.net/qq_27011361/article/details/79518057

    16.重写,重载,重定义有什么区别

    a.重载:重载是应用于相同作用域之内的同名函数,由于参数列表不同而产生的不同的实现方法

    想要构成重载函数必须要满足以下几个条件:

                       1.作用域相同(比如同一个类中); 
                       2.函数名相同; 
                       3.参数列表不同(参数个数、参数类型不同或者参数顺序不同); 

    注意:

      1.若一个重载版本的函数面前有virtual修饰,则表示他是虚函数,但他也是属于重载的一个版本

      2.不同的构造函数(无参构造、有参构造、拷贝构造)是重载的应用

      3.未体现多态

      4.函数返回值的类型可以相同,也可以不相同

    b.重写:也叫做覆盖,一般发生在子类和父类继承关系之间,子类重新定义父类中有相同名称和参数的虚函数,重写是C++中实现多态这个特性基础

    重写需要注意:

      1.被重写的函数不能是static的,必须是virtual的

      2.重写函数必须有相同的类型,名称和参数列表

      3.重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public、protect也是可以的

      4.作用域不同,分别位于基类和派生类

    c.重定义:也叫隐藏,子类重新定义父类中有相同名称的非虚函数,参数列表可以相同可以不同,会覆盖其父类的方法,未体现多态

    重定义需要注意:

            1.不在同一个作用域(分别位于基类、派生类)

            2.函数的名字必须相同

            3.对函数的返回值、形参列表无要求

            4.如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏

            5.如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual,此时,基类的函数被隐藏;有virtual,就是重写了

  • 相关阅读:
    python有哪些关键字?让他自己“吐”出来!
    jquery获取表单元素与回显
    前端开发笔记--flex布局
    Vue-Quill-Editor回显不显示空格的处理办法
    react项目中antd组件库的使用需要注意的问题
    React Native 列表的总结
    是时候了解React Native了
    Android 整合实现简单易用、功能强大的RecyclerView
    linux centos 一键安装环境
    推翻自己和过往,重学自定义View
  • 原文地址:https://www.cnblogs.com/ymjyqsx/p/9703063.html
Copyright © 2020-2023  润新知