第16章 动态存储
第16章 程序运行中不确定的存储容量大小——堆
在数组那个景点还有一个遗憾,就是在声明定义数组时,非要确定了数组元素的个数才行,即数组的大小一定要是常量,如果想在程序运行中改变数组大小,是做不到的。但生活中总会遇到这种情况,例如,要在字符数组中存放输入的一段字符,其长度是不确定的,那么该如何解决这一问题呢?C++想了一个办法,即划分出一个区域来存放这种数组元素个数不确定的数据或运行中才来确定存储容量大小的数据或对象。以一个晒谷子的晒坪为例,划分某个区域给张三晒谷子,又划分其他区域给李四晒谷子,划分的区域随个人需要可大可小,即存放的数组元素的个数可以根据程序运行的需要而改变。这样的存储数据的区域,C++称之为堆(或堆空间、堆内存),它可以随着程序的运行存储或多或少的数据和对象,给编写程序和存放数据带来了极大的方便。
对象中有数据成员,数据成员也可能是数组这种数据类型的成员,那同样也存在大小不确定的问题,既然堆可以解决数组大小不确定的问题,那么它也可以解决对象大小不能确定的问题。
C++真有这么大的本事?观赏体验便可知。
16.1 运用堆存储和使用高考被录取者的成绩的例题详解
【例 16-1】某个学校统计该年高考录取(入围)人数,并显示他们的成绩,请编程实现。
- 用心分析人想通,机随人行写提纲
同样是按照 “人定胜机,机随人行” 的法则编程。人的做法如图 16-1 所示。
- 对号入座写文章,头脑运行改文章
根据图16-1,用 C++ 语句对号入座写文章——编写 C++ 源程序代码。
//c16-1-4.cpp
#include<iostream.h>
#include<stdlib.h>
void main()
{
int num1;//数组元素的个数
int pernum=0;//入围人数,注意将初始值设为0
int i=0;
float *p1;//存放返回堆地址的指针变量
cout<<"请输入数组元素的个数";
cin>>num1;//根据程序运行的需要从键盘输入数组元素的个数
p1=new float[num1];//申请一个浮点型的堆
if(p1==NULL) //检查申请是否成功
{
cout<<"分配不到堆空间,系统退出"<<endl;
exit(1);//不成功则退出
}
for(i=0;i<num1;i++)//成功则使用堆:输入高考入
//围者的分数,并统计入围人数
{
cout<<"请输入高考入围者的分数";
cin>>p1[i];
if(p1[i]==0)
break;
pernum++;//累计入围人数
}
cout<<"入围人数="<<pernum<<endl;//显示入围
//人数
cout<<"高考入围者的分数分别为:"<<endl;
for(i=0;i<pernum;i++)
cout<<p1[i]<";//显示入围者分数
cout<<endl;
delete[]p1;
将写出的程序与图 16-1 所示的流程图进行分析比照,并用人脑跟踪运行,直至认为正确(即可以完成题目的功能,也符合 C++ 的语法及规定),就可以转入下一步了。

3. C++帮助且裁判,人定胜机凯歌扬
将上面的程序输入计算机→编译→连接→如果无错→运行[→调试修改→如果无错→运行]。
运行结果:
请输入数组元素的个数 10
请输入高考入围者的分数 890.5
请输入高考入围者的分数 850.7
请输入高考入围者的分数 901.4
请输入高考入围者的分数 810.5
请输入高考入围者的分数 0
入围人数=4
高考入围者的分数分别为:
890.5;850.7;901.4;810.5;
结果正确。
16.2 勤奋实践,总结评分
16.2.1 习题
1\. 将第8章习题的第1题改成使用堆数组来编写程序。
- 将第7章习题的第6题改成使用堆数组来编写程序。
- 将第8章习题的第2题改成使用堆数组来编写程序。
- 将第8章习题的第3题改成使用堆数组来编写程序。
- 将第6章习题的第5~7题改成使用堆数组来编写程序。
- 将第8章习题的第6题改成使用堆数组来编写程序。
- 将第8章习题的第5题改成使用堆数组来编写程序。
- 将第8章习题的第9题改成使用堆数组来编写程序。
16.2.2 思考题
1\. 将第8章思考题的第1题改成使用堆数组来编写程序。
- 将第8章习题的第10题改成使用堆数组来编写程序。
16.2.3 总结评分,继续提高
浏览、体验了例题、习题和思考题之后,说说自己的体会和收获,并认真总结,按表 16-1 给自己评判一下可以得多少分。
| 成绩 | A | B | C | D | 不足的原因 | 改进的方法 | 备注 |
| C++知识 | 完全理解和会用 | 基本理解和会用 | 肤浅理解和会用 | 基本不理解和不会用 | |||
| 程序原创(独立编程)的比例(%) | ≥90 | 80~89 | 60~79 | <60 |
不论得分多少,大家都要总结经验,扬长避短,勤奋努力,不断提高。
最后浏览一下本章的第4节,可以先考考自己,然后再浏览,以便使重点知识掌握得更牢固。
16.3 堆知识
本章开头提到了堆的好处及作用,既然堆这么好,那么哪些情况下能用它?怎么用?请一一往下观赏。
16.3.1 在什么情况下用到堆
当数据存储空间或对象存储空间的大小或生存期不确定时用堆,也就是在程序运行中,存储数据或对象的空间大小需要变化或生存期不确定时用堆。
例如,存放一个班的同学姓名的数据或对象就是不确定的,如班上原有50位同学,后来有些同学转专业了、转学了、出国了、退学了,而有时又会增加一些新同学,所以这个班的同学人数就不一定是50了,则存放同学姓名的数据或对象就不确定,要随情况的变化而变化,这种情况用堆是最合适不过的了。
16.3.2 关于堆
堆是什么,堆从何处获得,怎样使用堆?往下观赏便可知。
1. 堆是什么
堆是用来存放数据或对象的空间,随着程序运行的需要,可以人为地将该空间变大或变小,就像吹气球一样,可大可小。
堆是计算机内部存储器(简称内存)中的一个区域,即计算机内的一部分存储空间。
2. 堆的申请、分配和检查
怎样获得堆?通过执行相应的 C++语句即可获得堆,并分配给需要堆的数据或对象使用。堆的分配方法有几种,这里讲讲主要用的一种,即给数组分配堆。
分配堆是这样进行的:new 是分配堆的操作符,当程序执行到分配堆的定义语句时,就在内存的堆区间返回一个所分配的堆的起始地址,存放地址的变量是指针变量,所以定义语句的左边是存放返回堆地址的指针变量名。
堆中存放什么类型的数据要在 new 之后定义,这个数组元素的数据类型要与 “=” 左边的指针变量或指针对象的数据类型一致。
(1)动态数组堆的申请和分配语句
数据类型(一般为 int)数组元素的个数:
数组元素的数据类型 *存放返回堆地址的指针变量;
//定义存放返回堆地址的指针变量,注意:该指针的数据类型与
//存放的数组元素的数据类型必须相同
根据程序动态运行的需要从键盘输入数组元素的个数;
存放返回堆地址的指针变量名=new 数组元素的数据类型[数组元素的个数];
//堆分配给了该指针变量,相当于分配给了一维动态数组,
$$ // 因为一级指针就是是一维数组 $$
其中,“数组元素的个数”按程序运行的需要来确定,这个数一般是人工从键盘上输入给计算机的。
堆的大小是在程序运行时,按照人申请(键入)的容量大小分配的,而不是计算机在运行中自动给你申请容量大小无限制的堆。比如,当为数组申请堆时,人申请(键入)的容量大小为51,堆就给你分配存放51个元素的数组,当超出51个元素时,系统报错,退出。而不是说,你在输入数组元素时,输入到第52个,堆会为你增大容量。这个问题要搞清楚。
【例 16-2】试设计一个存放在校学生成绩的变量。
在校学生的成绩有整数,也有小数,故假设成绩均是浮点型数据,根据以前所学的知识,存放所有在校学生成绩的数据类型应该是数组,而且是浮点型数组,但这个数组的大小(成绩的数目)定为多少呢?定多了,占用的存储空间太大,定少了,可能又存放不了所有成绩。根据本章所学的知识,将这些成绩存放在堆中就可以解决这个问题了。
根据上述分析,编写程序如下:
int num1;//此例的数组元素的个数 num1 就是预先估计的在校学生人数
float * pscore;//定义存放返回堆地址的指针变量 pscore
//注意:该指针的数据类型与存放的数组元素的数据类型必须相同
cout<<"请输入数组元素的个数"<endl;
cin>>num1;//按程序运行的需要从键盘输入数组元素的个数
pscore=new float[num1];//按照申请的数组元素的个数 num1,把堆分配给了浮点型指针
//pscore,相当于分配给了一维动态数组
(2) 堆的检查
申请书交了,批不批准呢?不知道。可以通过下述语句去询问了解:
if(存放返回堆地址的指针变量名==NULL)//通过检查存放返回堆地址的指针变量名是否//为空,就真相大白了,如果为空,则没批准,申请失败;否则,申请成功
{
cout<<"分配不到堆空间,系统退出"<<endl;
exit(1); //系统退出
}
则例 16-2 的相应语句为:
if(pscore == NULL)
{
cout<<"分配不到堆空间,系统退出"<<endl;
exit(1);
}
以上这些申请语句和检查语句一般都要一起使用。
3. 堆的使用
使用堆时需遵循以下规则。
2)编译、连接、调试成功后就可以运行了。
1)正确书写上述的申请语句和检查语句。
3)运行时,人工从键盘上输入数组元素的个数给计算机。
4)就像使用一维数组或一级指针一样使用堆——一维动态数组,即用存放返回堆地址的指针变量名(如 pscore)的内容去存入或取出数据,如用 pscore [i]或*(pscore+i)去存入或取出数据均可。
4. 释放堆
为了节省计算机的内存空间,当使用完堆后,即程序运行中不再用到这个堆时,就应该将其释放出来,以供其他变量或对象使用。
怎样释放呢?很简单,给数组释放堆的语句格式如下。
delete[]存放返回堆地址的指针变量名;
例如:
delete[] pscore;
注意,要加上“[ ]”。
16.3.3 如何运用堆
在运用堆时,先按照要求决定哪些数据或对象需要申请堆,然后按照相应的申请方式(语句格式)申请堆,再检查堆的申请成功与否,不成功则退出;成功则使用堆。对于申请的一维动态数组的堆,就按与使用一维数组相同的方法去使用它。使用完堆后,要善始善终地把堆归还(释放堆)。
【例 16-3】运用堆存储高考被录取者的姓名和成绩,并将其显示出来。
(1)用心分析人想通,机随人行写提纲
同样是按照 “人定胜机,机随人行” 的法则编程。
运用堆存储高考被录取者的姓名和成绩的流程图基本与图 16-1 一致,只是初始化部分声明定义了一个结构变量,相应的,申请的堆是结构数组空间。
(2)对号入座写文章,头脑运行改文章
根据流程图,用 C++ 语句对号入座写文章——编写 C++ 源程序代码。
//c16-1-13
#include<iostream.h>
#include<stdlib.h>
void main()
{
int num1;//定义数组元素个数的变量
int pernum=0,i=0;
struct scor//声明存储高考被录取者的姓名和成绩的结构
{
char name5[20];//姓名
float score5;//成绩
};
scor *p5;//定义存放返回堆地址的指针变量 p5,
//p5 的数据类型是 scor 结构数据类型
cout<<"请输入数组元素的个数";
cin>>num1;
p5=new scor[num1];//分配了一个堆给 scor 类型的指针 p5,
//即分配了一维动态结构数组,数组元素的个数为 num1
if(p5==NULL)
{
cout<<"分配不到堆空间,系统退出"<<endl;
exit(1);
}
for(i=0;i<num1;i++)
{
cout<<"请输入高考入围者的姓名和成绩:";
cin>>p5[i].name5>>p5[i].score5;//使用堆的方法与使用一维结构数组的方法相同,
//注意加“."取结构分量
if(p5[i].score5==0)
break;
pernum++;
}
cout<<"入围人数="<<pernum<<endl;
cout<<"高考入围者的姓名和成绩分别为:"<<endl;
for(i=0;i<pernum;i++)
{
cout<<p5[i].name5<<": "<<p5[i].score5;
cout<<endl;
}
delete[ ]p5;
}
将写出的程序与图 16-1 进行分析比照,并用人脑跟踪运行,直至认为正确(即可以完成题目的功能,也符合 C++ 的语法及规定),就可以转入下一步了。
(3) C++帮助且裁判,人定胜机凯歌扬
将上面的程序输入计算机→编译→连接→如果无错→运行[→调试修改→如果无错→运行]。
运行结果:
请输入数组元素的个数5
请输入高考入围者的姓名和成绩:张三
890
请输入高考入围者的姓名和成绩:李四
860
请输入高考入围者的姓名和成绩:王五
690
请输入高考入围者的姓名和成绩:孙玉
870
请输入高考入围者的姓名和成绩:刘英
810
入围人数=5
高考入围者的姓名和成绩分别为:
张三:890
李四:860
王五:690
孙玉:870
刘英:810
结果正确。
16.4 堆的亮点
1. 在什么情况下用堆的亮点
当数据存储空间或对象存储空间的大小或生存期不确定时用堆。
2. 堆具体知识的亮点
1 )动态数组堆的申请和分配语句如下。
数据类型(一般为 int)数组元素的个数:
数组元素的数据类型 *存放返回堆地址的指针变量;
//定义存放返回堆地址的指针变量,注意:该指针的数据类型与
//存放的数组元素的数据类型必须相同
根据程序动态运行的需要从键盘输入数组元素的个数:
存放返回堆地址的指针变量名=new 数组元素的数据类型[数组元素的个数];
//堆分配给了该指针变量,相当于分配给了一维动态数组
2)申请了堆空间后要检查申请是否成功,成功才能使用堆,否则退出。
3)运行时,人工从键盘上输入数组元素的个数给计算机。
4)就像使用一维数组或一级指针一样使用堆——一维动态数组,即用存放返回堆地址的指针变量名的内容去存入或取出数据。
5)给数组释放堆的语句格式如下。
delete[]存放返回堆地址的指针变量名;
3. 运用堆的亮点
运用堆的过程是:申请堆空间→检查申请成功与否→申请成功则使用→使用完后释放该堆空间。