new
-
delete
操作符与
malloc()
和
free()
函数的区别
new
-
delete
操作符与
malloc()
和
free()
函数的区别
1. new
- delete
操作符与 malloc()
- free()
函数的区别
new
和delete
操作符用于取代malloc()
和free()
函数。new
能够真正地建立一个对象,则malloc()
函数只是分配内存。new
和delete
操作符用起来更方便 (能够自动完成sizeof
的计算工作,并会自动调用合适的构造函数和析构函数)。new
操作符可以在空闲存储器中为特定类型的新变量分配空间。堆的别名是空闲存储器。malloc()
函数从堆上动态分配内存。new
-delete
是 C++ 关键字,需要编译器支持。malloc()
-free()
是库函数,在头文件stdlib.h
中声明。new
内存分配失败时,则默认抛出一个使程序终止的异常,它不会返回NULL
。如果操作系统无法向malloc()
提供更多的内存,malloc()
就返回一个NULL
指针。对每个从malloc()
返回的指针都进行检查,确保它并非NULL
是非常重要的。free()
的参数必须要么是NULL
,要么是一个先前从malloc()
、calloc()
或realloc()
返回的值。向free()
传递一个NULL
参数不会产生任何效果。malloc()
返回一个类型为void *
的指针。标准表示一个void *
类型的指针可以转换为其他任何类型的指针。- 对于要求边界对齐的机器,
malloc()
所返回的内存的起始位置将始终能够满足对边界对齐要求最严格的类型的要求。
1.1 new
操作符
new
操作符动态分配空闲存储器中的内存。当根据请求成功分配内存之后,该操作符返回一个指向提供的内存区域头部的指针,如果因故不能分配内存,则默认抛出一个使程序终止的异常。
1.2 delete
操作符
使用 delete
操作符可以释放前面用 new
操作符分配的内存。
2. 动态内存分配
在执行期间,经常需要根据程序的输入数据,来决定应该给存储不同类型的变量分配的空间量。任何涉及处理事先不知道的数据项的程序,都可以利用这个功能在运行时分配内存来存储数据。
因为不可能在编译期间定义任何动态分配的变量,所以源程序中将没有它们的名称。当创建这些变量时,计算机用内存中相应的地址来标识它们,该地址存储在指针中。借助于指针的力量和 Visual C++
的动态内存管理工具,能很快、很轻松地使编写的程序具备这种灵活性。
2.1 堆的别名 - 空闲存储器
大多数情况下,当执行程序时,计算机中有部分未使用的内存。这些内存称为堆,有时还称为空闲存储器。使用一种特殊的操作符,可以在空闲存储器中为特定类型的新变量分配空间。该操作符返回分配给变量的内存地址,它就是 new
操作符,与其配对的是 delete
操作符,其作用是释放先前 new
分配的内存。
可以在程序中为某些变量在空闲存储器中分配空间,当不再需要这些变量时,再将分配的空间释放并返回到空闲存储器。这样,这部分内存就可以被其他动态分配的变量重用。这种技术非常强大,能够非常高效地使用内存。
智能指针是指针的替代品。动态分配内存时,尤其是给数组或更复杂的对象动态分配内存时,最好使用智能指针。不再需要智能指针时,会自动释放内存,所以我们不再需要担心内存的释放。
2.2 new
和 delete
操作符
假设某个 double
变量需要空间。我们可以定义一个指向 double
类型的指针,然后在运行时再请求分配内存。这项任务可以在下面的语句中用 new
操作符来完成:
double *pvalue {};
pvalue = new double; // Request memory for a double variable
所有指针都应该初始化。动态使用内存通常涉及大量四处漂浮的指针,因此确保它们不包含虚假值非常重要。如果指针没有包含合法的地址值,就应该将其设置为 nullptr
。
前面第二行代码中的 new
操作符应该返回空闲存储器中分配给 double
变量的内存地址,并在指针 pvalue
中存储该地址。之后,使用前面学过的间接寻址运算符,可以通过指针 pvalue
来引用 double
变量。
*pva1ue = 9999.0;
系统或许没有分配我们请求的内存,原因可能是空闲存储器中的内存己经用完,或者空闲存储器由于前面的使用而破碎 - 即没有足够的连续字节提供给需要获得空间的变量。不过,我们不必过多担心这一点。如果系统因故不能分配内存,那么 new
操作符将抛出一个异常,从而使程序终止。
new
创建的变量也可以初始化。以前面 new
分配的 double
变量为例,其地址存储在 pvalue
中,可以用下面这条语句,在创建该变量的同时将其初始化为 999.0
:
pvalue = new double {999.0}; // Allocate a double and initialize it
当然,也可以只用一条语句创建此指针并进行初始化:
double *pvalue {new double{999.0}};
当不再需要动态分配的某个变量时,可以用 delete
操作符将其占用的内存释放到空闲存储器中:
delete pvalue; // Release memory pointed to by pvalue
这样将确保这块内存以后可以被另一个变量使用。如果不使用 delete
,随后又在 pvalue
指针中存入一个不同的地址值,那么将无法再释放这块内存,或使用其包含的变量,因为我们失去了访问该地址的途径。这种情况称为内存泄漏 - 特别是出现在用户程序中的时候。
在释放指针指向的内存时,还应把该指针的值设置为 nullptr
,否则,就会出现悬挂的指针,我们可能通过这种指针访问已释放的内存。
2.3 为数组动态分配内存
为数组动态分配内存非常简单。要给 char
类型的数组分配空间,则可以编写下面的语句:
char *pstr {new char[20]}; // Allocate a string of twenty characters
该语句为 20 个字符的 char
数组分配空间,并将其地址存储入 pstr
中。为了删除刚才创建的数组,必须使用 delete
操作符。相应的语句如下所示:
delete [] pstr; // De1ete array pointed to by pstr
使用方括号是为了指出要删除的是一个数组。从空闲存储器中删除数组时,应该总是包括方括号,否则结果将不可预料。另外请注意,不用指定任何维数,只需要写出 []
即可。
当然,指针 pstr
现在包含的内存地址可能已经分配给有其他用途的变量,因此当然不应该再使用该指针。当使用 delete
操作符抛弃之前分配的某些内存之后,还应该总是将该指针重新设置成 nullptr
:
pstr = nullptr;
这可以确保我们不会试图访问己经删除的内存。将不再指向有效内存区域的指针复位成 nullptr
。
可以初始化在空闲存储器中分配空间的数组:
int *data {new int[10] {2,3,4}};
long *pprime {new long[4] {3L,6L,9L}};
第一条语句创建了一个包含 10 个整数元素的数组,并把前三个元素初始化为 2、3 和 4。其余元素初始化为 0。
第二条语句创建了一个包含 4 个长整数元素的数组,并把前三个元素初始化为 3L、6L 和 9L。其余元素初始化为 0。
2.4 多维数组的动态分配
与一维数组相比,在空闲存储器中为多维数组分配内存需要以略微复杂的形式使用 new
操作符。
假设己经声明了指针 pbeans
:
double (*pbeans)[4] {};
为了使本章前面曾经用过的数组 beans[3][4]
获得空间,可以使用下面这条语句:
pbeans = new double [3][4] // Allocate memory for a 3x4 array
只需要在数组元素类型名之后的方括号内指定数组维数即可。当然,也可以一步完成:
double (*pbeans)[4] {new double [3][4]};
如下所示,用 new
操作符给三维数组分配空间只需要再指定第三维即可:
auto pBigArray (new double [5][10][10]); // Allocate memory for a 5x10x10 array
这个语句使用 auto
自动推断指针类型。不要忘了,不能联合使用初始化列表和 auto
。可以编写如下语句:
auto pBigArray = new double[5][10][10]; // Allocate memory for a 5x10x10 array
无论创建的数组有多少维,都可以用下面这条语句将数组销毁,并将内存释放到空闲存储器中:
de1ete [] pBigArray; // Release memory for array
pBigArray = nullptr;
始终只需要在 delete
操作符后面跟上一对方括号即可,而无论相关数组的维数是多少。
可以使用变最来指定 new
分配的一维数组的维数。对二维或多维数组来说同样如此,但仅限于用变量指定最左边的那一维。所有其他维都必须是常量或常量表达式。因此可以这样写:
pBigArray = new double[max][10][10];
其中 max
是一个变量。不是最左边的维指定变量,将使编译器生成错误消息。