C++中类的动态内存分配与释放

  一般来讲,编译器使用三块独立的内存:
  1.存储静态变量的内存(关键字static)
  2.存储自动变量的内存(关键字auto)
  3.动态内存(关键字new)
  计算机内存中又分为堆(heap)与栈(stack),前文已经讲过,栈是用于处理自动变量的。而堆就比较厉害了,堆负责动态内存分配。什么是动态内存呢?大部分的变量,在编译后就已经确定了自己在内存的位置;而有些变量则是在程序运行过程中确定自己的位置,也就是用你的时候再分配你,这样的变量叫动态分配变量。
  C++中使用new关键字来在堆中分配出一块内存块,使用指针指向该内存块,进而操作该内存块。需要注意的是,在一些不健壮的操作系统中,请求大型内存块可能会导致程序结束后不会被自动释放,造成内存泄漏。因此为了编程规范与程序安全,每一块由new申请的内存都应当手动使用delete关键字进行释放。
  OK现在我们知道了:动态内存由关键字new申请;动态内存靠指针操作;动态内存必须要由delete关键字释放。知道这三点,使用动态分配就比较简单了。但是在类对象的动态分配中,由于构造函数与析构函数的介入等,情况变得更加复杂。
  在工程的头文件中定义了如下一个类:

#include <iostream>
#include <string>
#ifndef NEWTEST_H_
#define NEWTEST_H_
using namespace std;
namespace NEWTEST
{
	class Newtest
	{
	private:
		string words;//字符串数据变量
		int number;//整形数据变量
	public:
		Newtest(const string &s,int n);//构造函数
		~Newtest();//析构函数
		void show() const;//显示函数
	};
}
#endif

  研究的对象是如何动态分配与释放类,因此该类的设计比较简单。类中仅有两个隐藏的数据变量,公共接口只有构造函数、析构函数和一个显示对象数据的函数。
  按照编程规范,在另一个cpp文件中对类的成员函数进行定义:

#include <iostream>
#include <string>
#include "newTest.h"

namespace NEWTEST
{
	Newtest::Newtest(const string &s,int n)
	{
		words=s;
		number=n;
		cout<<words<<"  object created"<<endl;
	}
	Newtest::~Newtest()
	{
		cout<<words<<" object destroyed"<<endl;
	}
	void Newtest::show() const
	{
		cout<<"words: "<<words;
		cout<<" number:"<<number<<endl;
	}
}

  在主程序之前,需要知道new和delete的一些用法。
  new有两种主要的用法:普通new运算符与定位new运算符。普通的运算符是较为常见的用法,即不关心变量到底被分配到了哪里,我只是单纯的想分配一块内存而已。如下所示:

int *p1;
p1=new int;//p1指向一块动态内存,用于存储一个整形变量

  定位new运算符,顾名思义,程序员需要指定该内存被分配的位置,格式如下:

char buffer[128];//定义了一块128字节的内存块,也称为内存池
int *p1;
p1=new (buffer) int;//p1指向一块动态内存,用于存储一个整形变量,该整形变量的地址就是内存池buffer的首地址

  比较常见的搭配是,先使用动态分配分配出一块内存池,再利用内存池的地址来为动态分配的变量指定位置。
  关键字delete用法比较简单,原则只有一个,就是格式必须与对应的new格式相同:

char *str1=new char[5];
delete [] str1;//创建用new [],释放用delete []
char *str2=new char;
delete str2;//创建用new,释放用delete

  此外,在类对象的处理中,使用普通new运算符分配动态内存给对象会自动调用构造函数,使用delete会自动调用析构函数。普通new运算符创建对象和释放内存的语句如下:(把主程序拆了拆)

p2=new Newtest("number one",20);//普通new创建对象,自动调用构造函数进行初始化
delete p2;//删除p2,自动调用析构函数

  麻烦的是定位new运算符。delete运算符可以和普通new运算符搭配,但是不能与定位new运算符搭配。原因是定位new运算符本质上没有开辟新内存,定位new运算符是基于开辟了一块内存池,才得以实现的。也就是说指向定位new运算符创建的对象的指针并没有收到new运算符返回的地址,因此使用delete操作会产生错误。
  该问题的解决方案是:针对定位new运算符创建的对象,显示调用析构函数,这也是整个C++中需要显示调用析构函数的罕见情况之一。
  定位new创建对象和释放内存的语句如下:

p1=new (buffer) Newtest("C++ is",10);//定位new创建对象,自动调用构造函数进行初始化
p1->~Newtest();//显式调用析构函数

  主程序如下:

#include <iostream>
#include <new>
#include <string>
#include "newTest.h"
using namespace NEWTEST;
const int BUFF=512;

int main()
{
	char *buffer=new char[BUFF];//分配一块内存区512字节
	Newtest *p1,*p2,*p3;
	p1=new (buffer) Newtest("C++ is",10);//定位new
	p2=new Newtest("number one",20);//普通new
	p3=new (buffer+sizeof(Newtest)) Newtest("without a doubt!",30);//定位new
	cout<<"address of buffer:"<<endl<<(void*)buffer<<endl;//打印内存区的首地址
	cout<<"memory contents:"<<endl;
	cout<<p1<<": ";//显示p1地址
	p1->show();//显示p1内容
	cout<<p2<<": ";//显示p2地址
	p2->show();
	cout<<p3<<": ";//显示p3地址
	p3->show();
	delete p2;//删除p2
	p3->~Newtest();//删除p3 
	p1->~Newtest();//删除p1 定位new删除顺序与创建顺序相反
	delete [] buffer;//删除内存区
	cout<<"program ends!"<<endl;
	cin.get();
	return 0;
}
 

  程序运行结果如下:
在这里插入图片描述
  主程序中p2指向一个普通new创建的内存,p1和p3指向两个定位new创建的内存,p3指向的内存相对p1偏移量为一个Newtest类的大小。
  需要注意一点,定位new创建对象的删除顺序应该与创建顺序相反,因为晚创建的对象有可能依赖于早创建的对象。此外,当所有对象的内存被释放后,不要忘记释放内存池所占的内存空间。

发布了54 篇原创文章 · 获赞 18 · 访问量 9575

猜你喜欢

转载自blog.csdn.net/m0_37872216/article/details/101122482