C++动态内存分配
new运算符
1.C++中,用new和delete动态创建和释放数组或单个对象。
动态创建对象时,只需指定其数据类型,而不必为该对象命名,new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。
int*pi=new int;
这个new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi 。
2动态创建对象的初始化
动态创建的对象可以用初始化变量的方式初始化。
int*pi=new int(100); //指针pi所指向的对象初始化为100
string*ps=new string(10,'9');//*ps 为“9999999999”
如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
也可以对动态创建的对象做值初始化:
int*pi=new int( );//初始化为0
int*pi=new int;//pi 指向一个没有初始化的int
string*ps=new string( );//初始化为空字符串 (对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化)
3撤销动态创建的对象
delete表达式释放指针指向的地址空间。
deletepi ;// 释放单个对象
delete[ ]pi;//释放数组
如果指针指向的不是new分配的内存地址,则使用delete是不合法的。
4在delete之后,重设指针的值
deletep; //执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。
一旦删除了指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int*ip=0;)
5区分零值指针和NULL指针
零值指针,是值是0的指针,可以是任何一种指针类型,可以是通用变体类型void*也可以是char*,int*等等。
空指针,其实空指针只是一种编程概念,就如一个容器可能有空和非空两种基本状态,而在非空时可能里面存储了一个数值是0,因此空指针是人为认为的指针不提供任何地址讯息。
6new分配失败时,返回什么?
1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[])以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象.
操作符new 和new[ ]
操作符new的存在是为了要求动态内存。new 后面跟一个数据类型,并跟一对可选的方括号[ ]里面为要求的元素数。它返回一个指向内存块开始位置的指针。其形式为:
pointer= new type
或者
pointer= new type [elements]
第一个表达式用来给一个单元素的数据类型分配内存。第二个表达式用来给一个数组分配内存。
例如:
int* bobby;
bobby= new int [5];
在这个例子里,操作系统分配了可存储5个整型int元素的内存空间,返回指向这块空间开始位置的指针并将它赋给bobby。因此,现在bobby 指向一块可存储5个整型元素的合法的内存空间,如下图所示。
C++动态内存分配
你可能会问我们刚才所作的给指针分配内存空间与定义一个普通的数组有什么不同。最重要的不同是,数组的长度必须是一个常量,这就将它的大小在程序执行之前的设计阶段就被决定了。而采用动态内存分配,数组的长度可以常量或变量,其值可以在程序执行过程中再确定。
动态内存分配通常由操作系统控制,在多任务的环境中,它可以被多个应用(applications)共享,因此内存有可能被用光。如果这种情况发生,操作系统将不能在遇到操作符new 时分配所需的内存,一个无效指针(nullpointer)将被返回。因此,我们建议在使用new之后总是检查返回的指针是否为空(null),如下例所示:
int* bobby;
bobby= new int [5];
if(bobby == NULL) {
//error assigning memory. Take measures.
};
删除操作符delete
既然动态分配的内存只是在程序运行的某一具体阶段才有用,那么一旦它不再被需要时就应该被释放,以便给后面的内存申请使用。操作符delete 因此而产生,它的形式是:
deletepointer;
或
delete[ ] pointer;
第一种表达形式用来删除给单个元素分配的内存,第二种表达形式用来删除多元素(数组)的内存分配。在多数编译器中两种表达式等价,使用没有区别, 虽然它们实际上是两种不同的操作,需要考虑操作符重载
NULL是C++库中定义的一个常量,专门设计用来指代空指针的。如果这个常量没有被预先定义,你可以自己定以它为0:
#defineNULL 0
在检查指针的时候,0和NULL并没有区别。但用NULL 来表示空指针更为常用,并且更易懂。原因是指针很少被用来比较大小或被直接赋予一个除0以外的数字常量,使用NULL,这一赋值行为就被符号化了。
#include<iostream>
#include<string>
usingnamespace std;
intmain ( )
{
char input [100];
int i,n;
long * l;
cout << "How many numbers doyou want to type in? ";
cin.getline (input,100);
i=atoi(input);
l= new long[i];
if (l == NULL)
{
exit (1);
}
for (n=0; n<i; n++)
{
cout << "Enternumber: ";
cin.getline (input,100);
l[n]=atol (input);
}
cout << "You have entered:";
for (n=0; n<i; n++)
{
cout << l[n] <<", ";
}
delete[] l;
return 0;
}
ANSIC中的动态内存管理Dynamicmemory in ANSIC
操作符new 和delete 仅在C++中有效,而在C语言中没有。在C语言中,为了动态分配内存,我们必须求助于函数库stdlib.h。因为该函数库在C++中仍然有效,并且在一些现存的程序仍然使用,所以我们下面将学习一些关于这个函数库中的函数用法。
之前一直不是很明白为什么当一个类中还有一个要动态分配内存的指针之类的数据成员时一定要声明一个拷贝构造函数和一个赋值操作符。(尽管你在构造函数和析构函数中分别进行了new和delete操作)
原因是看下面介绍:
当你没定义这2个函数时,
那么当你声明2个对象时,比如
tmpclassa("hello");
tmpclassb("world");
这个时候都是完好正确的,a指向"hello/0",b指向"world/0"。
但当你进行如下操作时,即
b=a;时,由于你没有定义赋值操作符,那么C++会自动生成并调用一个缺省的operator=操作符。这个缺省的赋值操作符会执行从a的成员到b成员的逐个成员的赋值操作,对指针来说就是逐位拷贝。
那么赋值后,就会出现2个问题,
1:b曾指向的内存永远不会被删除,永远丢失,这时产生内存泄露的典型例子
2:现在a和b包含的指针都指向了同一个字符串的地址。那么当一个离开他的生存空间时,其析构函数就会删除掉那块内存。那么另一个对象指向的就是非法空间了。
下面这个代码就说明了这一点、
注意,造成这个问题的原因是C++会自动给没有定义赋值操作符的类生成了一个缺省的操作符函数,而这个操作符函数是个位拷贝的进行赋值
#include<iostream>
usingnamespace std;
classstring1 {
public:
string1(const char *value);
~string1();
// 没有拷贝构造函数和operator=
char *data;
};
string1::string1(constchar *value)
{
if (value) {
data = new char[strlen(value) + 1];
strcpy(data, value);
}
else {
data = new char[1];
*data = '/0';
}
}
voiddonothing(string1 localstring) {
cout<<localstring.data<<endl;
}
string1::~string1() { delete [] data; }
intmain(){
string1 s = "the truth is outthere";
donothing(s);//该函数调用时localstring会自动调用缺省的拷贝构造函数,也是执行的是位拷贝,所以函数结束后,自动调用析构函数,导致s指向的内存被释放了
cout<<s<<endl;
string1 a("hello");
int flag=1;
while(flag){
flag=0;
string1 b("world");
cout<<a.data<<''<<b.data<<endl;
a=b;
cout<<a.data<<''<<b.data<<endl;
}
string1 c=a;
cout<<c.data<< ""<<a.data<<endl;
}
解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。