内存可以整体分为三个部分:栈区、堆区、静态常量区。栈区主要存储的是局部变量和函数参数;C语言当中malloc、calloc、realloc、C++当中的new申请的空间基本是在堆区,堆区也被称为自由存储区;静态常量区有两个主要的标记:static定义的变量和字符串常量。静态常量区细分可以是数据段和代码段,全局变量和static变量存放在数据段,字符串常量这种只读常量放在代码段。
- 栈又叫堆栈,存储非静态局部变量、函数参数、返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
- 堆用于程序运行时动态内存分配,堆是可以向上增长的。
- 数据段存储全局数据和静态数据。
- 代码段存储可执行的代码和只读常量。
void main()
{
int *p1 = (int*)malloc(sizeof(int) * 10);
if (p1 == NULL)
{
cout << "out of memory.\n"<<endl;
return;
}
free(p1);
int *p2 = new int[10];
delete []p2;
}
如果用new申请的是数组空间,那么delete的时候要加上[],表示释放的是数组空间,[]内不需要写任何数字。
用new申请空间的时候,不需要判断是否申请成功,同时,相对与C语言申请空间的那一套办法,可以进行更加丰富的初始化。C语言只有calloc函数可以将空间初始化为0,我们来看看用new申请空间可以达到什么样的效果:
void main()
{
int *p1 = new int(1);
int *p2 = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };
delete p1;
delete[]p2;
}
new可以初始化单个变量,也可以初始化整个数组。
class Test
{
public:
Test() :m_data(0)
{
cout << "Test::Test()" << endl;
}
~Test()
{
cout << "Test::~Test()" << endl;
}
private:
int m_data;
};
void main()
{
Test *p = (Test*)malloc(sizeof(Test));
assert(p != NULL);
free(p);
}
对于自定义类型,我们用C语言的malloc函数去申请空间的时候,仅仅是申请了一份空间,申请的空间大小满足自定义类型的大小,但是在申请空间的过程中,不会自动执行构造函数,也不会执行析构函数,自然也不会初始化,如果我们需要它初始化,需要执行其他操作,就需要再自己调用其他函数。
下面就是用C语言给自定义类型进行一整套流程的代码:
#include<iostream>
#include<assert.h>
using namespace std;
class Test
{
public:
Test() :m_data(0)
{
cout << "Test::Test()" << endl;
}
~Test()
{
cout << "Test::~Test()" << endl;
}
void InitTest()
{
m_data = 0;
ptr = (int*)malloc(sizeof(int) * 10);
assert(ptr != NULL);
}
void DestroyTest()
{
free(ptr);
}
private:
int m_data;
int *ptr;
};
void main()
{
Test *p = (Test*)malloc(sizeof(Test));
//申请空间
assert(p != NULL);
p->InitTest();
//初始化对象
p->DestroyTest();
//摧毁对象
free(p);
//释放空间
}
#include<assert.h>
using namespace std;
class Test
{
public:
Test() :m_data(0)
{
cout << "Test::Test()" << endl;
ptr = new int[10];
}
~Test()
{
cout << "Test::~Test()" << endl;
delete[]ptr;
}
private:
int m_data;
int *ptr;
};
int main()
{
Test *pt = new Test;
//1、申请内存空间
//2、调用构造函数初始化对象
delete pt;
//1、调用析构函数摧毁对象
//2、释放对象空间
return 0;
}
在C++中,new不仅仅具有申请内存空间的作用,而且在开辟空间的过程中,会调用构造函数初始化对象。delete也不仅仅会释放内存空间,它会先调用析构函数摧毁对象,再释放对象空间。
int main()
{
Test *p1 = new Test[10];
Test *p2 = new Test(10);
return 0;
}
p1是申请数组空间,p2是申请单个元素空间。
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]
Test *p1 = new Test[10];
同时需要注意的是申请数组空间的时候,如果发生错误,要检查是否有默认构造函数支持。
在我们进行深赋值的时候,会调用赋值语句,同时,还需要注意的是深拷贝会申请空间,但是在申请空间的时候可能会发生异常,也就是说可能会申请空间不成功,如果此时我们再释放原有空间,就会导致程序崩溃。那这个问题要怎么解决呢?
第一种方法就是先用new分配新内容再用delete释放已有的内容:
类Srting:
#include<iostream>
#include<assert.h>
using namespace std;
class String
{
public:
String(const char *str = "")
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
String(const String &s)
{
m_data = new char[strlen(s.m_data) + 1];
strcpy(m_data, s.m_data);
}
~String()
{
delete[]m_data;
m_data = nullptr;
}
private:
char *m_data;
};
void main()
{
String s("abcxyz");
String s1 = s;
String s2("Hello");
s2 = s1;
}
拷贝构造函数如下:
String& operator=(const String &s)
{
if (this != &s)
{
char *new_data = new char[strlen(s.m_data) + 1];
delete []m_data;
m_data = new_data;
strcpy(m_data, s.m_data);
}
return *this;
}
还有一个更好的办法就是先创建一个临时实例,再交换临时实例和原来的实例。
String& operator=(const String &s)
{
if (this != &s)
{
String tmp(s);
char *ptmp = tmp.m_data;
tmp.m_data = m_data;
m_data = ptmp;
}
return *this;
}
在这个过程中,我们将创建一个临时对象tmp,在创建临时对象的时候,我们会调用它的构造函数里用new分配内存,如果内存不足抛出异常,此时我们还没有修改原来的实例的状态,因此,此时实例的状态还是有效的,这样子也就保证了异常安全性,tmp是一个局部对象,出了作用域就会调用析构函数进行释放,也不用担心内存溢出的问题。
也可以直接调用交换函数。
String& operator=(const String &s)
{
if (this != &s)
{
String tmp(s);
swap(m_data,tmp.m_data);
}
return *this;
}