定义变量时,必须制定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new表达式返回指向新创建对象的指针,我们通过该指针来访问此对象。
int i;
int *pi = new int;
这个new表达式在自由存储区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi。
1. 动态创建对象的初始化
动态创建的对象可用初始化变量的方式实现初始化:
int i(1024);
int *pi = new int(1024);
string s(10,'9');
string *ps = new string(10,'9');
C++使用直接初始化语法规则初始化动态创建的对象。如果提供了初值,new表达式分配到所需要的内存后,用给定的初值初始化该内存空间。pi所指向的新创建对象将被初始化为1024,ps指向的对象初始化为十个9的字符串。
2. 动态创建对象的默认初始化
如果不提供显示初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
string *ps = new string;
int *pi = new int; //pi points to an uninitialized int
通常,除了对其复制之外,对未初始化的对象所关联的值的的任何使用都是没有定义的。
同样也可对动态创建的对象做值初始化:
string *ps = new string(); // initialized to empty string
int *pi = new int(); // pi points to an int value-initialized to 0
cls *pc = new cls(); // pc points to a value-initialized object of type cls
以上表明程序员想通过在类型名后面使用一对内容为空的圆括号对动态创建的对象做值初始化。内容为空的圆括号表示虽然要做初始化,但实际上并未提供特定的初值。对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化:无论程序是明确地不初始化还是要求进行值初始化,都会自动调用其默认构造函数初始化该对象。而对于内置类型或没有定义默认构造函数的类型,采用不同初始化方式则有显著的差别:
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(); // pi points to an int value-initialized to 0
第一个语句的int型变量没有初始化,而第二个语句的int型变量则被初始化为0。
3. 耗尽内存
尽管现代机器的内存容量越来越大,但是自由存储区宗有可能被耗尽。如果程序用完了可用的内存,new表达式就有可能失败。如果new表达式无法获取需要的内存空间,系统将抛出名为bad_alloc的异常。
4. 撤销动态创建的对象
动态创建的对象用完后,程序员必须显示地将该对象占用的内存返回给自由存储区。C++提供了delete表达式释放指针所指向的地址空间。
delete pi;
该命令释放pi指向的int型对象所占用的内存空间。如果指针指向不是用new分配的内存地址,则在该指针上使用delete是不合法的。
C++没有明确定义如何释放指向不是用new分配的内存地址的指针。下面提供了一些安全的和不安全的delete表达式。
int i;
int *pi = &i;
string str = "dwarves";
double *pd = new double(33);
delete str; //error: str is not dymamic object
delete pi; //error: pi refers to a local
delete pd; //ok
值得注意的是:编译器可能会拒绝编译str的delete语句。编译器知道str并不是一个指针。因此会在编译时就能检查出这个错误。第二个错误则比较隐蔽:通常来说,编译器不能断定一个指针指向什么类型的对象,因此尽管这个语句是错误的,但在大部分编译器上仍能通过。
5. 零值指针的删除
如果指针的值为0,则在其上做delete操作是合法的,但这样做没有任何意义:
int *ip = 0;
delete ip; // ok
C++中,删除0值的指针是安全的。
6. 在delete之后,重设指针的值
执行语句
delete p;
后,p变成不确定的指针。在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指向对象的地址,然而p所指向的内存已经释放,因此p不再有效。删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存。但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。
一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。
7. const 对象的动态分配和回收
C++允许动态创建const对象:
const int *pci = new const int(1024);
与其他常量一样,动态创建的const对象必须在创建时初始化,并且一经过初始化,其值就不能再修改。上述new表达式返回指向int型const对象的指针。与其他const对象的地址一样,由于new返回的地址上存放的是const对象,因此该地址只能赋给指向const的指针。
对于类类型的const动态对象,如果该类提供了默认的构造函数,则此对象可隐式初始化:
const string *pcs = new const string;
new 表达式没有显示初始化 pcs 所指向的对象,而是隐式地将 pcs 所指向的对象初始化为空的string对象。内置类型对象或未提供默认构造函数的类类型对象必须显示初始化。
警告:动态内存的管理容易出错
下面三种常见的程序错误都与动态内存分配有关:
(1) 删除(delete)指向动态分配内存的智真失败,因而无法将该块内存返回给自由存储区。删除动态内存失败为“内存泄露(memory leak)”。内存泄露很难发现,一般需等应用程序运行一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。
(2)读写已删除的对象。如果删除指针所指向的对象之后,将指针置为0值,则比较容易检测出这类错误。
(3)对同一个内存空间使用两次delete表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做delete运算,将该对象的内存空间返还给自由存储区,然后接着delete第二个指针,此时自由存储区可能会被破坏。
操纵动态分配的内存时,很容易发生上述错误,但这些错误却很难以跟踪和修正。
8. 删除const对象
尽管程序员不能改变const对象的值,但可撤销对象本身。如同其他动态对象一样,const动态对象也是使用删除指针来释放的:
delete pci;
及时delete表达式的操作数是指向int 型const 对象的指针,该语句同样有效地回收pci所指向的内容。