C++动态内存分配与引用
注意这时释放了pi所指的目标的内存空间,也就是 撤销了该目标,称动态内存释放(dynamic memory deallocation),但指针pi本身并没有撤销,它自 己仍然存在,该指针所占内存空间并未释放。
使用new运算符时必须已知数据类型,new运算符会向系 统堆区申请足够的存储空间,如果申请成功,就返回该 内存块的首地址,如果申请不成功,则返回零值。
9
【例9-1:利用new对变量进行分配空间】
#include
<iostream>
如果内存分配失败,则 返回零值。所以在动态
using namespace std; 分配内存时,应对返回
void main()
的指针进行检查
{
char *pc; //通过指针指向动态分配的内存的首地址
int *pi;
栈区
于{ 动
态
char * string=new char[20];
申 请
if (string==0)
数
{
return;
}
组
char str[20];
空
strcpy(string,"It is a string.");
间
strcpy(str,"It is a string too.");
的 释
cout<<string<<endl;
12
str 栈区 string 堆区
I
I
t
t
…
…
13
【例9-3:动态数组的建立与撤销】
void main(){ int n; char *pc; cout<<"请输入动态数组的元素个数"<<endl; cin>>n; //在运行时确定,可输入17 pc=new char[n];
//申请17个字符(可装8个汉字和一个结束符)的内存空间
2
通常定义变量时,编译器在编译时根据该变 量的类型,在适当的时候为他们分配所需的 内存空间大小。这种内存分配称为静态存储 分配。
但有些操作只有在程序运行时才能确定,这 样编译器在编译时就无法为他们预定存储空 间,只能在程序运行时,系统根据运行时的 要求进行内存分配,这种方法称为动态存储 分配。所有动态存储分配都在堆区中进行。
指针变量名=new 类型名(初始值);
delete 指针名;
new运算符返回的是一个指向所分配类型变 量(对象)的指针。对所创建的变量或对象, 都是通过该指针来间接操作的,而动态创建的 对象本身没有标识符名。
6
一般定义变量和对象时要用标识符命名,称 命名对象,而动态的称无名对象。 new表达式的操作序列如下:从堆区分配对象, 然后用括号中的值初始化该对象。从堆区分配 对象时,new表达式调个动态分配的变量或对 象时,必须向系统申请取得堆区中的一块所需 大小的存储空间,用于存储该变量或对象。 ➢ 当不再使用该变量或对象时,也就是它的生 命结束时,要显式释放它所占用的存储空间, 这样系统就能对该堆空间进行再次分配,做到 重复使用有限的资源。
在C++中,申请和释放堆中分配的存储空间, 分别使用new和delete这两个运算符来完成, 其使用的格式如下:
3
全局数据区 代码区
栈区
堆区
data area code area stack area heap area
全局变量 静态数据 常量
函数
函数运行 时分配的 局部变量、 函数参数、 返回数据、 返回地址 等
内存中剩余的 空间由程序员 负责申请和释 放在C++里堆 空间的申请和 释放分别用到 操作符:new 和 delete
} 以变量形式分配内存比较死板。有了new和delete,就可
以实现一种动态分配内存的形式,即通过指针引用,而
内存的分配和释放可以在程序的任何地方进行。 21
1.动态分配失败。返回一个空指针(NULL), 表示发生了异常,堆资源不足,分配失败。
2.指针删除与堆空间释放。删除一个指针p (delete p;)实际意思是删除了p所指的目标 (变量或对象等),释放了它所占的堆空间, 而不是删除p本身,释放堆空间后,p成了空 指针。
两式中的方括号是非常重要的,两者必须配对使用, 如果delete语句中少了方括号,因编译器认为该指针 是指向数组第一个元素的指针,会产生回收不彻底的 问题(只回收了第一个元素所占空间),加了方括号 后就转化为指向数组的指针,回收整个数组。 •delete [ ]的方括号中不需要填数组元素数,系统自 知。即使写了,编译器也忽略。 请注意“下标表达式”不是常量表达式,即它的值不 必在编译时确定,可以在运行时确定。
4.动态分配的变量或对象的生命期。我们也称堆空 间为自由空间(free store),但必须记住释放该对 象所占堆空间,并只能释放一次,在函数内建立,而 在函数外释放,往往会出错。
20
void main() { int i, *a, *p; a = new int[10]; cout<<"input 10 integers:"<<endl; for(i=0; i<10; i++) cin>>*(a+i); //也可用a[i] cout<<"---- The result ----"<<endl; for(p=a+9; p>=a; p--) cout<<*p<<" "; cout<<endl;
一般格式
格式1:指针变量名=new 类型标识符; 格式2:指针变量名=new 类型标识符(初始值); 格式3:指针变量名=new 类型标识符 [内存单元个数];
说明:格式1和格式2都是申请分配某一数据类型所占字节数 的内存空间;但是格式2在内存分配成功后,同时将一初值 存放到该内存单元中;而格式3可同时分配若干个内存单元, 相当于形成一个动态数组。
pi=new int(8);
*pc='a';
cout<<pc<<endl;
cout<<*pi<<endl;
}
15
栈区
pc pi
堆区 1000
a 8
1001
当该函数或者该程序执行完毕后系统弹栈, pc和pi这两个变量将消失,但他们指向的 堆内的内存并不会自动释放,那么该内存 将再不能使用,除非系统重启
1
对于计算机程序设计而言,变量和对象在内 存中的分配都是编译器在编译程序时安排好 的,这带来了极大的不便,如数组必须大开 小用,指针必须指向一个已经存在的变量或 对象。
对于不能确定需要占用多少内存的情况,动 态内存分配解决了这个问题。
C/C++定义了4个内存区间:代码区,全局数 据区,栈区,堆(heap)区。
pc=new char; //格式1 pc
pi=new int(8);//格式2 pi
*pc='a';
堆区
a 1000 8 1001
cout<<pc<<endl;
cout<<*pi<<endl;
}
输出结果: a
8
10
• 对于数组进行动态分配的格式为:
指针变量名=new 类型名[下标表达式]; delete [ ] 指向该数组的指针变量名;
3.内存泄漏(memory leak)和重复释放。new与 delete 是配对使用的, delete只能释放堆空间。如 果new返回的指针值丢失,则所分配的堆空间无法回收, 称内存泄漏,同一空间重复释放也是危险的,因为该 空间可能已另分配,所以必须妥善保存new返回的指针, 以保证不发生内存泄漏,也必须保证不会重复释放堆 内存空间。
【例9-2:new 运算符为数组分配空间】
void main() {
char * string=new char[20]; char str[20]; strcpy(string,"It is a string."); strcpy(str,"It is a string too."); cout<<string<<endl; cout<<str<<endl; } 问:内存区域中,栈区怎么变化?堆区怎么变 化?
strcpy(pc,"堆内存的动态分配"); cout<<pc<<endl; delete []pc; //释放pc所指向的n个字符的内存空间 }
使用new运算符分配的内存一定要释放,否则 会产生系统内存泄漏。如:
void main(){
char *pc;
int *pi;
pc=new char;
int *pi=new int(0);
说明:pi现在所指向的变量是由库操作符 new()分配的,位于程序的堆区中,并且该对 象未命名。
下面看演示:
1.用初始化式(initializer)来显式初始化
int *pi=new int(0);
堆
2.当pi生命周期结束时,
Pi
0
必须释放pi所指向的目标:
delete pi;
放
cout<<str<<endl;
delete []string;
}
18
栈区 str
string 堆区
I
I
t
t
1000
…
…1000
…
回收的空间
19