C++内存管理(动态内存开辟)

news2024/10/6 8:36:41

  我们在C语言当中想要使用堆区的空间的时候就需要使用malloc函数进行手动的申请,但是我们在申请的时候需要手动进行计算,经过计算之后还需要进行判空操作,并且还不能进行任意值的初始化。这一切看起来在学习完C++当中的动态开辟之前显得很正常,但是在学习完C++当中的动态内存开辟之后我们就会感绝倒C语言的麻烦之处,接下来我们就来认识一下C++当中的动态内存开辟操作符,并将它和C语言当中的malloc函数进行对比吧。

  一:认识new和delete操作符

  在C++当中想要动态内存开辟的时候我们使用的是new和delete操作符。我们通过一段代码来学习他的使用方法:

#include<iostream>
int main()
{
	//使用new操作符进行动态内存开辟,使用delete进行内存空间的释放
	//开辟一个字符的空间并使用
	char* pyes = new char;
	*pyes = 'y';
	std::cout << *pyes << std::endl;
	//开辟一个连续的空间
	int* pret = new int[3];
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		pret[i] = i + 1;
		std::cout << pret[i] <<" ";
	}
	//使用delete释放单个元素开辟的空间
	delete pyes;
	//使用delete释放动态开辟数组的空间的时候需要使用以下格式进行空间的释放
	delete[] pret;
	return 0;
}

  通过上面的代码我们肯定可以发现C++当中很多不同的用法。想要动态内存开辟就需要使用new操作符,new操作符后面跟的是我们想要开辟的空间的类型,不像C语言当中的malloc函数返回值是void所以在使用的时候还需要对目标的空间进行强转,new开辟出来的空间直接使用即可。

  如果想要开辟一串连续的空间的话,我们只需要在new的类型的后面加上一个方括号 [ ] 里面填入我们想要的元素的个数即可。不再需要我们自己亲手计算。在申请完空间之后我们不再需要对其进行判空操作,直接使用即可。因为我们的new操作符在申请失败的时候会抛出一个异常我们以后可以通过捕获进行获得异常的报错。(捕获等后面我们再详细讲解)这也就避免了我们重复判断的麻烦。使用的方式和正常的相同。

  在使用完毕的时候我们使用delete将我们开辟的空间释放即可。对于单个空间我们只需要使用delete加上指针即可,对于连续的空间我们使用delete [ ] 指针进行释放。如上程序运行的结果如下:

   但是我们在上面说到过new和malloc的区别之一就是可以对申请的空间进行任意的赋值,这和我们的malloc甚至是calloc函数都有很大的区别。同样的,我们通过一段代码进行详细的了解:

#include<iostream>
int main()
{
	//尝试使用new开辟空间并初始化
	//开辟单个字符的空间并初始化
	int* pret = new int(3);
	std::cout << *pret << std::endl;
	delete pret;
	//开辟一个数组的空间并对其进行初始化
	int* ptmp = new int[2]{3,4};
	std::cout << ptmp[0] << " " << ptmp[1] << std::endl;
	delete[] ptmp;
	//开辟一个数组的空间,使用默认的初始化方式进行试验
	int* pres = new int[3]();
	std::cout << pres[0] << " " << pres[1] << " " << pres[2] << std::endl;
	delete[] pres;
	return 0;
}

  当我们想要对单个字符进行初始化的时候我们只需要在类型的名称后面加上一个小括号,里面输入我们想要初始化的值即可。但是对于一个数组来说我们所需要进行的操作就发生了些许的改变:我们需要分为很多种情况:1.具有构造函数的情况 2.没有构造函数的情况 我们来一一对其进行解释。

  1.如果没有构造函数就会调用系统的默认初始化的方式,后面可以加上括号但是什么值都不能有,系统会将数组的值默认初始化为0。如果想要对没有构造函数的数组进行初始化我们还可以通过大括号 { } 的当时一一对数组的元素进行赋值,但是并不能统一进行赋值。如上代码运行的结果:

  2.对具有默认构造的对象进行初始化,我们对于具有默认构造的函数进行初始化的情况,大多数都是针对于类来说的,我们通常会在类当中写一个构造函数,之后我们在使用new操作符开辟空间的时候就会自动调用类的构造函数进行对象的初始化。同样的,我们通过一段代码来学习具有构造函数的对象如何进行初始化:

#include<iostream>

//写一个类,在使用new开辟空间的时候验证默认会自动调用默认初始化
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	//写一个构造函数,用于验证初始化
	Date(int year = 2022, int month = 12, int day = 12)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		std::cout << _year << "年" << _month << "月" << _day <<"日"<< std::endl;
	}
};
int main()
{
	//使用new开辟一个类大小的空间,并验证其初始化的内容
	Date* pret = new Date;
	(*pret).print();
	//开辟多个类的空间,并验证其初始化的内容
	Date* ptmp = new Date[3];
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		ptmp[i].print();
	}
    delete pret;
    delete[] ptmp;
	return 0;
}

  经过上面的代码我们可以看到,在使用new进行默认初始化的时候,当存在默认构造函数的时候就会自动调用默认构造函数进行初始化,如果像针对某个对象进行特定初始化的时候我们同样可以使用 { } 进行指定的初始化。 程序运行的结果如下:

  二:new和delete操作符的原理 

  在认识了new和delete函数的使用之后相信大家对于这两个操作符可以实现该功能的原因,所以我们就在此进行进一步的原理讲解。

  就像我们上面所看到的现象所展示的那样,假如我们在申请堆区的指定的空间的时候,对于有默认构造的对象会调用指定的默认构造,对于没有默认构造的对象想要初始化要么将其初始化为0,要么就需要对其进行指定的构造。所以我们也可以联想到我们的new操作符是不是调用了我们类当中的构造函数?我们对其进行验证:

  我们可以通过上面的运行结果得到答案,可以看出我们的程序是一定调用了构造函数了的。

  与之相对应的我们的delete操作符在发挥作用的时候也可以验证一下是否调用了析构函数:

   同样我们可以知道的是,在使用delete操作符的时候调用了我们类当中所定义的析构函数。

  实质上C++当中的操作符在定义的时候其实没有那么高级,也并不是说有了new就可以完全舍弃malloc函数。在底层的封装之上其实new操作符就是使用我们的malloc函数再加上我们的构造函数所得到的。本质上我们的new操作符在发挥作用的时候会先调用malloc函数进行空间的申请,假如申请成功之后再调用我们类的构造函数,这样就形成的new操作符。delete操作符的原理其实是一样的。我们在空间释放的时候会先调用析构函数,在将我们的数据清理了之后再调用free函数将我们申请的空间释放掉。

三:定向空间申请

  这其实并不是new操作符的使用方法当中最巧妙的一个,最巧妙的是:当我们需要多次向堆区申请空间的时候,每一次的申请都会造成一定程度上的浪费。所以我们可以现申请好一整块的空间,之后使用我们申请好的空间即可。这样就可以免去我们连续调用malloc函数所带来的缺陷。我们同样通过一段代码进行学习这种使用的方法:

#include<iostream>

//写一个类,在使用new开辟空间的时候验证默认会自动调用默认初始化
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	//写一个构造函数,用于验证初始化
	Date(int year = 2022, int month = 12, int day = 12)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}
};
int main()
{
	//使用new开辟一个类大小的空间,并验证其初始化的内容
	Date* pret = new Date[3];
	pret[0] = {2023,1,1};
	pret[0].print();
	return 0;
}

  其使用的方法为:new (place_address) type或者new (place_address) type(initializer-list)

  其中place_address必须是一个指针,initializer-list是类型的初始化列表。也就是说我们new后面括号里的是我们提前申请好的地址的指针,括号后面的 type 表示我们使用的地址的类型。我们还可以在后面的 { } 里面进行初始化,但是我们需要注意的是:我们进行的初始化只能是单个对象的初始化,否则就有可能产生特殊意想不到的结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/827345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新版本mac版Idea 激活Jerbel实现热部署

1.环境准备 1.安装docker desktop 客户端创建本地服务 2.创建guid 3.随便准备一个正确格式的邮箱 2.具体操作 1.通过提供的镜像直接搭建本地服务 docker pull qierkang/golang-reverseproxy docker run -d -p 8888:8888 qierkang/golang-reverseproxy2.guid 通过如下网址直…

小C说历史(人物介绍第一篇):传奇人物Linus Torvalds 缔造Linux和Git

传奇人物Linus Torvalds 缔造Linux和Git Linus Torvalds&#xff0c;1969年12月28日出生于芬兰的赫尔辛基&#xff0c;Linux核心的创作者。当Linus十岁时&#xff0c;他的祖父&#xff0c;赫尔辛基大学的一位统计教授&#xff0c;购买了一台Commodore VIC-20计算机。Linus帮助他…

Mybatis-Plus面向实用知识点——结合SpringBoot

目录 环境配置基本流程各类中的方法BaseMapperIServiceCOUNTGETQueryListPageRemoveSaveUpdate 环境配置 参考java项目各框架环境配置 基本流程 创建Mapper Mapper public interface MyMapper extends BaseMapper<Entity>{}创建Service public interface MyService …

【娱乐圈明星知识图谱2】信息抽取

目录 1. 项目介绍 2. 信息抽取介绍 3. ChatGPT 信息抽取代码实战 4. 信息抽取主逻辑 5. 项目源码 1. 项目介绍 利用爬虫项目中爬取的大量信息 【娱乐圈明星知识图谱1】百科爬虫_Encarta1993的博客-CSDN博客娱乐圈明星知识图谱百度百科爬虫百度百科爬虫百度百科爬虫百度百…

【vue】 vue2 监听滚动条滚动事件

代码 直接上代码&#xff0c;vue单文件 index.vue <template><div class"content" scroll"onScroll"><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容…

java 定时任务不按照规定时间执行

这里写目录标题 使用异步启动可能出现的问题排查代码中添加的定时任务步骤是否正确排查是否任务阻塞&#xff0c;如果定时任务出现异常阻塞后&#xff0c;将不会在次执行java中多个Scheduled定时器不执行为了让Scheduled效率更高&#xff0c;我们可以通过两种方法将定时任务变成…

springboot 整合代码块实现访问

一 springboot整个代码块实 2.访问

补充JDK源码-IDEA集成工具

在阅读JDK8源码的时候发现&#xff0c;只有一小部分常用包是存在源码及其注释的&#xff0c;而很多内部包是没有源码&#xff0c;class文件在阅读的时候对阅读者十分不友好。在网上搜集了很多资料都没有解决问题。 解决问题办法&#xff1a;参考文档。本文主要是根据这篇文章记…

语义检索系统【一】:基于无监督预训练语义索引召回:SimCSE、Diffcse

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

VB6: 安装界面中文乱码,打开项目中文乱码

Win11环境下的VB6开发&#xff0c;遇到中文乱码问题。 1.在安装VB6开发环境的时候&#xff0c;对话框各种乱码 2.安装完&#xff0c;或者用绿色版VB6&#xff0c;打开现有项目的时候&#xff0c;中文内容出现乱码 解决方法&#xff1a; 参考这篇文章&#xff1a;Windows: 文…

cc2652主协处理器分时控制同一个外设的问题

问题已提交TI论坛&#xff0c;我是提交到的中文论坛&#xff0c;然后fae给转到英文论坛了。 简单描述就是&#xff0c;怎么让这个单片机一会用主处理器控制SPI设备&#xff0c;一会再用协处理器控制同一个设备。 主处理器的spi配置使用 CCS studio配置的 协处理器使用Sensor Co…

监控对象都有哪些分类

1、业务监控 这类指标是管理层非常关注的&#xff0c;代表企业营收&#xff0c;或者跟客户主流程相关&#xff0c;类似 BI 数据。不过相比 BI 数据&#xff0c;业务监控指标有两点不同。 对精确度要求没有那么高&#xff1a;因为监控只要发现趋势异常就可以&#xff0c;至于是…

Spring Boot的自动配置原理

一.原理解释 Spring Boot的自动配置是Spring框架的一个重要特性&#xff0c;它旨在简化应用程序的开发和部署过程。自动配置通过基于类路径中的依赖关系和配置文件内容来预先配置Spring应用程序的各种组件和功能。这样&#xff0c;我们可以在无需显式配置大量参数的情况下&…

springboot中配置bpmnjs插件-activiti7流程图绘制插件/IDEA中运行bpmnjs

BPMNJS的安装和使用需要依赖nodejs插件,需要先安装NODEJS,因为bpmnjs插件的运行需要使用到NODEJS中的npm命令。 安装nodejs 安装和使用bpmnjs插件,绘制activiti工作流需要的流程图。 1、安装和配置nodejs 2.1、下载nodejs https://nodejs.org/en 1.2、安装nodejs,默认安…

通用指令(汇编)

一、数据处理指令1&#xff09;数学运算数据运算指令的格式数据搬移指令立即数伪指令加法指令带进位的加法指令减法指令带借位的减法指令逆向加法指令乘法指令数据运算指令的扩展 2&#xff09;逻辑运算按位与指令按位或指令按位异或指令左移指令右移指令位清零指令 3&#xff…

RabbitMQ-API

这里写目录标题 Hello word 模式添加依赖生产者消费者获取信道工具类 Work Queues模式消费者代码 C1开启多线程运行启动 消费者代码 C2生产者代码 消息应答自动应答消息应答的方法Multiple 的解释消息自动重新入队消息手动应答代码消费者API 队列持久化消息持久化不公平分发消息…

思科模拟器配置静态路由(下一跳使用IP)

Router0配置代码&#xff1a;##端口配置 Router(config)#int fastEthernet 0/0 Router(config-if)#ip address 192.168.10.254 255.255.255.0 Router(config-if)#no shutdown Router(config-if)#int fastEthernet 0/1 Router(config-if)#ip address 192.168.20.1 255.255.255.2…

拼多多海外版Temu商业模式分析

拼多多于2022 年 9 月在美国上线跨境平台 Temu&#xff0c;发布仅2个月就成为北美下载量最高的应用程序&#xff0c;持续霸榜。 这篇文章帮你快速了解下Temu&#xff1a; 商业模式如何竞争情况如何有哪些优势和挑战后期业务如何发展 一、Temu商业模式 Temu平台拥有对商品的最…

如何一目了然地监控远程 Linux 系统

动动发财的小手&#xff0c;点个赞吧&#xff01; Glances 是一款免费的开源、现代、跨平台、实时 top 和类似 htop 的系统监控工具&#xff0c;与同类工具相比&#xff0c;它提供了先进的功能&#xff0c;并且可以在不同的模式下运行&#xff1a;作为独立模式、客户端/服务器模…

32.利用fmincon 解决 最小费用问题(matlab程序)

1.简述 fmincon函数非线性约束下的最优化问题 fmincon函数&#xff0c;既是求最小约束非线性多变量函数 该函数被用于求如下函数的最小值 语法如下: x fmincon(fun,x0,A,b) x fmincon(fun,x0,A,b,Aeq,beq) x fmincon(fun,x0,A,b,Aeq,beq,lb,ub) x fmincon(fun,x0,A,b,Aeq…