1.C中的动态内存分配
在程序运行的过程中,我们需要能够自由地创建和销毁对象。
在C中,提供了动态内存分配(dynemic memory alloction)函数malloc()和free(),这些函数可以在运行时从堆中分配单元。
然而,在C++中这些函数将无法按照预期的情况去执行。因为构造函数不允许我们向它传递内存地址来进行初始化。而将对象的定义和初始化分离是一件十分危险的事情,可能会出现以下情况 :
①分配内存之后忘记初始化;
②在对象的初始化完成之前就对它进行操作;
③把错误规模的对象传递给它。
不正确的初始化要对大部分编程问题承担责任,因为在堆上创建对象时,确保构造函数的调用是非常重要的。
C++使动态语言分配成为核心,malloc()和free()是库函数,因此不在编译器的控制范围内。我们需要一个完成动态内存分配及初始化组合动作的运算符和一个完成清理及释放组合动作的运算符,编译器仍可以保证所有的构造函数和析构函数都会被使用。
对象的创建与释放
当创建一个对象时,会发生两件事:
①为对象分配内存;
②调用对象的构造函数来初始化对象;
C++强调对象在哪里和如何被调用都无关紧要,但构造函数必须要被调用。
在分配内存时,我们有很多方式:
①在静态存储区域,存储空间在程序开始之前就可以分配,这个存储空间在程序的整个运行期间都存在。(全局变量&static)
②无论何时到达一个特殊的执行点“{”时,存储单元都可以在栈区被创建,离开执行点“}”时,堆栈指针上移,这个内存单元出栈并被释放,但在执行前,必须明确地知道需要多少个存储单元。(局部变量)
③存储单元也可以从一块被称为堆的空间中分配。这被称为动态内存分配。在运行时调用程序分配这些内存。这意味着可以在任何时候确定分配内存的大小。当然也需要负责决定何时释放内存,这块内存的生存期由我们决定,而不受程序的范围决定。
1. C从堆中获取存储单元的方法
C在它的标准库函数中提供了
从堆中申请内存的malloc()、calloc()、realloc()
释放内存返回给堆的函数free()
class Obj {
int x_ ;
int y_ ;
public :
void Initialization() {
cout << "Initialization obj" << endl ;
x_ = 0 ;
y_ = 0 ;
}
void Destory() {
cout << "Destroy obj" << endl ;
}
};
int main(void) {
Obj * ptrObj = (Obj *)malloc(sizeof(Obj)) ;
requires(ptrObj != 0) ;
ptrObj ->Initialization() ;
ptrObj ->Destory() ;
free(Obj) ;
return 0 ;
}
使用malloc()时,用户必须指明对象的长度。由于malloc()只分配一块内存而不是生成一个对象,因此返回一个(void *)类型指针,C++不允许将一个void *类型的指针赋给其它类型的指针,所以必须做类型的转换。
malloc()可能找不到可分配的内存,所以必须检查返回的指针。
对象在使用之前必须初始化,没有使用构造函数的士隐构造函数不能被显式地调用——它是在对象被创建时由编译器调用。使用malloc()时会将定义和初始化分开,会产生危险的后果。
2.C++的改进方案
C++中的解决方案是把创建对象所需的所有动作都封装在一个称为new的运算符
把释放对象所需的所有动作都封装在一个称为delete的运算符
简单来说: new = malloc() + constructor() ;
delete = distructor() + free() ;
如果写出下面的语句
obj * ptrobj = new obj(1 , 2) ;
运行时等价于调用malloc(sizeof(obj)),并使(1 , 2)作为参数来调用构造函数,this指针指向返回值的地址。并且new还会自动进行检查内存是否分配成功。
new使得在堆中动态分配分配创建对象变得简单。
delete表达式首先调用析构函数,然后释放内存,参数是一个对象的地址。用法如下:
delete ptrobj ;
如果想对一个void *类型的指针进行delete操作,这可能成为程序的错误,除非这个void *指针指向简单的内容,否则将不会执行析构函数。如果我们在一段看起来正常的程序中发现了内存丢失的情况,那么就搜索所有的语句
1.new的对象是否都被delete释放
2.delete释放的指针是否是void * 类型
3.数组的new和delete
创建一个对象数组同样简单,应为数组中的每一个对象调用构造函数。
在对象数组中,每个对象会自动调用默认构造函数。
obj * ptrobj = new obj[100];
表示在堆上分配100个ob对象,并为每个对象都调用构造函数。
但我们得到的ptrobj是指向这个对象数组的起始地址,可以用ptrobj[3]的形式来选择元素,但如果我们使用
delete ptrobj ;
表示给地址指向的obj对象调用析构函数,然后释放内存。对于ptrobj,后面的99个对象都没有调用析构。
解决方法是给编译器一个提示,告诉它delete释放的是一个起始地址
delete ptrobj[] ;
[ ]是一个标识,不需要填具体数字,因为我们需要释放的是整个数组,需要填数字的话反而会引起麻烦。
4.重载new和delete
当系统提供的默认new和delete不适合解决实际遇到的问题时,我们可以选择重载new和delete运算符。
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std ;
class Complex {
int rear_ ;
int imag_ ;
public :
Complex() {
puts("Complex::Complex()") ;
}
~Complex() {
puts("Complex::~Complex()") ;
}
};
void * friend operator new(size_t sz){
void * ptr = malloc(sz) ;
return ptr ;
}
void * friend operator delete(size_t sz) {
free(ptr) ;
}
这就是重载new和delete的通常形式。
有一点尤为重要,重载运算符new和delete时,只需要实现分配内存的部分即可,其他功能由编译器负责调用,不需要我们去控制。