这里的动态创建对象,特指在程序中通过new命令创建对象;而撤销,特指通过delete命令来删除对象并释放其内存空间。
new和delete的基本用法
new
命令会在堆区域分配创建一个对象,而后返回此对象的地址。
delete
命令会释放指针指向的对象所占用的内存空间,而此后指针指向的地址是没有意义的,为了避免错误,一般来说,应该在delete后立即将指针置为NULL。
1 2 3 4 5 6 7
| int *pi=new int;
delete pi;
pi=NULL;
|
注意:delete
命令只能用来释放由new
获得的指针,而且new
得到的指针必须通过delete
释放掉,否则会有内存泄漏的问题。
动态创建对象的默认初始化
用new
创建的对象的默认初始化规则与局部变量的初始化规则相同,即:对内置类型,不进行初始化;对于类类型变量,用默认构造函数进行初始化。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include<iostream>
using namespace std;
class MyClass //类定义 { public: MyClass(); };
MyClass::MyClass() { cout<<"MyClass-默认构造函数"<<endl; }
int main() { int *pi=new int;
cout<<"*pi="<<*pi<<endl;
MyClass *pClass=new MyClass;
return 0; }
|
程序运行结果为:
1 2 3 4 5
| *pi=-842150451
MyClass-默认构造函数
|
可见对pi 没有进行初始化,对pClass调用了MyClass类的默认构造函数。所以对于内置类型,用new申请变量时必须进行初始化,否则其指向的值是未知的。
new int; or new int(); ?
这两种形式,有没有区别?哪一种对?到底该用哪一种呢?一开始我也搞不清楚,慢慢查查才明白其中区别。
new int()这种形式叫值初始化(value-initialize),与动态创建的不同:对于内置类型,动态创建不会对其进行初始化;而值初始化会进行初始化。对于类类型变量,两种形式都会调用默认构造函数,所以没有区别。
如下例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include<iostream>
using namespace std;
class MyClass //类定义 { public: MyClass(); ~MyClass();
private:
};
MyClass::MyClass() { cout<<"MyClass-默认构造函数"<<endl; }
MyClass::~MyClass() { } int main() { int *p1=new int;
int *p2=new int();
int *p3=new int(23);
cout<<"*p1="<<*p1<<endl;
cout<<"*p2="<<*p2<<endl;
cout<<"*p3="<<*p3<<endl;
MyClass *pClass1=new MyClass;
MyClass *pClass2=new MyClass();
delete pClass2;
delete pClass1;
delete p3;
delete p2;
delete p1;
return 0; }
|
程序运行结果为
1 2 3 4 5 6 7 8 9 10
| *p1=-842150451
*p2=0
*p3=23
MyClass-默认构造函数
MyClass-默认构造函数
|
由结果可推得上述结论。
对内置类型,建议使用值初始化,可以使用特定的值对其进行初始化,如上例中p3的初始化。对于类类型,一般用第一种。
delete or delete[] ?
其实这两者的区别是很明显的, 前者是释放一个位置,而后者是释放一个数组,一段位置。在使用delete[]时,编译器会获取被释放对象new时申请的数据大小size,然后全部释放size个数据。可以认为,用new申请的,用delete释放;用new[]申请的,用delete[]释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include<iostream>
using namespace std;
int main() {
int *pi=new int(23);
float *pf=new float[256];
string *pstring=new string[128];
delete pi;
delete[] pf;
delete pstring;
return 0; }
|
在上面例子中,pstring释放的格式是错误的,相当于是只释放了pstring[0],后面的127个对象都没有被正确释放。
指针数组与指针的指针
指针数组的每个成员是指针,相当于是一系列指针的集合;而指针的指针就是字面意思所表示的,指向指针的指针。
这两者其实是有关系的:因为数组名相当于一个指针,所以指针数组可以看作指针的指针+特定内存空间。某些情况下,两者可以混用。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include<iostream>
using namespace std;
const int STUDENT_NUM=1024;
struct Student //Student结构体定义 { int number; int age; };
int main() { Student **ppStudent=new Student*[STUDENT_NUM];
for(int i=0;i<STUDENT_NUM;i++) { ppStudent[i]=new Student;
ppStudent[i]->number=i;
ppStudent[i]->age=22;
}
for(int i=0;i<STUDENT_NUM;i++) { delete ppStudent[i]; }
delete[] ppStudent;
return 0; }
|
可以看到ppStudent是指针的指针,但其初始化时却用的时指针的数组。
很容易忽略的一点是:在释放时忘记释放每个ppStudent[i]。为了避免这种错误,可以强制性地对应new[]和delete[],new和delete,有new[]必有对应的delete[],有new必有对应的delete。
单向链表的创建和释放
链表的动态创建和释放也很容易犯错,写程序时需要多关注有关new和delete的细节部分。
下面是一个不带头结点的链表的动态创建和释放:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include<iostream>
using namespace std;
struct Node { int data;
Node* next;
};
int main() { Node *list=new Node;
list->data=0;
list->next=NULL;
for(int i=1;i<1000000;i++) {
Node *pNode=list;
while(pNode->next!=NULL) { pNode=pNode->next; }
pNode->next=new Node;
pNode->next->data=i;
pNode->next->next=NULL;
}
if(list!=NULL) { while(list->next!=NULL) { Node *p=list;
list=list->next;
delete p; } }
return 0; }
|