C++内存分配方式详解(堆、栈、自由存储区、全局/静态存储区和常量存储区)

C++内存分配方式详解(堆、栈、自由存储区、全局/静态存储区和常量存储区)

一、数据结构中的栈和堆

虽说我们经常把堆栈放在一起称呼,但是不可否认的是,堆栈实际上是两种数据结构:堆和栈。

堆和栈都是一种数据项按序排列的数据结构。

栈:就像装数据的桶或箱子,它是一种具有后进先出性质的数据结构。

堆:一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。

二、内存分配中的栈和堆

注意:一般情况下,当我们说“堆栈”的时候,其实说的是“栈”!

一般情况下程序存放在Rom或Flash中,运行时需要拷到内存中执行,内存会分别存储不同的信息。内存中的栈区处于相对较高的地址以地址的增长方向为上的话,栈地址是向下增长的。

栈中分配局部变量空间,堆区是向上增长的用于分配程序员申请的内存空间。另外还有静态区是分配静态变量,全局变量空间的;只读区是分配常量和程序代码空间的;以及其他一些分区。

程序的内存分配:
  一个由C/C++编译的程序占用的内存分为以下几个部分:  
 1、栈区(stack)—  由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。  
 2、堆区(heap) —  一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。  
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。  
4、文字常量区  —常量字符串就是放在这里的。程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码。  

实例讲解:

int  a=0;   全局初始化区    
char *p1;   全局未初始化区    
int  main()    
{    
  int  b; //栈    
  char  s[]="abc"; //栈    
  char  *p2; //栈    
  char  *p3="123456"; //123456/0在常量区,p3在栈上。    
  static int c =0;//全局(静态)初始化区    
  p1 =  (char  *)malloc(10);  //分配得来得10和20字节的区域就在堆区
  p2  = (char  *)malloc(20);       
  strcpy(p3,"123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"  优化成一个地方。    
}    
下面详细讲解一下内存分配的几个区:

:就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。

 :就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。堆可以动态地扩展和收缩。

 自由存储区,就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free 来结束自己的生命的。

 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放),在 C++ 里面没有这个区分了,他们共同占用同一块内存区。

常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)

void f() { int* p=new int[5]; }

  这条短短的一句话就包含了堆与栈,看到 new,我们首先就应该想到,我们分配了一块堆内存,那么指针 p 呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针 p。在程序会先确定在堆中分配内存的大小,然后调用 operator new 分配内存,然后返回这块内存的首地址,放入栈中。

那么该怎么去释放呢?

使用 delete []p,这是为了告诉编译器:我删除的是一个数组,VC6 就会根据相应的 Cookie 信息去进行释放内存的工作。

堆和栈究竟有什么区别?

主要的区别由以下几点:

  1、管理方式不同;

  2、空间大小不同;

  3、能否产生碎片不同;

  4、生长方向不同;

  5、分配方式不同;

  6、分配效率不同;

  管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

  空间大小:一般来讲在 32 位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:打开工程,依次操作菜单如下:Project->Setting->Link,在 Category 中选中 Output,然后在 Reserve 中设定堆栈的最大值和 commit。注意:reserve 最小值为 4Byte;commit 是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

  碎片问题:对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。

  生长方向对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

  分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 malloc 函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

  分配效率栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是 C/C++ 函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

  从这里我们可以看到,堆和栈相比,由于大量 new/delete 的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP 和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。

  虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。

  无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候 debug 可是相当困难的 :)

堆和栈还有几点不同:

1、申请后系统的响应    
  栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。    
  堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。  
  另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。    
   
2、申请大小的限制    
  栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。    
 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。   
   
3、申请效率的比较:    
  栈由系统自动分配,速度较快。但程序员是无法控制的。    
  堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 
  另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。     
   
4、堆和栈中的存储内容    
  栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。  
  当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。    
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。    

5、堆和栈上的内存操作越界

1>堆内存越界主要是操作的内存超过了calloc/malloc/new等在堆上分配内存函数所分配的大小,后果导致下次calloc/malloc/new的失败,malloc失败发生_int_malloc错误(引起abort)大多是这种情况引起的;

2>栈内存越界的情况大多出现在对数组的操作上,数组下标超过了数组定义的长度,后果导致覆盖其他变量。
小结一下:

堆和栈的区别可以用如下的比喻来看出:    
  使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。    

  使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

 //==============================================================//

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缺省/n";  }

//带参数构造函数:

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<<‘/n’;

    return *this;}

堆内存是最常用的需要自定义拷贝构造函数的资源,但不是唯一的,如打开文件等也需要。

   如果类需要析构函数来释放某些资源,则类也需要一个自定义的拷贝构造函数。此时,对象的拷贝就是深拷贝了。


猜你喜欢

转载自blog.csdn.net/sinat_21026543/article/details/79884441