啃书《C++ Primer Plus》 动态内存管理(上) new和delete的使用

这一节来梳理有关动态内存管理的内容。
首先是C++风格的动态内存的创建与释放,指得是newdelete关键字的使用,相较于C语言的mallocfree他们的功能更加丰富,使用也更加方便。

啃书《C++ Primer Plus》 面向对象部分 动态内存管理(中) 动态对象的创建 重载new和delete

《动态内存管理》内容思维导图如下:
在这里插入图片描述


动态内存的分配与释放

new和delete关键字的使用

new和delete

newdelete关键字是C++动态内存的基础,用于分配和释放动态内存。

相较于C语言的mallocnew在申请内存时可以不必考虑具体内存的大小而是专注于根据怎样的类型来申请内存,它会自动计算所给类型的大小并为之申请相应大小的空间。

它的用法如下:

new 类型名称;
new 类型名称(初始化数据);

对于前一种,编译器会给予变量默认的赋值。对于基本类型,这个值是0,对于对象则会调用其无参构造函数。

语句的执行结果将是该类型的指针,指向刚刚分配的内存。

将上面的用法补写完整就是:

类型名称 * 指针名称 = new 类型名称;
类型名称 * 指针名称 = new 类型名称(初始化数值);

这块分配出来的内存与常规定义变量分配的内存块不同,这些内存是分配在堆区(自由存储区)而非栈区。因此即使指向其指针的作用域失效,该内存也不会像栈区变量那样被释放,而是会继续保留。如果没有及时的清理或是寻找另一个继续指向他的指针。该快内存就会变得不能访问,却依然占据着空间。

因此,申请了动态内存,还应当在使用完后将其“归还”给计算机。(好借好还,再借不难嘛)
将内存归还也就是释放,需要使用到delete关键字。

用法如下:

delete 指针;

需要注意的是:

  • newdelete关键字必须搭配使用,即申请的动态内存必须进行释放,否则将会造成内存泄漏。
  • 应当避免对同一动态内存的多次释放,会造成未知的问题。
  • delete对于空指针是安全的
  • 不要使用delete释放非new关键字分配的内存

看下面一个例子

int main()
{
	int* pI = new int;
	double* pD = new double(3.14);
	std::cout << *pI << std::endl;
	std::cout << *pD << std::endl;
	delete pI;
	delete pD;
}

执行结果:
在这里插入图片描述

new[]和delete[]

使用new[]可以分配一整块连续的内存用来并排存放多个同类型的对象,即创建动态数组。

它的通用格式如下:

类型名称 * 指针名称 = new 类型名称[数组规模];

动态分配数组中的元素,将全部被初始化为默认值。使用这个数组,只需要考虑到数组名称就是指向第一个成员的同类型的指针。因此,上面的声明方式使得指针名称就是该数组名称。因此,对数组的处理方式仍然有效:

另外,对于初始化,给一般数组进行初始化的语法在这里依然适用。

using namespace std;
int main()
{
	int * a = new int[3];//默认赋值为0
	*(a + 1) = 1;//通过指针访问
	a[2] = 3;//通过方括号访问
	cout << a[0] << a[1] << a[2] << endl;
	int * b = new int[3]{2,4,6};//使用大括号进行初始化
	cout << b[0] << b[1] << b[2] << endl;
}

执行结果:
在这里插入图片描述
当然,刚刚提到了,只分配不释放都是耍流氓。因此刚刚的程序就成功的耍了一回流氓。
那些动态分配出的数组也需要进行释放的,而释放数组使用的关键字是delete[]

用法如下:

delete[] 动态数组指针;

根据这个用法,我们来将上面的流氓改的文明些。

using namespace std;
int main()
{
	int * a = new int[3];
	*(a + 1) = 1;
	a[2] = 3;
	cout << a[0] << a[1] << a[2] << endl;
	delete[] a;
}

需要注意的是,除了上面列举的几个注意事项以外。newdeletenew[]delete[]的使用必须对应。

  • 使用new分配的内存必须使用delete关键字进行释放,使用new[]分配的内存必须使用delete[]进行释放。

定位new关键字

new运算符存在一种变体:定位new运算符
需要说明的是,如要使用定位new运算符,需要包含头文件new,它提供了定位new的原型。
这个运算符可以向特定位置创建对象。
该运算符需要提供一个地址用于创建对象,其余用法同new关键字相同。

形式如下:

类型名称 指针名称 = new(地址) 类型名称;

但是需要注意的是,使用定位new关键字放置的内存不能使用delete进行释放。换句话说,这块内存并不是由定位new分配的,因此这块内存应该交由开辟它的new进行释放。我们来看下面的例子:

#include<iostream>
#include<new>//这玩意千万不要丢了
using namespace std;
int main()
{
	int a[20];//在栈区申请一个长度为20的int类型数组
	int * p1 = new(a) int(2);//使用定位new指定a[0]为一个可操作的位置,此时并没有分配新的内存
	
	int * b = new int[20];//使用new[]在堆区分配一个长度为20的int类型的数组
	int * p2 = new(b + 1) int(3);//使用定位new指定b[1]为一个可操作的位置,此时并没有分配新的内存
	cout << *p1 << endl << *p2 << endl;
	delete[] b;//使用delete[]释放分配的b数组,好借好还
}

运行结果:
在这里插入图片描述
在上面的程序中,两个定位new并没有分配新的内存,而是在相应区域指定了一个可操作的位置,释放他们的工作交给了提供内存的变量本身。

定位new也可以指定成块的区域,如数组,
我们再来一个程序演示一下:

#include<iostream>
#include<new>//这玩意千万不要丢了
int main()
{
	int a[20];
	int * p1 = new(a) int[2]{1,2};
    cout << a[0] << a[1] << endl;
}

结果:
在这里插入图片描述


啃书系列往期博客

语言基础部分:

面向对象部分:

猜你喜欢

转载自blog.csdn.net/wayne_lee_lwc/article/details/105784064