一、Effective C++条款16:成对使用new和delete时采用相同的形式
通常我们使用new和delete有两种情形,第一,动态的为单一对象分配内存,第二,动态的创建数组。new和delete的使用需遵循许多规则,这里着重理解相同形式这一关键词。且看下面的动作有什么错?
std::string *stringArray = new std::string[100];
...delete stringArray;
乍看起来,同时使用了new和delete,似乎没什么错误。这里使用new在运行阶段动态分配了100string对象,但是仅使用delete只释放掉了单一对象的所占用的内存,其中99个string对象不太可能被适当的删除,因为它们的析构函数没有被调用。
请注意:这里仅仅同时使用了new和delete,但并没有满足相同的形式这一条件。我们所面临的问题可以更简单的叙述为:即将被删除的那个指针,所指的是单一对象还是对象数组?另外,请尽量使用C++11智能指针,以防止写代码时忘记释放掉new分配的内存而导致内存泄漏。
请记住
如果在new表达式中使用了[],必须在相应的delete表达式中使用[]。如果在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。确保“同时使用、相同形式”。
二、使用new动态分配内存
为单一对象(可以是数据结构,基本类型或类)获得并指定分配内存的通用格式如下:
typeName *pointter_name = new typeName;
例如,在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值,可以写为:int * pn = new int;
程序员告诉new要为int类型的数据类型分配内存,new根据这种数据类型找到长度(在这里int为4个字节)正确的内存块,并返回内存块的地址,程序员负责将该地址赋给一个指针(pn是被声明为指向int的指针,现在pn是一个地址,而*pn为存储在那里的值)。
请看下面的例子:
int *pt = new int;
*pt = 1001;
double *pd = new double;
*pd = 3.14;
则有:如果这里int占4个字节,double占8个字节,则sizeof(pt)为4,sizeof(*pt)为4。sizeof(pd)为4,sizeof(*pd)为8。这里,指向int的指针的长度与指向double的指针相同,它们都是地址,但由于声明了指针的类型,因此程序知道*pd为8个字节的double值,*pt为4个字节的int值。
三、使用new和delete时要遵循的规则
- 不要使用delete释放不是new分配的内存。
- 不要使用delete释放同一个内存两次。
- 使用new[]为数组分配的内存一定要使用delete[]释放,不能使用delete;new为单个对象分配的内存一定使用delete来释放。
- 对空指针delete是安全的。
四、使用动态数组
创建动态数组后,如何使用数组中的元素?
int *pt = new int[10]; // get a block of 10 ints
如果这里int占4个字节,上述语句中sizeof(pt)为4个字节,sizeof(*pt)为4 × 10个字节。那么如何使用数组中的元素呢?其实和普通数组一样。
double *pd = new double[3];
pd[0] = 1.0;
pd[1] = 2.0;
pd[2] = 3.0;
那么,pd[0](或*pd) 的值为1.0。如果有pd = pd + 1;, 那么pd[0]的值为2.0。另外请注意,不能修改数组名(常量),但指针(pd)为变量,因此可以修改它的值。而且pd+1的效果是指指针指向数组第一个元素的下一个位置,即第二个元素,因此pd[0]为2.0。
五、杂项
指针和数组等价的原因在于指针算术和C++内部处理数组的方式。首先我们来看一下算术,将整型变量加1后,其值将增加1;但将指针变量增加1后,增加的量等于它指向的数据类型的字节数,如:将指向double的指针加1后,如果系统对double使用8个字节存储,则其值增加8。另外,C++将数组名解释为数组第一个元素的地址。
double arrD[3] = {1.0, 2.0, 3.0};
short arrSh[3] = {1, 2, 3};
double *pd = arrD; // 等价于&arrD[0]
short *psh = &arrSh[0];
...
假设double占4个字节,short占2个字节,则:arrD[0] ,*arrD和*(arrD + 0)等价,其值为1;*psh为1。
sizeof(arrD)为3 × 8 = 24,sizeof(pd)为4,sizeof(*pd)为8。
sizeof(arrSh)为3 × 2 = 6,sizeof(psh)为4,sizeof(*psh)为2。
这是因为 指针为指向变量的地址,且地址占4个字节;对指针的解引用为指针指向变量的值,所占内存谓该值所属类型所占的字节数。
注意:虽然数组名被解释为指向数组第一个元素的地址,但不能修改数组名,是因为数组名为常量;可以修改指针,是因为指针(指向变量的地址)为变量。
pointerName = pointerName + 1; // valid
arrayName = arrayName + 1; // not alllowed
数组的地址
数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址。
short tell[10]; // tell为20字节的数组
cout << tell ; // &tell[0] 即数组第一个元素的地址,占2个字节的内存块的地址
cout << &tell; // 整个数组的地址,从数字上说,该地址和tell的地址相同,但是&tell为占20个字节的内存块的地址
总之,使用new来创建数组以及使用指针来访问不同的元素时,只需要把指针当成数组名对待即可。