但是这些都是简单数据类型,如果要分配数组一样的连续空间,则需要使另一对武器。
20.3new[]和delete[]
new/delete用于分配和释放单个变量的空间,而new[]/delete[]则用于分配连续多个变量的存间。
20.3.1new[]/delete[]基本用法
new[]语法:
指针变量=new数据类型[元素个数]
语法实例:
int*p=newint[20];
首先,你需要迅速回想一下,如果是int*p=newint(20);那么该是什么作用?
否则你很容易在事后把二者混了。
实例中,用new申请分配了20个连续的整数所需的空间,即:
20*sizeof(int)=80个字节。
图示为:
(指针变量p指向一段连续的内存空间)
newint只是分配了一个整数的内存空间,而newint[N]却分配了N个整数的连续空间。
看来,new[]比new“威力更猛”,所以,我们同样得记得:
用new[]分配出空间,当不在需要时,必须及时调用delete[]来释放。
delete[]语法:
delete[]指针变量;
如:
//分配了可以存放1000个int的连续内存空间:
int*p=newint[1000];
//然后使用这些空间:
……
//最后不需要了,及时释放:
delete[]p;
20.3.2new[]/delete[]示例
在WindowsXP、WindowsNT或Windows2000中,按Ctrl+Alt+Del(其它操作系统,如Windows98/Me等千万不要按些组合键,否则电脑将重启)。
可以调出Windows任务管理器,其中要以看出当前粗略的的内存使用量。
下面我们结合该工具,写一个程序,先分配100M的内存,再释放。
这是程序代码的抓图:
各步运行结果:
程序显示
任务管理器抓图
第一步:
分配内存之前
(任务管理显示我的机器使用了207兆的内存)
第二步:
分配了100兆的内存
(多出了100M)
第三步:
又释放出这100兆
(回到207兆)
注意:
使用new得来的空间,必须用delete来释放;使用new[]得来的空间,必须用delete[]来释放。
彼此之间不能混用。
用new[]分配出连续空间后,指针变量“指向”该空间的首地址。
20.3.3详解指向连续空间的指针
在通过new[]指向连续空间以后,p就变得和一个一维数组很是类似。
我们先来复习一下数组相关知识。
假设是这么一个数组:
intarr[20];
则arr的内存示意图为(为了不太占用版面我缩小了一点):
(数组arr的内存示意)
和指针变量相比,数组没有一个单独的内存空间而存放其内存地址。
即:
指针变量p是一个独立的变量,只不过它的值指向另一段连续的内存空间;而数组arr,本身代表的就是一段连续空间。
如果拿房间来比喻。
指针和数组都是存放地址。
只不过,指针是你口袋里的那本通讯录上写着的地址,你可以随时改变它的内容,甚至擦除。
而数组是你家门楣上钉着的地址,你家原来是“复兴路甲108号”,你绝对不能趁月黑天高,把它涂改为“唐宁街10号”。
数组是“实”的地址,不能改变。
当你和定义一个数组,则这个数组就得根据它在内存中的位置,得到一个地址,如上图中的“0x1A000000”。
只要这个数组存在,那么它终生的地址就是这个值。
指针是一个“虚”的地址,可以改变地址的值。
当你定义一个指针变量,这个变量占用4个字节的内存,你可以往这4字节的内存写入任意一个值,该值被当成一个内存地址。
比如,你可以写入上面的“0x1A000000”,此时,指针p指向第一个元素。
也可以改为“0x1A000003”,此时,指针p指向第二个元素。
所以,当p通过new[]指向一段连续空间的结果是,p是一个指向数组的指针,而*p是它所指的数组。
我们来看实例,首先看二者的类似之处。
下面左边代码使用数组,右边代码使用指针。
数组
指针(通过new[]所得)
//定义:
intarr[20];
//定义:
int*p=newint[20];
//让第一个元素值为100:
arr[0]=100;
//让第一个元素值为100:
p[0]=100;
//让后面19个元素值分别为其前一元素加50:
for(inti=1;i<20;i++)
{
arr[i]=arr[i-1]+50;
}
//让后面19个元素值分别为其前一元素加50:
for(inti=1;i<20;i++)
{
p[i]=p[i-1]+50;
}
//输出所有元素:
for(inti=0;i<20;i++)
{
cout<}
//输出所有元素:
for(inti=0;i<20;i++)
{
cout<
}
//也可以不用[],而通过+号来得到指定元素:
//当然,对于数组,更常用的还是[]操作符。
cout<<*(arr+0)<cout<<*(arr+1)<cout<<*(arr+1)<
输出结果:
100
150
200
//也可以不用[],而通过+号来得到指定元素:
//其实,对于指针,这样的+及-操作用得还要多点。
cout<<*(p+0)<cout<<*(p+1)<cout<<*(p+1)<
输出结果:
100
150
200
当指针变量P通过new[]指向一连续的内存空间:
1、p[N]得到第N个元素(0<=N<元素个数);2、*(p+N)同样得到第N个元素(0<=N<元素个数)
如p[0]或*(p+0)得到内存空间第0个元素;
把上面右边代码中的大部分p替换为arr,则和左边代码变得一模一样。
下面再来比较二者的不同。
数组
指针
//定义并且初始化:
intarr[20]={0,1,2,3,4,5,6,7,8,9,0,……,19};
//定义、并且生成空间,但不能直接初始空间的内容:
int*p=newint[20]{0,1,2,3,4……}//错!
//只得通过循环一个个设置:
for(inti=0; i<20;i++)
{
p[i]=i;
}
//不能通过对数组本身+或-来改变数组的位置:
arr=arr+1; //错!
cout<<*arr<
arr++; //错!
cout<<*arr<
arr--; //错!
cout<<*arr<
输出结果:
无,因为程序有语法错误,通不过编译。
//可以通过+或-操作直接改变指针:
p=p+1;
cout<<*p<
p++;
cout<<*p<
p--;
cout<<*p<
输出结果:
1
2
1
//释放空间:
//数组所带的空间由系统自动分配及回收
//无须也无法由程序来直接释放。
//释放空间:
//指向连续空间的指针,必须使用delete[]来释放
delete[]p;
关于指针本身的+和-操作,请复习上一章相关内容。
接下来的问题也很重要。
20.4delete/delete[]的两个注意点
指针通过new或new[],向系统“申请”得到一段内存空间,我们说过,这段内存空间必须在不需要将它释放了。
有点像人类社会的终极目标“共产主义”下的“按需分配”。
需要了就申请,不需要了,则主动归还。
现在问题就在于这个“主动归还”。
当然,指针并不存在什么思想觉悟方面的问题,说光想申请不想归还。
真正的问题是,指针在某些方面的表现似乎有些像“花心大萝卜”。
请看下面代码,演示令人心酸的一幕。
/*
初始化p -----p的新婚
通过new,将一段新建的内存“嫁给”指针p
这一段分配的内存,就是p的原配夫妻
*/
int*p=newint[100];
/*
使用p -----恩爱相处
N多年恩爱相处,此处略去不表
*/
……
/*
p改变指向----分手
*/
intgirl[100]; //第三者出现
p=girl; //p就这样指向girl
/*
delete[]p---- 落幕前的灾难
终于有一天,p老了,上帝选择在这一时刻
惩罚他
*/
delete[]p;
扣除注释,上面只有4行代码。
这4行代码完全符合程序世界的宪法:
语法。
也就是说对它们进行编译,编译器会认为它们毫无错误,轻松放行。
但在灾难在delete[]p时发生。
我们原意是要释放p最初通过newint[100]而得到的内存空间,但事实上,p那时已经指向girl[100]了。
结果,第一、最初的空间并没有被释放。
第二、girl[100]本由系统自行释放,现在我们却要强行释放它。
20.4.1一个指针被删除时,应指向最初的地址
当一个指针通过+,-等操作而改变了指向;那么在释放之前,应确保其回到原来的指向。
比如:
int*p=newint[3];
*p=1;
cout<<*p<
p++; //p的指向改变了,指向了下一元素
*p=2;
cout<<*p<
//错误的释放:
delete[]p;
在delete[]p时,p指向的是第二个元素,结果该释放将产生错位:
第一个元素没有被释放,而在最后多删除了一个元素。
相当你盖房时盖的是前3间,可以在拆房时,漏了头一间,从第二间开始拆起,结果把不是你盖的第4房间倒给一并拆了。
如何消除这一严重错误呢?
第一种方法是把指针正确地"倒"回原始位置:
p--;
delete[]p;
但当我们的指针指向变化很多次时,在释放前要保证一步不错地一一退回,会比较困难。
所以另一方法是在最初时“备份”一份。
在释放时,直接释放该指针即可。
int*p=newint[3];
int*pbak=*p; //备份
//移动p
……
//释放:
delete[]pbak;
由于pbak正是指向p最初分配后的地址,我们删除pbak,就是删除p最初的指向。
此时我们不能再删除一次p。
这也就引出new/delete及new[]/delete[]在本章的最后一个问题。
20.4.2已释放的空