您的位置:首页 > 编程语言 > C语言/C++

C++编程思想--动态对象的创建(1)

2013-03-16 13:39 288 查看
动态对象创建
1、为什么不使用C中的函数:运行时可以创建和销毁对象,于是C中提供了动态内存分配,它们在运行时从堆中分配储存单元。Eg:malloc、free。以及他们的变体

(1). 但是在C++中因为构造函数不允许我们向它传递地址来进行初始化。若可以的话,很可能在对对象进行初始化之前意外地对对象进行了某种操作。

(2). Malloc.free是库函数,不在编译器的控制范围之内:它不是运算符,不能将类的构造函数与析构函数的任务强加于这两个函数上,使用它们需要包含头文件。New,delete的地位与加减乘数一样。若有一个完成动态内存分配及初始化组合动作的运算符和完成清理及释放内存组合动作的运算符,编译器可以保证所有对象的构造函数和析构函数都会被调用

2、对象创建

对象创建时会进行的操作是:

1)会为对象分配内存。它可能会在几种不同的方式中发生

①  在静态存储区域中,存储空间在程序开始之前就可以分配。这个储存空间在程序的整个运行期间都存在。

② 存储单元在栈上被创建,当执行到右大括号时,这个存储单元自动被释放,

③ 最后的是在堆中,这是一种动态的分配内存。意味着可以在任何时候决定分配内存以及多少内存,当然也需要决定何时释放内存。

这三个区域会放在一块连续的物理存储器中,堆的实现可以通过调用运算系统分配的一块存储单元

2)并调用构造函数来初始化内存。

为什么不使用C语言中的malloc与free函数,因为使用malloc申请了一个内存。对象使用之前必须进行初始化,但是使用这个C中的函数,构造函数并没有被使用。在构造函数中会自动的对成员变量进行初始化。它是不能显示被调用的,只能在对象创建的时候由编译器调用。在类中没有显示的定义自己的构造函数,编译器也会为我们自动创建一个。我们可能在使用对象的时候忘记进行初始化,于是程序就容易出错。

3)new,在C++中使用一个new运算符进行检查以确信在传递地址给函数之前内存分配是成功。

(1). 首先使用operator new分配内存,首先从堆里搜索一块足够大的内存来满足要求,如何做到呢,通过检查按某种方式排列的映射或目录来实现,在指向这块内存的指针返回之前,这块内存的大小和地址必须记录下来,这样,以后调用malloc就不会使用它,而且当调用free的时候就会知道释放多大的内存。所以在堆栈中自动创建对象时,对象的大小和他们的生存期被准确在内置在生成的代码里。这是因为编译器知道确切的类型、数量和范围。但是在堆里创建还包括时间和空间的开销。然后调用构造函数。

(3). new在堆上创建一个对象数组,会为每一个对象调用构造函数。在堆上创建对象需要的内存会比期望的多4个字节,这个是系统用来存放数组信息的。

(4). 若operator new在内存中找不到足够大的内存块来安排对象,那么new-handler这个特殊的函数将会被调用,首先检查指向函数的指针,如果函数的指针非0,则指向的函数将被调用。那么new-hander主要是做什么呢?它的默认动作是产生一个异常,若是在程序中用堆分配内存,可以使用内存已耗尽等信息代替new-handler,并异常中断。

#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;

int count=0;
void out_of_memory()
{
cerr<<"memory exhaust after "<<count<<" allocations!"<<endl;
exit(1);
}

int NewHandlerTest()
{
set_new_handler(out_of_memory);//当内存满的时候调用这个函数,把out_of_memory作为参数传递进去
//在函数体里面首先进行的是_BEGIN_LOCK(_LOCK_MALLOC),也就是阻止再次的创建,
//再创建一个new_handler类型,值为out_of_memory,调用这个函数,于是在控制台输出耗尽内存消息
while(1)
{
count++;
new int(1);
}
}

//int main()
//{
//	NewHandleTest();
//}


4)delete,首先调用析构函数,然后使用operator delete释放内存。它只用于删除由new创建的对象,若是malloc创建的对象,用delete删除,动作是未定义的,因为大多数默认的new和delete实现机制都是用malloc和free.所以会存在没有调用析构函数就释放了内存。

(1)在使用delete的时候我们看到删除对象的指针为0时为安全的,所以在删除指针指指向的对象时候务必要将指针的值置为0.防止野指针的情况出现。

(2)我们要避免删除类型为void 的指针,比如:

#include <iostream>
using namespace std;

class Object
{
void *data;
const int size;
const char id;
public:
Object(int sz,char c):size(sz),id(c)
{
//id=c;
data=new char[size];
cout<<"Constructing object"<<id<<"  ,size= "<<size<<endl;
}
~Object()
{
cout<<"Destructing object "<<id<<endl;
delete []data;//必须包括[],否则只会删除第一个,其它的依旧存在。因为不知道内存的大小。
//那么[]告诉编译器这只是一个数组的起始地址,让其产生将数组创建时存放在某处的对象数量取回,
//那么是否我们可以自己定义比如delete[100]data,事实是可以的,但是很可能会存在程序员将对象的数量弄错的情况。
//并将对应的内存释放的代码,若是对象数组,则会为数组中每个元素调用析构函数。
}
};

int BadPointerDeletionTest()
{
Object *a=new Object(40,'a');
delete a;//这里会删除a对应的对象,以及data对应的内存。
void *b=new Object(40,'b');
delete b;//但是这里因为是使用void类型进行接收,那么其实因为没有类型信息,使得编译器不知道调用哪个析构函数
//于是析构函数不能调用,只能释放object指向的内存,在析构函数中delete data的代码就不能得以执行。
return 0;
}

小结:

1、注意void的类型,编译器不能识别,是不能够调用析构函数的,

2、在内存消耗之后会调用new-hander函数,
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: