C++初学者指南 第十二篇(5)

来源:互联网 发布:ios11拆卸软件 编辑:程序博客网 时间:2024/05/09 10:17
转载请标明出处:http://blog.csdn.net/zhangxingping

基本技能 12.4:动态内存分配

C++程序中有两种主要的方式用于在内存中存储数据。第一种就是通过使用变量。变量的存储空间是在编译时就分配好的,在程序运行时是不可改变的。第二种方式就是通过C+的动态内存分配机制来进行内存分配。在这种方式中,数据存储空间是在程序需要的时候由系统从位于程序代码区和栈区之间的空闲区域中进行分配。这段区域被称为堆。图12-1从概念上展示了C++程序在内存中的存储方式。

        

高地址内存

 

 

全局数据区

低地址内存

程序代码区域

12-1 C++程序中存储空间使用示意图

动态内存分配是在程序运行时进行的。因此动态内存分配使得我们可以在程序运行的时候根据需要创建变量。根据需要,我们创建或多或少的变量。动态内存分配常常被用来支持诸如线性链表,二叉树或者小型数组。当然,我们可以根据需要自由地使用动态内存分配机制。动态内存分配机制几乎是现实的程序中的一个很重要的部分。

动态内存分配是从堆空间中进行的。我们都知道,在极端的情况下内存是有可能被耗尽的。因此,经过动态内存分配机制很灵活,但是堆取的空间也是有限的。

C++中提供了两个和动态内存分配相关的运算符:newdeletenew、运算符用于分配内存空间并返回指向该空间的首地址。delete运算符用于释放先前有new分配的空间。newdelete的常用形式如下:

p_var = new类型;

delete p_var;

其中,p_var是一个指针。指向一块足够大的可用于存储type类型数据的内存空间。

由于堆空间是有限,因此堆空间也会是会被耗尽的。如果没有足够的可用内存用于new来进行分配,那么new会分配失败,并抛出bad_alloc_exception异常。这种异常在头文件<new>中有定义。有程序来负责处理该异常并进行正确的处理。如果程序中没有处理这个异常,程序会异常终止。

标准的C++有对new分配空间失败进行相应的描述。麻烦的问题是:一些老的编译器会以不同的方式实现new操作。当C++刚被发明出来的时候,如果分配内存空间失败,new会返回空指针。后来,人们对此进行了修改,才抛出异常。如果您使用的是老的编译器,请查阅编译器文档,看看其中是如何实现new操作的。

既然标准的C++中描述到new操作失败会抛出异常,我们就以这种方式来编写相应的代码。如果您所使用的编译器使用的是不同的new操作的实现方式,你可能需要修改下面的程序。

下面的程序使用动态内存分配机制来分配空间用于存储一个整型数:

//演示new和delete运算符 #include<iostream>#include<new> using namespace std; int main(){   int *p;   try   {       p =new int; //为整型数分配内存   }   catch (bad_alloc xa)   {       cout <<"Allocation Failure/n";       return 1;   }    *p = 100;   cout <<"At " <<p <<" ";   cout <<"is the value " << *p <<"/n";    delete p; //释放分配的内存    return 0;}


在上面的程序中,我们给p赋值为一块足够大可用于存储整型数的堆空间中的一个地址。然后给这块内存赋值为100,随后在屏幕上输出该内存空间的值,最后释放掉我们动态分配的内存空间。

delete运算中的指针必须是先前使用new分配的有效的指针。在delete操作的时候如果使用的是其他类型的指针,则会导致严重的问题,例如系统崩溃,因为这种行为是没有定义的(就是说没有明确针对这种情况应该如何处理)。

初始化分配的内存

我们可以在分配内存的时候给内存初始化为已知的值。这是通过在new语句中的类型后面的括号中写上初始化数值来完成:

p_var = new变量类型(初始化值);

当然,初始化值的类型必须和被分配的内存空间数据的类型相兼容。

下面的程序中,动态分配的整型数的初始值为87

//内存初始化 #include<iostream>#include<new> using namespace std; int main(){   int *p;    try   {       p =new int(87); //初始化值为   }   catch (bad_alloc xa)   {       cout <<"Allocation Failure/n"; //分配内存空间失败   }    cout <<"At " <<p <<" ";   cout <<"is the value " << *p <<"/n";    delete p;    return 0;}


动态分配数组

我们可以使用下面的形式来动态为数组分配空间:

p_var = new array_type[size];

其中,size指定的是数组中元素的数量。释放数组的空间时,使用下面的形式:

delete []p_var;

其中的[ ]意思为需要释放的是数组空间。例如下面的程序就为10个整型数分配数组空间:

//动态为数组分配空间 #include<iostream>#include<new> using namespace std; int main(){   int *p,i;   try   {       p =new int[10]; //分配个整型数的数组空间   }   catch(bad_alloc xa)   {       cout <<"Allocation Failure/n";       return 1;   }    for(i = 0;i < 10;i++)   {       p[i] =i;   }    for(i = 0;i < 10;i++)   {      cout <<p[i] <<" ";   }    delete []p;      return 0;}


请注意其中的delete语句。正如前面提到的那样,当我们使用delete释放new所分配的数组空间的时候,必须使用[ ]来表明是释放数组空间。(正如我们在下一章节中看到的那样,当我们动态分配对象的数组空间的时候,这一点显得尤为重要。)

        关于动态分配数组空间的一个限制:可以不指定初始化的值。也就是说,在分配数组空间的时候可以不对其进行初始化。

 

专家答疑

问:我曾经看到过使用malloc()free()进行动态内存分配的C++程序,这两个函数是用来做什么的?

答:C语言是不支持使用newdelete运算符的。相反,C语言中使用mallocfree来进行动态内存分配。malloc()函数用来分配内存空间;free()用来释放空间。C++也是支持这两个函数的,所以我们在C++代码中偶尔也能看到使用这两函数来分配内存空间。如果代码是从原始的C代码中升级过来的,这种情况就很可能发生。然而新写的代码中我们应该使用newdelete来进行内存空间的分配和释放。这是因为newdelete不仅提供了更为方便的内存分配和释放的方式,而且还因为使用这两个运算符能避免使用mallocfree时常出现的几种错误。还有一点:尽管C++中没有明确规定不能在程序中把这两对函数进行混合使用,但是我们最好还是不要这样做。这样可以保持程序内存的分配和释放相互兼容。

动态分配对象

我们还可以使用new运算符来动态地分配对象。此时,new运算会创建一个对象并返回指向该对象的指针。动态创建的对象和普通的对象是一样的:当我们创建该对象的时候会调用对象的构造函数;当我们释放该对象的时候,会调用其析构函数。

下面的程序中创建了一个Rectangle类,其中封装了长方形的宽度和高度。在main()函数中,我们动态地创建了一个Rectangle类的对象。这个对象在程序结束的时候被释放。

//动态分配对象 #include<iostream>#include<new> using namespace std;class Rectangle{   int width;   int height;public:   Rectangle(int w,int h)   {       width =w;       height =h;       cout <<"Constructing " <<width <<" by "<<height <<" rectangle./n";   }    ~Rectangle()   {       cout <<"Destructing " <<width <<" by " <<height <<" rectangle./n";   }    int area()   {       return width*height;   }}; int main(){   Rectangle *p;   try   {       p =new Rectangle(10,8); //动态生成Rectangle对象,这里会调用Rectangle的构造函数   }   catch (bad_alloc xa)   {               cout <<"Allocation Failure/n";       return 1;   }    cout <<"Area is " <<p->area();    cout <<"/n";    delete p;    return 0;    }

上面程序的输入如下:

Constructing 10 by 8 rectangle.

Area is 80

Destructing 10 by 8 rectangle.

请注意,在上面的程序中,对象的构造函数的参数是直接写在对象类型的后面的,这点和普通的初始化是一致的。另外,由于p是一个指向对象的指针,所以当调用area()函数的时候要使用->箭头运算符而不是.点号运算符。

我们还可以动态分配对象的数组。但是此时有一点需要注意:由于new生成的数组不能对其中的元素进行显示初始化,所以对象的类中必须有不需要参数的构造函数。如果没有定义不需要参数的构造函数,C++编译器就会在分配对象数组的时候因为找不到匹配的构造函数而报告错误。

下面我们重写之前的程序,我们为Rectangle类增加了不需要参数的构造函数,这样在动态生成对象数组的时候这些对象可以被正确的初始化。我们还增加了set()函数,用来设置长方形的尺寸。

//分配对象数组 #include<iostream>#include<new> usingnamespacestd; class Rectangle{   int width;   int height;public:   Rectangle() //不需要参数的构造函数   {       width =height = 0;       cout <<"Constructing " <<width <<" by " <<height <<" rectangle./n";   }    Rectangle(int w,int h)   {       width =w;       height =h;       cout <<"Constructing " <<width <<" by "<<height <<" rectangle./n";   }    ~Rectangle()   {       cout <<"Destructing " <<width <<" by " <<height <<" rectangle./n";   }    void set(int w,int h)   {       width =w;       height =h;   }    int area()   {       return width*height;   }}; int main(){   Rectangle *p;   try   {       p =new Rectangle[3]; //动态生成Rectangle对象,这里会调用Rectangle的构造函数   }   catch (bad_alloc xa)   {               cout <<"Allocation Failure/n";       return 1;   }    p[0].set(3, 4);   p[1].set(10, 8);   p[2].set(5, 6);    for(inti = 0;i < 3; ++i)   {       cout <<"Area is " <<p[i].area() << endl;   }       cout <<"/n";    delete []p; //此时会调用数组中每个对象的析构函数    return 0;    }

上面程序的输出如下:

Constructing 0 by 0 rectangle.

Constructing 0 by 0 rectangle.

Constructing 0 by 0 rectangle.

Area is 12

Area is 80

Area is 30

 

Destructing 5 by 6 rectangle.

Destructing 10 by 8 rectangle.

Destructing 3 by 4 rectangle.

由于指针p是通过delete []来释放的,数组中的每一个对象的析构函数都会被调用,正如程序的输出那样。还有,注意上面的p可以当做数组,通过索引来访问其中的每一个对象,然后使用.点号运算符来访问对象的成员。

练习

1.        那个运算符可用于分配内存空间?那个运算符可用于释放内存空间?

2.        如果分配满足指定要求的空间失败会怎么样?

3.        当内存在被分配时是否可以对其进行初始化?

原创粉丝点击