使用new
来分配内存
之前是使用以下模式来分配指针的地址:
int fellow;
int* pt = &fellow;
变量fellow
是在编译时分配的有名称的内存,而指针只是为了可以通过名称直接访问的内存提供了一个别名。
而指针的真正的用武之地在于,在运行阶段分配内存名的内存以存储值。这种情况只能使用指针来访问内存。
例如:
int* pt = new int;
在这里,new
会在这里寻找一个适合int
类型长度正确的内存块,并返回该内存块的地址给指针pt
。
int nights = 1001;
int* pt = new int;
*pt = 1001;
在上面的例子,因为pt
以及分配了内存,所以可以使*pt = 1001
。因为此时已经知道指针pt
所存储的地址。
内存被耗尽?
计算机可能会由于没有足够的内存而无法满足new
的请求。在这种情况下,new
通常会引发异常—一种将在第15章讨论的错误处理技术;而在较老的实现中,new
将返回0
。在C++中,值为0
的指针被成为空指针(null pointer)。C++确保空指针不会指向有效的数据,因此它常被用来表示运算符或函数失败(如果成功,它们将返回一个有用的指针)。将在第6章讨论的if
语句可帮助您处理这种问题;就目前而言,您只需如下要点:C++提供了检测并处理内存分配失败的工具。
使用delete
释放内存
当需要内存的时候,我们可以使用new
来请求内存。当使用完内存的时候,我们需要将内存归还给内存池,使用delete
运算符来释放。归还或释放(free)的内存可供程序的其他部分使用。
int* ps = new int; //allocate memory with new
... //use the memory
deletes ps; //free memory with delete when done
这里释放了指针ps
的所分配的内存,但是不会删除ps
变量。
这里new
和delete
一定是配对使用的。否则会发送内存泄露(memory leak),会占用内存,是内存无法使用,积累下来,会造成内存不够而使程序终止。
- 第一,不能用
delete
来释放已经释放的内存块,即指针,C++标准指出,这样做的结果将是不正确的,意味着everything will happen。
int* ps = new int; //ok
delete ps; //ok
delete ps; //not ok now
- 第二,不能使用
delete
来释放声明变量所获得的内存的指针;
int jugs = 5; //ok
int* pi = &jugs; //ok
delete pi; //not allowed,memory not allocated by new
警告:只能用delete
来释放使用new
分配的内存。然而,对空指针使用delete
是安全的。
new
与数组
如果使用new
和delete
是分配和释放一个值,则是简单的运用一个内存块。
但是对于大型数据(如数组,字符串和结构),才是new
真正的用武之地。
使用
new
是运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态编程(dynamic binding),意味着数组是在程序运行时创建的。这种数组叫作动态数组(dynamic array)。
使用new
创建动态数组
需要将数组的元素类型和元素数目告诉new
即可。必须在等号右边类型名后加上方括号,其中包含元素数目。
例如,要创建 一个包含10个int
元素的数组:
int* psome = new int [10]; //get a block of 10 ints
运算符new
会返回第一个元素的地址给指针psome
。
使用完new
分配的内存块时,也应该使用delete
来释放它们。
delete[] psome; //free a dynamic array
使用
new
和delete
的,应遵守以下规则:
- 不要使用
delete
来释放不是new
分配的内存;- 不要使用
delete
释放同一个内存块两次;- 如果使用
new[]
为数组分配内存,则应使用delete[]
来释放;- 如果使用
new
为一个实体分配内存,则应使用delete
(没有方括号)来释放;- 对空指针应用
delete
是安全的。
对于new
分配的动态数组,psome
是指向一个int
(数组第一个元素)的指针。然而编译器不能对psome
是指向10个整数中的第一个进行跟踪其他的元素,所以需要让程序知道跟踪元素的数目,因此在编写程序的时候,需要写明数组元素的数目。
实际上,程序在确定跟踪的分配的内存量之后,也方便最后使用delete[]
运算符时能够正确地释放内存。
为数组分配内存的通用格式如下:
type_name* pointer_name = new type_name [num_elements];
使用new
运算符可以确保内存块足以存储num_elements
个类型为type_name
的元素,而pointer_name
指向第一个元素。
使用动态数组
new
语句提供了识别内存块中每个元素所需要的全部信息;
而且,只要将指针当作数组名使用,就可以知道其他元素的数值(数值和指针基本等价时C和C++的优点之一)。
通过以下例子:
double* p3 = new double [3]; //space for 3 doubles
p3[0] = 0.2; //treat p3 like an array name
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is "<<p3[1]<<".\n";
p3 = p3 + 1; //increment the pointer
cout << "Now p3[1] is "<<p3[1]<<" and ";
cout << "p3[1] is "<<p3[1]<<".\n";
p3 = p3 - 1; //point back to begining
deleta[] p3; //free the memory
return 0;
下面是程序的输出结果:
p3[1] is 0.5.
Now p3[0] is 0.5 and p3[1] is 0.8.
- 程序将指针p3当做数组名来使用,从而可以知道其他数组元素的值;
- 当
p3 = p3 + 1
,已经将指针p3
由指向p3[0]
变成指向p3[1]
,由此可知,new
提供了识别内存块中每个元素所需要的全部信息,同时将指针变量加1后,其增加的值等于指向的类型占用的字节数; - 经过
p3 = p3 - 1
,需要将指针p3
返回原来的值,从而方便delete[]
的释放内存提供正确的值。
short stacks[3] = {3,2,1};
cout << "access two elements with array notation\n";
cout << "stacks[0] = " << stacks[0]
<< ", stacks[1] = " << stacks[1] << endl;
cout << "access two element with pointer notation\n";
cout << "*stacks = " << *stacks
<< ", *(stacks + 1) = " << *(stacks + 1) << endl;
结果是:
access two elements with array notation
stacks[0] = 3, stacks[1] = 2
access two element with pointer notation
*stacks = 3, *(stacks + 1) = 2
- C++编译器将该表达式看作是
*(stacks + 1)
,这意味着先计算数组第2个元素的地址,然后找到存储在那里的值。最后的结果便是stacks[1]
的含义(运算符优先使用括号,如果不使用括号,将给*stacks
加1,而不是给stacks+1); - 同时
*(stecks+1)
和stacks[1]
是等价的。
关于数组名和指针对于运算的区别
例如都可以进行以下执行:
arraymame[i] becomes *(arrayname + i);
pointername[i] becomes *(pointername + i);
但是数组无法像指针一样执行下面的算法,因为数组名是一个常量:
pointername = pointername + 1; //valid
arrayname = arrayname + 1; //not allowed
当使用sizeof时,求指针pw
,得到指针的长度,求数组名wages
,得到的是数组的长度
24 = size of wages array << displaying sizeof wages
4 = size of pw pointer << displaying sizeof pw
这种情况下,C++不会将数组名解释为地址。
在之前,给数组声明指针,都是数组名直接是地址,或者对首个数组的值取地址,如果对数组取地址应该怎么取:
short (*pas)[20] = &tell; //pas points to array of 20 shorts
此处是将pas
和*
先 进行结合,&tell
是一个指向包含20个元素的short
的数组。pas
被设置为&tell
,因此*pas
与tell
等价,因此(*pas)[0]
为tell
数组的第一个元素。
#include<iostream>
int main()
{
using namespace std;
short tell[10] = { 1,2,3,4,5,6,7,8,9,10 };
short* ps = tell;
cout << "The ps is "<<ps << endl;
cout << "The tell is " << tell << endl;
cout << "The tell+1 is " << tell+1 << endl;
cout << "The &tell is " << &tell << endl;
cout << "The &tell+1 is " << &tell+1 << endl;
cin.get();
return 0;
}
输出结果是:
The ps is 0053FD9C
The tell is 0053FD9C
The tell+1 is 0053FD9E
The &tell is 0053FD9C
The &tell+1 is 0053FDB0
指针和字符串
请看以下代码:
char flower[10] = "rose";
cout << flower <<"s are red\n";
由前面所知,数组名是第一个元素的地址,所以语句中flower
是包含字符r
的char
元素的地址。cout
对象认为char
的地址是字符串的地址,因此它打印该地址处的字符,然后会继续打印后面的字符,直到遇到空字符(\0)
为止。
因此,在这里需要记住的一点是,flower
是一个char
的地址,而不是一个数组名。
如果需要将一个数组的地址赋值给一个指针且使用同样的内存单元和字符串,则使用=
运算符,如果是需要将字符串赋值给一个指针且同时分配新的内存,则使用strcpy()
或strncpy()
,例如:
char animal[20] = "bear";
char* ps;
ps = animal; //set ps to point to string
ps = new char[strlen(animal) + 1]; //get new storage
strcpy(ps,animal); //copy string to new storage
注意:经常需要将字符串放到数组中。初始化数组时,请使用=
运算符;否则应使用strcpy()
或strncpy()
。