一、使用new
来分配内存
前面我们都将指针初始化为变量的地址;变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供 了一个别名。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc( )
来分配内存;在 C++中仍然可以这样做,但C++ 还有更好的方法——new
运算符。
下面来试试这种新技术,在运行阶段为一个int
值分配未命名的内存,并使用指针来访问这个值。这里的关键所在是C++的new
运算符。程序员要告诉new
,需要为哪种数据类型分配内存;new
将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。下面是一个这样的示例:
int *pn=new int;
new int
告诉程序,需要适合存储int
的内存。new
运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来, 将地址赋给pn
,pn
是被声明为指向int
的指针。现在, pn
是地址,而*pn
是存储在那里的值。将这种方法与将变量的地址赋给指针进行比较:
int higgens;
int *pt=&higgens;
在这两种情况(pn
和pt
)下,都是将一个int
变量的地址赋给了指针。在第二种情况下,可以通过名称higgens
来访问该int
,在第一种情况下,则只能通过该指针进行访问。这引出了一个问题:pn
指向的内存没有名称,如何称呼它呢?我们说pn
指向一个数据对象,这里的“对象”不是“面向对象编程”中的对象,而是一样“东西”。术语“数据对象”比“变量”更通用,它指的是为数据项分配的内存块。因此, 变量也是数据对象,但pn
指向的内存不是变量。乍一看,处理数据对象的指针方法可能不太好用,但它使程序在管理内存方面有更大的控制权。
为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:
typeName * pointer_name = new typeName;
需要在两个地方指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。当然,如果已经声明了相应类型的指针,则可以使用该指针,而不用再声明一个新的指针。
#include<iostream>
using namespace std;
int main(){
int *pt=new int;
*pt=1;
cout<<pt<<endl<<*pt<<endl;
pt=new int;
*pt=2;
cout<<pt<<endl<<*pt<<endl;
return 0;
}
//输出:
//0x234e288bfa0
//1
//0x234e28a9fa0
//2
当然,内存位置的准确值随系统而异。
对于指针,需要指出的另一点是,new
分配的内存块通常与常规变量声明分配的内存块不同。一般变量的值都存储在被称为栈(stack)的内存区域中,而new
从被称为堆(heap)或自由存储区(free store) 的内存区域分配内存。
二、使用delete
释放内存
当需要内存时,可以使用new
来请求,这只是C++内存管理数据包中有魅力的一个方面。另一个方面是delete
运算符,它使得在使用完内存后,能够将其归还给内存池,这是通向最有效地使用内存的关键一 步。归还或释放(free)的内存可供程序的其他部分使用。使用delete
时,后面要加上指向内存块的指针(这些内存块最初是用new
分配的)
int *p=new int;
delete p;
这将释放p
指向的内存,但不会删除指针p
本身。例如, 可以将p
重新指向另一个新分配的内存块。一定要配对地使用new
和delete
;否则将发生内存泄漏(memory leak),也就是说,被分配的内存再也无法使用了。如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。不要尝试释放已经释放的内存块,C++标准指出,这样做的结果将是不确定的,这意味着什么情况都可能发生。另外,不能使用delete
来释放声明变量所获得的内存。但是对空指针应用delete
是安全的。
int *p=new int; //可行
delete p; //可行
delete p; //二次删除不可行
int a=1;
int *p=&a; //可行
delete pi; //不可行
注意,使用delete
的关键在于,将它用于new
分配的内存。这并不意味着要使用用于new
的指针,而是用于new
的地址。一般来说,不要创建两个指向同一个内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。
三、使用new
来创建动态数组
如果程序只需要一个值,则可能会声明一个简单变量,因为对于管理一个小型数据对象来说,这样做比使用new
和指针更简单,尽管给人留下的印象不那么深刻。通常,对于大型数据(如数组、字符串和结构),应使用new
,这正是new
的用武之地。例如,假设要编写一个程序,它是否需要数组取决于运行时 用户提供的信息。如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那里,它占用了内存。在编译时给数组分配内存被称为静态联编(static binding),意味着数组是在编译时加入到程序中的。但使用new
时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编(dynamic binding),意味着数组是在程序运行时创建的。这种数组叫作动态数组(dynamic array)。使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
在C++中,创建动态数组很容易;只要将数组的元素类型和元素数目告诉new
即可。必须在类型名后加上方括号,其中包含元素数目。
int *a=new int [10];
//创建一个包含十个int元素的数组
new
运算符返回第一个元素的地址。在这个例子中,该地址被赋给指针a
。
当程序使用完new
分配的内存块时,应使用delete
释放它们。然而, 对于使用new
创建的数组,应使用另一种格式的delete
来释放。
delete [] a;
方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。请注意delete
和指针之间的方括号。 如果使用 new
时,不带方括号,则使用delete
时,也不应带方括号。如果使用 new
时带方括号,则使用delete
时也应带方括号。
程序跟踪了创建动态数组时分配的内存量,以便以后使用delete []
运算符时能够正确地释放这些内存。 但这种信息不是公用的,例如,不能使用sizeof
运算符来确定动态分配的数组包含的字节数。
仍然使用本章的例子,可以将a
看作是一根指向该元素的手指。假设int
占4个字节,则将手指沿正确的方向移动4个字节, 手指将指向第2个元素。总共有10个元素,这就是手指的移动范围。
现在从实际角度考虑这个问题。如何访问其中的元素呢?第一个元素不成问题。由于a
指向数组的第1个元素,因此*a
是第1个元素的值。这样,还有9个元素,只要把指针当作数组名使用即可。也就是说,对于第1个元素,可以使用a[0]
,而不是一定是*a
;对于第2个元素,可以使用a[1]
,依此类推。这样,使用指针来访 问动态数组就非常简单了,虽然还不知道为何这种方法管用。可以这样做的原因是,C和C++内部都使用指针来处理数组。数组和指针基本等价是C和C++的优点之一(这在有时候也是个问题,但这是另一码事)。
#include<iostream>
using namespace std;
int main(){
double *a=new double [3];
a[0]=0.1,a[1]=0.2,a[2]=0.3;
cout<<a[0]<<" "<<*a<<endl;
cout<<a[1]<<" "<<a[2]<<endl;
a=a+1;
cout<<*a<<endl;
return 0;
}
//输出:
//0.1 0.1
//0.2 0.3
//0.2
上面的程序将指针a
当作数组名来使用,a[0]
为第1个元素,依次类推。下面的代码行指出了数组名和指针之间的根本差别。
a=a+1;
不能修改数组名的值。但指针是变量,因此可以修改它的值。请注意将a
加1
的效果。表达式a[2]
现在指的是数组的第2
个值。因此, 将a
加1
导致它指向第2
个元素而不是第1
个。将它减1
后,指针将指向原来的值,这样程序便可以给delete[]
提供正确的地址。
相邻的int
地址通常相差2个字节或4个字节,而将a
加1后,它将指向下一个元素的地址,这表明指针算术有一些特别的地方。情况确实如此。
我的博客换域名了,刚备案下来,请将墨冢这个改一下,感谢。
名称:异数
链接:https://www.yishu.pro/
描述:笔落惊风雨,诗成泣鬼神。
头像:https://www.yishu.pro/img/logo.jpg链接已加好:https://www.yishu.pro/index.php/links.html
对了,博客之前的友链,现已更名。
原名:春花秋月
新名:我的飛鳥集
麻烦有空更改呢~