zoukankan      html  css  js  c++  java
  • C++动态分配内存

    1.堆内存分配 :

    C/C++定义了4个内存区间:
        代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或自由存储区(free store)。
    堆的概念:
    通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。这种内存分配称为静态存储分配;
        有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配。所有动态存储分配都在堆区中进行。
    当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。
    2.堆内存的分配与释放
    堆空间申请、释放的方法:
    在C++中,申请和释放堆中分配的存贮空间,分别使用new和delete的两个运算符来完成:  

    指针变量名=new 类型名(初始化式);
             delete 指针名;
    例如:1、 int *pi=new int(0);
          它与下列代码序列大体等价:
          2、int ival=0, *pi=&ival;
    区别:pi所指向的变量是由库操作符new()分配的,位于程序的堆区中,并且该对象未命名。  
    堆空间申请、释放说明:
    ⑴.new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而且动态创建的对象本身没有名字。
    ⑵.一般定义变量和对象时要用标识符命名,称命名对象,而动态的称无名对象(请注意与栈区中的临时对象的区别,两者完全不同:生命期不同,操作方法不同,临时变量对程序员是透明的)。
    ⑶.堆区是不会在分配时做自动初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。new表达式的操作序列如下:从堆区分配对象,然后用括号中的值初始化该对象。
    3.堆空间申请、释放演示:
    ⑴.用初始化式(initializer)来显式初始化
    int *pi=new int(0);
    ⑵.当pi生命周期结束时,必须释放pi所指向的目标:
             delete pi;
    注意这时释放了pi所指的目标的内存空间,也就是撤销了该目标,称动态内存释放(dynamic memory deallocation),但指针pi本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放。
    下面是关于new 操作的说明
    ⑴.new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有名字。
       ⑵.一般定义变量和对象时要用标识符命名,称命名对象,而动态的称无名对象(请注意与栈区中的临时对象的区别,两者完全不同:生命期不同,操作方法不同,临时变量对程序员是透明的)。
    ⑶.堆区是不会在分配时做自动初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。new表达式的操作序列如下:从堆区分配对象,然后用括号中的值初始化该对象。
     
    4. 在堆中建立动态一维数组
    ①申请数组空间:
    指针变量名=new 类型名[下标表达式];
    注意:“下标表达式”不是常量表达式,即它的值不必在编译时确定,可以在运行时确定。
    ②释放数组空间:
    delete [ ]指向该数组的指针变量名;
    注意:方括号非常重要的,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。delete [ ]的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。
    #include <iostream.h>
    #include <string.h>
    void main(){
         int n;
         char *pc;
         cout<<"请输入动态数组的元素个数"<<endl;
         cin>>n; //n在运行时确定,可输入17
         pc=new char[n]; //申请17个字符(可装8个汉字和一个结束符)的内存空间
         strcpy(pc,“堆内存的动态分配”);//
         cout<<pc<<endl;
         delete []pc;//释放pc所指向的n个字符的内存空间
         return ; }
    5. 动态一维数组的说明
    ① 变量n在编译时没有确定的值,而是在运行中输入,按运行时所需分配堆空间,这一点是动态分配的优点,可克服数组“大开小用”的弊端,在表、排序与查找中的算法,若用动态数组,通用性更佳。一定注意:delete []pc是将n个字符的空间释放,而用delete pc则只释放了一个字符的空间;
    ② 如果有一个char *pc1,令pc1=p,同样可用delete [] pc1来释放该空间。尽管C++不对数组作边界检查,但在堆空间分配时,对数组分配空间大小是纪录在案的。
    ③ 没有初始化式(initializer),不可对数组初始化。

    6.指针数组和数组指针
    指针类型:
    (1)int*ptr;//指针所指向的类型是int
    (2)char*ptr;//指针所指向的的类型是char
    (3)int**ptr;//指针所指向的的类型是int* (也就是一个int * 型指针)
    (4)int(*ptr)[3];//指针所指向的的类型是int()[3] //二维指针的声明
    指针数组:
    一个数组里存放的都是同一个类型的指针,通常我们把他叫做指针数组。
    比如 int * a[2];它里边放了2个int * 型变量 .
    int * a[2];
    a[0]= new int[3];
    a[1]=new int[3];
    delete a[0];
    delete a[1];
    注意这里 是一个数组,不能delete [] ;
    数组指针:
     一个指向一维或者多维数组的指针.
    int * b=new int[10]; 指向一维数组的指针b ;
    注意,这个时候释放空间一定要delete [] ,否则会造成内存泄露, b 就成为了空悬指针
    int (*b2)[10]=new int[10][10]; 注意,这里的b2指向了一个二维int型数组的首地址.
    注意:在这里,b2等效于二维数组名,但没有指出其边界,即最高维的元素数量,但是它的最低维数的元素数量必须要指定!就像指向字符的指针,即等效一个字符串,不要把指向字符的指针说成指向字符串的指针。
    int(*b3) [30] [20];  //三级指针――>指向三维数组的指针;
    int (*b2) [20];     //二级指针;――>指向二维数组的指针;
    b3=new int [1] [20] [30];
    b2=new int [30] [20];
    删除这两个动态数组可用下式:
    delete [] b3;  //删除(释放)三维数组;
    delete [] b2;  //删除(释放)二维数组;
    在堆中建立动态多维数组
    new 类型名[下标表达式1] [下标表达式2]……;
    例如:建立一个动态三维数组
    float (*cp)[30][20] ;  //指向一个30行20列数组
                                 //的指针,指向二维数组的指针
    cp=new float [15] [30] [20];
          //建立由15个30*20数组组成的数组;
    注意:cp等效于三维数组名,但没有指出其边界,即最高维的元素数量,就像指向字符的指针即等效一个字符串,不要把指向字符的指针,说成指向字符串的指针。这与数组的嵌套定义相一致。

    float(*cp) [30] [20];  //三级指针;
          float (*bp) [20];     //二级指针;
          cp=new float [1] [20] [30];
          bp=new float [30] [20];
         两个数组都是由600个浮点数组成,前者是只有一个元素的三维数组,每个元素为30行20列的二维数组,而另一个是有30个元素的二维数组,每个元素为20个元素的一维数组。
           删除这两个动态数组可用下式:
    delete [] cp;  //删除(释放)三维数组;

    //1、先看二维数组的动态创建:
    void main(){
        double **data;
        data = new double*[m]; //申请行
        if ((data ) == 0)
         { cout << "Could not allocate. bye ...";
            exit(-1);}
        for(int j=0;j<m;j++)
         { data[j] = new double[n]; //设置列
            if (data[j] == 0)
            { cout << "Could not allocate. Bye ...";
               exit(-1);}  } //空间申请结束,下为初始化
        for (int i=0;i<m;i++)
           for (int j=0;j<n;j++)  data[i][j]=i*n+j;
    display(data); //2、二维数组的输出,此处略。
    //3、再看二维数组的撤销与内存释放:
       for (int i=0;i<m;i++)
             delete[] data[i];
          //注意撤销次序,先列后行,与设置相反
        delete[] data;
        return;
    }
    二维数组的内存释放可以做成函数,
    调用语句de_allocate(data);
    void de_allocate(double **data){
           for (int i=0;i<m;i++)    delete[] data[i];
           delete[] data;
           return; }
    通过指针使堆空间,编程中的几个可能问题
    ⑴.动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。
              data = new double*[m]; //申请行
              if ((data ) == 0)……
    ⑵.指针删除与堆空间释放。删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针,不能再通过p使用该空间,在重新给p赋值前,也不能再直接使用p。
    ⑶.内存泄漏(memory leak)和重复释放。new与delete 是配对使用的, delete只能释放堆空间。如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。
    ⑷.动态分配的变量或对象的生命期。无名对象的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为自由空间(free store)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错。
    编程学习-动态内存分配-基于C++类
    堆对象与构造函数
     通过new建立的对象要调用构造函数,通过deletee删除对象也要调用析构函数。
    CGoods *pc;
    pc=new CGoods;  //分配堆空间,并构造一个无名
                                   //的CGoods对象;
    …….
    delete pc;  //先析构,然后将内存空间返回给堆;
        堆对象的生命期并不依赖于建立它的作用域,所以除非程序结束,堆对象(无名对象)的生命期不会到期,并且需要显式地用delete语句析构堆对象,上面的堆对象在执行delete语句时,C++自动调用其析构函数。
    正因为构造函数可以有参数,所以new后面类(class)类型也可以有参数。这些参数即构造函数的参数。
    但对创建数组,则无参数,并只调用缺省的构造函数。见下例类说明:
    class CGoods{
               char Name[21];
               int  Amount;
               float Price;
               float Total value;
    public:
      CGoods(){}; //缺省构造函数。因已有其他构造函数,系统不会再自动生成缺省构造,必须显式说明。
      CGoods(char* name,int amount ,float price){
                strcpy(Name,name);
                Amount=amount;
                Price=price;
                Total_value=price*amount;  }
                ……
    };//类声明结束
    //下面注意如何使用:
    void main(){
      int n;
      CGoods *pc,*pc1,*pc2;
      pc=new CGoods(“夏利2000”,10,118000);
      //调用三参数构造函数
      pc1=new CGoods();  //调用缺省构造函数
      cout<<’输入商品类数组元素数’<<endl;
      cin>>n;
      pc2=new CGoods[n];
     //动态建立数组,不能初始化,调用n次缺省构造函数
      ……
      delete pc;
      delete pc1;
      delete []pc2;  }
    此例告诉我们堆对象的使用方法:
    申请堆空间之后构造函数运行;
    释放堆空间之前析构函数运行;
    再次强调:由堆区创建对象数组,只能调用缺省的构造函数,不能调用其他任何构造函数。如果没有缺省的构造函数,则不能创建对象数组。
    浅拷贝与深拷贝
    对象的构造,也可以由拷贝构造函数完成,即用一个对象的内容去初始化另一个对象的内容。
    此时,若对象使用了堆空间(注意和“堆对象”区分),就有深、浅拷贝的问题,不清楚则很容易出错。
    1、什么是浅拷贝?
    2、浅拷贝可能带来什么问题?
    3、什么是深拷贝?
    4、深拷贝的实现方法?
    什么是浅拷贝
    缺省拷贝构造函数:用一个对象的内容初始化另一个同类对象,也称为缺省的按成员拷贝,不是对整个类对象的按位拷贝。这种拷贝称为浅拷贝。
    class CGoods{
               char *Name; //不同与char Name[21] ?
               int  Amount;
               float Price; float Total_value;
    public: CGoods(){Name=new char[21];}
     CGoods(CGoods & other){ //缺省拷贝构造内容:
                this->Name=other.Name;
                this->Amount=other.Amount;
                this->Price=other.Price;
               this->Total_value="/blog/other.Total_value;}
    ~CGoods(){delete Name;}//析构函数
    };  //类声明结束
    浅拷贝带来的问题
    void main(){
         CGoods pc;  //调用缺省构造函数
       CGoods pc1(pc);   //调用拷贝构造函数
    } //程序执行完,对象pc1和pc将被析构,此时出错。
    析构时,如用缺省的析构函数,则动态分配的堆空  
            间不能回收。
    如果用有“delete Name;”语句的析构函数,则先
            析构pc1时,堆空间已经释放,然后再析构pc  
           时出现了二次释放的问题。
    这时就要重新定义拷贝构造函数,给每个对象独
            立分配一个堆字符串,称深拷贝。
    深拷贝——自定义拷贝构造
    CGoods(CGoods & other){ //自定义拷贝构造
                this->Name=new char[21];
                strcpy(this->Name,other.Name);
                this->Amount=other.Amount;
                this->Price=other.Price;
                this->Total_value="/blog/other.Total_value;}
    例子:定义copy structor和拷贝赋值操作符(copy Assignment Operator)实现深拷贝。
    //学生类定义:
    class student{
         char *pName; //指针成员
    public:
         student();
         student(char *pname);
         student(student &s); //拷贝构造函数
         ~student();
         student & operator=(student &s);
                                        //拷贝赋值操作符
    };
    //缺省构造函数:
    student::student()
     {   pName=NULL; cout<<“Constructor缺省 ";  }
    //带参数构造函数:
    student::student(char *pname){
           if(pName=new char[strlen(pname)+1])     
                  strcpy(pName,pname);
           cout <<"Constructor" <<pName<<endl;}
    //拷贝构造函数:
    student::student(student &s){
        if(s.pName!=NULL){
            if(pName=new char[strlen(s.pName)+1])
                   strcpy(pName,s.pName); }
      //加1不可少,否则串结束符冲了其他信息,析构会出错!
        else pName=NULL;
        cout <<"Copy Constructor" <<pName<<endl;}
    //析构函数:
    student::~student(){
        cout<<"Destructor"<<pName<<endl;
        if(pName) delete[ ]pName;} //释放字符串
    //拷贝赋值操作符:
    student & student::operator=(student &s){
        if(pName) delete[ ]pName;
        if(s.pName){
           if(pName=new char[strlen(s.pName)+1]) 
                 strcpy(pName,s.pName);}
        else pName=NULL;
    cout <<"Copy Assign operator" <<pName<<‘ ’;
        return *this;}
    堆内存是最常用的需要自定义拷贝构造函数的资源,但不是唯一的,如打开文件等也需要。
       如果类需要析构函数来释放某些资源,则类也需要一个自定义的拷贝构造函数。此时,对象的拷贝就是深拷贝了。
     
  • 相关阅读:
    [ Algorithm ] N次方算法 N Square 动态规划解决
    [ Algorithm ] LCS 算法 动态规划解决
    sql server全文索引使用中的小坑
    关于join时显示no join predicate的那点事
    使用scvmm 2012的动态优化管理群集资源
    附加数据库后无法创建发布,error 2812 解决
    浅谈Virtual Machine Manager(SCVMM 2012) cluster 过载状态检测算法
    windows 2012 r2下安装sharepoint 2013错误解决
    sql server 2012 数据引擎任务调度算法解析(下)
    sql server 2012 数据引擎任务调度算法解析(上)
  • 原文地址:https://www.cnblogs.com/yingying0907/p/2616975.html
Copyright © 2011-2022 走看看