当前位置: 首页 > news >正文

c++内存管理:

目录

new和delete

使用方法:

注意事项:

new申请不需要检查返回值

operator new和operator delete函数的讲解


c语言申请内存有哪些方法:

答:malloc  calloc realloc三种

#include<stdlib.h>
void test()
{
	int*p1 = (int*)malloc(sizeof(int));
	free(p1);
	int*p2 = (int*)calloc(4, sizeof(int));
	int *p3 = (int*)realloc(p2, sizeof(int)* 10);
	free(p3);
}

其中,malloc就是普通的动态内存申请

calloc相当于malloc加上memset把申请的空间全部初始化为0

realloc相当于内存扩容,分为异地扩容和原地扩容,当扩容的次数少,空间小时,会执行原地扩容,当扩容的次数多,空间大时,会执行异地扩容。

原地扩容和异地扩容的区别?

答:如上图代码所示,p2和p3指针指向同一块空间,而异地扩容则不然,异地扩容会先找一块新的空间,然后把原空间的内容拷贝到新空间位置,然后释放掉原空间,所以返回的就是p3.

new和delete

使用方法:

c++是通过什么申请内存的呢?

答:c++是通过两个关键字(操作符)来申请和释放空间的。

new和delete

int main()
{
	int *p1 = new int;
	delete p1;
	return 0;
}

相当于这里申请一个字节的空间,返回指向该空间的指针p1,然后delete表示释放空间。

注意:这里申请空间并不会对空间上的内容完成初始化:

例如:

 我们申请的空间并没有进行初始化。

我们如何申请空间的同时并初始化呢?

答:我们可以这样操作

例如:

int main()
{
	int *p1 = new int(0);
	delete p1;
	return 0;
}

 我们在后面加上0表示申请空间并把空间初始化为0.

我们如何申请多个空间呢?

答:

int main()
{
	int *p1 = new int[10];
	delete[] p1;
	return 0;
}

这里表示我们要申请十个整型空间,注意:我们在delete释放时,要和我们申请的空间进行一一对应。

 假如我们要对申请的多个空间进行初始化呢?

答:我们可以这样写:

int main()
{
	int *p1 = new int[10]{1, 2, 3, 4};
	delete[] p1;
	return 0;
}

这里表示我们对申请的十个空间中的前4个进行初始化:

 我们发现,对于内置类型,这里的new和delete和c语言的动态内存申请函数本质上没什么区别,那为什么++要定义这两个关键字呢?

答:对于内置类型,c语言和c++的动态内存申请本质是一样的,但是对于自定义类型,结果就不同了

例如:

我们写一个简单的类:

class A
{
	A(int a = 0)
	:_a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

这个类中有两个成员函数,分别是构造函数和析构函数,函数的目的是当调用构造和析构函数,打印对应的提示并打印出this指针。

我们进行实验:

int main()
{
	/*int *p1 = new int[10]{1, 2, 3, 4};
	delete[] p1;
	return 0;*/
	A* p1 = new A;
	
}

我们动态内存申请一个类的空间,返回指向该空间的指针p1

我们进行编译:

 这里表示我们的动态内存申请new调用了构造函数。

但是我们的malloc是不会调用构造函数的:

int main()
{
	/*int *p1 = new int[10]{1, 2, 3, 4};
	delete[] p1;
	return 0;*/
	/*A* p1 = new A;*/
	A*p1 = (A*)malloc(sizeof(A));
}

我们进行编译:

 所以new相较于malloc对于自定义类型来说,new会调用自定义类型的构造函数,而malloc不会。

new会调用自定义类型的构造函数,那么delete是不是也会调用析构函数?

答:会:

int main()
{
	/*int *p1 = new int[10]{1, 2, 3, 4};
	delete[] p1;
	return 0;*/
	A* p1 = new A;
	/*A*p1 = (A*)malloc(sizeof(A));*/
	delete p1;
}

我们进行调用:

 所以delete也会调用自定义类型的析构函数。

我们举一个之前写的链表的例子:

struct ListNode
{
	ListNode(int val = 0)
	:_next(nullptr)
	, _val(val)
	{}
	ListNode* _next;
	int _val;
};

我们现在可以这样写链表:

struct和class都是类关键字,struct当我们不处理时,类中的成员的默认用public修饰。

我们可以在类中写构造函数,相当于我们之前的创建新节点

int main()
{
	ListNode*n1 = new ListNode(1);
	ListNode*n2 = new ListNode(2);
	ListNode*n3 = new ListNode(3);
	ListNode*n4 = new ListNode(4);
	n1->_next = n2;
}

这样写链表就会方便很多。

注意事项:

注意:

new和delete一定要匹配,否则会产生意想不到的问题,我们就只举一个例子:

例如:

nt main()
{
	A*p1 = new A[10];
	delete p1;
}

我们写出这样的代码进行运行就会报错:

为什么会这样呢?

我们先写一个正常的进行分析:

 

int main()
{
	A*p1 = new A[10];
	delete[] p1;
}

我们的A的成员只有一个整型,所以A占四个字节的空间,十个A就占40个字节的空间。

 我们先进行编译:

 我们进行申请时或进行释放时,都会调用多次调用构造函数或析构函数。

我们在创建时,知道我们需要创建十个A类所占的空间

但是我们在析构的时候,并不清楚我们需要析构多少次,这时候,我们需要额外申请一个整型的空间:

 接下来,我们把p1往前置:

 这时候,我们就知道我们需要析构多少次,并且从这里可以把我们申请的空间全部释放。

我们返回来看之前报错的情况:

 

int main()
{
	A*p1 = new A[10];
	delete p1;
}

为什么会报错呢?

答: 因为我们释放没有写[],所以我们构造了十次,但是我们不清楚析构了多少次,所以就会报错。

上面的这些都是关于编译器vs2013的一些情况,举这些例子只是为了说明一定要把申请的空间和delete释放的空间进行对应

new申请不需要检查返回值

我们知道malloc申请大的空间或者连续申请小的空间就会报错:

int main()
{
	while (1)
	{
		int *p1 = (int*)malloc(1024 * 1024);
		if (p1)
		{
			cout << p1 << endl;
		}
		else
		{
			cout << "申请失败" << endl;
			break;
		}
	}
}

并且我们知道,当malloc申请失败的时候,会返回空指针,所以我们可以写出以上代码来进行实验:

 

 申请多次的时候,报错

接下来,我们对new进行实验:

int main()
{
	while (1)
	{
		int *p1 = new int[1024 * 1024];
		if (p1)
		{
			cout << p1 << endl;
		}
		else
		{
			cout << "申请失败" << endl;
			break;
		}
	}
}

 运行很快就停止了,但是并没有打印出申请失败,说明我们new失败的返回值并不是0,或者说,new失败没有返回值。

new失败的话,就会抛异常,所以我们不需要对返回值进行检查,对于抛异常的问题,我们之后再进行详解。

operator new和operator delete函数的讲解

上述的两个函数是new和delete的底层实现:

 new的底层实现就是通过new调用operator new函数,operator函数中有malloc,调用完毕之后调用构造函数。

注意:operator new函数并不是new的重载。

operator new相当于是一个新的全局函数。

我们看一下operator函数的定义:

 我们可以发现,operator new函数就是malloc函数的封装,无非就是加上了当申请失败时,不反回,而是抛异常。

我们观察一下operator delete函数。

 相当于我们operator的主体部分也是调用了free函数。

我们是否可以使用operator new来申请空间呢?

答:可以:

例如:

int main()
{
	while (1)
	{
		char*p1 = (char*)operator new(1024 * 1024 * 1024);
		cout << (void*)p1 << endl;
	}
}

operator new的使用方法和malloc相似。

不同点在于:operator new申请失败的话不需要报错,因为会抛异常。

相关文章:

  • Hbase性能调优(一)
  • STM32CUBEMX(10)--内部Flash读写
  • 基于 Maven 的 MyBatis 逆向工程
  • 在阿里干了6年自动化测试,30岁即将退休的我,告诉你自动化测试工程师有多吃香...
  • 3.接口
  • LabVIEW-数据采集
  • Django全家桶
  • 内网渗透 Metasploit(MSF)基础使用
  • 机器学习笔记 - 使用TensorFlow Lite从头创建模型
  • 直流信号隔离采样
  • 锐捷——RIP基础配置
  • 一文搞懂【知识蒸馏】【Knowledge Distillation】算法原理
  • 啃完这些 Spring 知识点,我竟吊打了阿里面试官(附面经 + 笔记)
  • Java集合List接口详解——含源码分析
  • 自动化测试怎么做?python自动化测试断言详细实战代码(看这一篇就够了)
  • #Java异常处理
  • 4. 路由到控制器 - Laravel从零开始教程
  • ES6语法详解(一)
  • idea + plantuml 画流程图
  • Java基本数据类型之Number
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • leetcode46 Permutation 排列组合
  • Protobuf3语言指南
  • React Transition Group -- Transition 组件
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 爬虫模拟登陆 SegmentFault
  • 扑朔迷离的属性和特性【彻底弄清】
  • 前嗅ForeSpider采集配置界面介绍
  • 使用 @font-face
  • 小程序 setData 学问多
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​​​​​​​​​​​​​​Γ函数
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • #pragma once
  • #控制台大学课堂点名问题_课堂随机点名
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (2)STM32单片机上位机
  • (BFS)hdoj2377-Bus Pass
  • (LeetCode 49)Anagrams
  • (MATLAB)第五章-矩阵运算
  • (二)hibernate配置管理
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)AS3正则:元子符,元序列,标志,数量表达符
  • (转载)OpenStack Hacker养成指南
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .net/c# memcached 获取所有缓存键(keys)
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)