C++学习笔记----6、内存管理(一)---- 使用动态内存(3)

news2024/9/21 0:52:31

3.2、对象数组

        对象数组与原型/基础类型的数组没有什么不同,除了元素的初始化之外。当你使用new[N]去分配N个对象,就把N个连续的块空间分配出去了,每一个块空间可以放一个单独的对象。对于对象数组,New[]对每一个对象自动调用0参数(也就是缺省)构造函数,而原型数组清爽型缺省是没有被初始化的元素。用这种方式,使用new[]来分配对象数组返回一个指向完全构造并且初始化了的对象的指针。

       例如,考虑以下的类:

class Simple
{
public:
    Simple() { println("Simple constructor called!"); }
    ˜Simple() { println("Simple destructor called!"); }
};

       如果你分配了四个Simple对象的数组,Simple的构造函数就被调用了四次。

Simple* mySimpleArray { new Simple[4] };

        下图展示了数组的内存图示。可以看到,与基本类型的数组没有什么区别。

3.3、删除数组

       当你用new[](也就是new的数组版本)来分配内存时,也要用delete[](也就是delete的数组版本)来释放它。这个版本自动析构数组中的对象并且释放与其关联的内存。

Simple* mySimpleArray { new Simple[4] };
// Use mySimpleArray...
delete [] mySimpleArray;
mySimpleArray = nullptr;

       如果你不使用数组版本的delete,你的程序运行可能就会很怪异。对于有些编译器,只有数组的第一个元素的析构函数会被调用,因为编译器只知道你在删除一个指向对象的指针,数组的其他元素就会变成孤儿对象。而对于其他的编译器,内存可能会出现崩溃,因为new与new[]用的内存分配模式完全不一样。

       对于用new分配的要用delete,而对于用new[]分配的要用delete[]。

       当然了,只有数组的元素为对象时才需要调用析构函数。如果你有一个指针数组,你仍然需要删除为每个对象单独分配的空间。如下代码所示:

const size_t size { 4 };
Simple** mySimplePtrArray { new Simple*[size] };
// Allocate an object for each pointer.
for (size_t i { 0 }; i < size; ++i) { 
    mySimplePtrArray[i] = new Simple{}; 
}
// Use mySimplePtrArray...
// Delete each allocated object.
for (size_t i { 0 }; i < size; ++i) {
    delete mySimplePtrArray[i];
    mySimplePtrArray[i] = nullptr;
}
// Delete the array itself.
delete [] mySimplePtrArray;
mySimplePtrArray = nullptr;

        在现代C++中,当有属主介入时,应该避免使用原始C风格的指针。不要把原始指针存储在C风格的数组中,应该把智能指针存储在现代标准构造函数中,比如std::vector。我们马上要讨论的智能指针会自动地在合适的时间将与其关联的内存释放掉。

3.4、多维数组

        多维数组将索引值的记号扩展到多个索引。例如,一个跳跳棋游戏可能会使用一个二维的数组来代表一个3乘3的网格。下面的例子展示了在栈上声明的一个数组,用0进行了初始化,以及一些测试代码:

char board[3][3] {};
// Test code
board[0][0] = 'X'; // X puts marker in position (0,0).
board[2][1] = 'O'; // O puts marker in position (2,1).

        你可能会想二维数组的第一个下标是X坐标还是Y坐标呢。事实是它并不重要,只要保持一致就行。一个4乘7的网格可以声明为char board[4][7]或者board[7][4]。对于大部分应用来说,第一个下标为x轴第二个下标为y轴可能更容易理解。

3.5、多维栈数组

        在内存中,3乘3的栈的二维board数组看起来像下图。

       因为内存不是二维的(地址是连续的),计算机标识二维数组也会像一维数组一样。不同之处在于数组的大小有及用什么方法来访问。

       多维数组的大小是所有维度相乘,然后乘以数组中的单个元素的大小。在上图中,3乘3的游戏板就是3*3*1=9个字节,假设一个字符是一个字节。对于一个4乘4的字节游戏板来说,数组就是4*7*1=28个字节。

       要访问多维数组中的一个值,计算机将每一个下标视为多维数组中的一个子数组。例如,3乘3的网格,board[0]实际上就是下图中高亮的子数组。

       当你加上第二个下标,比如board[0][2],计算机就会通过子数组中的第二个下标查找到正确的元素进行访问,如下图所示:

        这个技巧可以扩展到N维数组,虽然高于三维的数组难于概念化并且较少被用到。

3.6、多维自由内存空间上的数组

       如果需要在运行时决定多维数组的维度,可以使用在自由内存空间上的数组。与一维动态分配的数组通过指针访问一样,多维动态分配的数组也可以通过指针访问。不同的地方在于在二维数组中,需要用一个指向指针的指针;在一个N维的数组中,需要N层的指针。一开始,好像正确的方式是声明并且分配一个动态分配的多维数组如下:

char** board { new char[i][j] }; // BUG! Doesn't compile

        该代码编译不通过,因为多维自由内存空间上的数组与栈上数组不一样。其内存构成不是连续的。实际情况是,是在自由内存空间上为第一个下标维度用分配一个连续的数组开始。数组的每一个元素实际上是指向另一个保存了第二个下标维度的元素的数组的指针。下图展示了2乘2动态分配游戏板的构成。

        不幸的是,编译器不会为你对子数组进行内存分配。你可以像一维自由内存空间上的数组那样分配第一维的数组。但是每一个子数组必须显示分配。下面的函数正确地为一个二维数组分配了内存:

char** allocateCharacterBoard(size_t xDimension, size_t yDimension)
{
    char** myArray { new char*[xDimension] }; // Allocate first dimension
    for (size_t i { 0 }; i < xDimension; ++i) {
        myArray[i] = new char[yDimension]; // Allocate ith subarray
    }
    return myArray;
}

        同样的,当你想要对自由内存空间上的多维数组进行内存释放时,delete[]语法也不能为你清理子数组。释放数组的代码与分配的代码相对应,如下函数:

void releaseCharacterBoard(char**& myArray, size_t xDimension)
{
    for (size_t i { 0 }; i < xDimension; ++i) {
        delete [] myArray[i]; // Delete ith subarray
        myArray[i] = nullptr;
    }
    delete [] myArray; // Delete first dimension
    myArray = nullptr;
}

       上面的为多维数组分配内存的例子并不是一个非常高效的解决方案。它首先为第一维分配了内存,接着为每一个子数组分配了内存。结果就是内存块在内存中散落各处,对于这样的数据结构上的算法来讲有很大的性能影响。如果算法在连续的内存上会跑得更快。好的解决方案是分配一个单独的内存块,足够保存xDimension * yDimension个元素,用像x*yDimension + y的公式来访问(x,y)位置的元素。

       既然你已经知道了数组工作的细节,推荐你尽可能避免这些旧的C风格的数组,因为它们不能提供内存安全。在这儿解释这么多,是因为你会在遗留的代码中会碰到。在新的代码中,应该使用C++标准库函数,比如std::array与vector。例如,使用vector<T>来用一维动态数组。对于二维动态数组 ,可以使用vector<vector<T>>,再多维的数组也类似。当然了,直接使用像vector<vector<T>>这样的数据结构也比较烦,特别是要构造它们,也会有前面讨论的同样的内存碎片问题。所以啊,如果在你的应用中确实需要N维动态数组,考虑写一个helper类,提供一个易于使用的接口。例如,对于二维数据,有同样长的行,你可以考虑写(当然也可以重用)一个Matrix<T>或者Table<T>类模板,把内存分配/释放与用户访问元素算法隐藏下来,我们以后会专门讨论写类模板的细节。

       不要使用C风格的数组,要使用C++标准库函数,比如std::array,vector等等。

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

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

相关文章

Linux虚拟机安装(CentOS9)

需要自己设置一下内存&#xff0c;处理器&#xff0c;以及镜像文件 **************************************************************** 设置完硬件后&#xff0c;启动虚拟机&#xff0c;开始配置操作系统 选择英文 这里需要改三个地方 1. 2. 3. 设置root账户的密码 全部完…

Anaconda安装教程就看这里

Anaconda安装教程就看这里 1 了解Anaconda2 为什么要安装Anaconda3 安装Anaconda3.1 下载并安装3.2 环境配置 4 管理python环境4.1 NAVIGATOR创建python3.94.2 命令行创建python3.84.3 Pycharm中通过conda管理python环境 1 了解Anaconda Anaconda是易于安装的包管理器、环境管…

RLHF PPO DPO

生成式大模型的RLHF技术&#xff08;一&#xff09;&#xff1a;基础​​​​​​​ DPO: Direct Preference Optimization 论文解读及代码实践 深入对比 DPO 和 RLHF 深入理解DPO&#xff08;Direct Preference Optimization&#xff09;算法

数据结构---链表

//链表的创建 Link_t *create_link() {Link_t *plink malloc(sizeof(Link_t));if(NULL plink){perror("fail plink");return NULL;}plink->phead NULL;plink->clen 0;return plink; } //头插 int push_link_head(Link_t *plink, DataType data) {Link_Nod…

九月最新蜘蛛池|泛码网的SEO工具怎么样?

在当今数字化的时代&#xff0c;网络营销已经成为企业和个人推广的重要手段。而在众多网络营销方式中&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;备受关注。随着 SEO 的发展&#xff0c;各种 SEO 工具也如雨后春笋般涌现。那么&#xff0c;这些 SEO 工具到底有没有用…

AI制作情侣头像副业项目,每天只需2小时,收入是我工资的三倍(附教程)

现在好赚钱的项目模式&#xff0c;一般都是提供情绪价值&#xff0c; 用AI制作情侣头像这个就是一个非常不错的情绪价值输出。 这个项目被真名是可以落地实操的&#xff0c;做好了日入500是可以有的。(文末免费领取教程) 项目介绍 在当下社会中&#xff0c;年轻情侣都喜欢用…

SpringFrameWork学习笔记

本笔记基于【尚硅谷新版SSM框架全套视频教程&#xff0c;Spring6SpringBoot3最新SSM企业级开发】https://www.bilibili.com/video/BV1AP411s7D7?vd_sourcea91dafe0f846ad7bd19625e392cf76d8 总结 资料获取网址&#xff1a;https://www.wolai.com/v5Kuct5ZtPeVBk4NBUGBWF 技术…

用 coze 做一个简单的外卖评价助手

大家有没有遇到过这种情况&#xff1f;点外卖时看到"好评返现金"的活动&#xff0c;可是又不想输入太多内容&#xff0c;那该怎么办呢&#xff1f; 别担心&#xff0c;今天我就来教大家如何利用 Coze 智能体&#xff0c;只需要一张简单的图片就能自动帮你生成好评! …

2024年建筑电工(建筑特殊工种)证模拟考试题库及建筑电工(建筑特殊工种)理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年建筑电工(建筑特殊工种)证模拟考试题库及建筑电工(建筑特殊工种)理论考试试题是由安全生产模拟考试一点通提供&#xff0c;建筑电工(建筑特殊工种)证模拟考试题库是根据建筑电工(建筑特殊工种)最新版教材&#…

Phalcon 增删改查的搭建过程

一 结果展示 先展示效果: 1 查询: 2 删除 3 插入 插入之前,数据库里面表的数据如下: 插入之后:

Git基础教程:掌握版本控制的秘密

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;C从入门到精通 目录 &#x1f680; 什么是Git &#x1f680; 在本系列博客中所实现的目标 一&#xff1a; &#x1f525; Git 初识 二&#xff1a; &#x1f525; Git 安装 &#x1f34a; Linux-c…

结构型设计模式—组合模式

结构型设计模式—组合模式 欢迎长按图片加好友&#xff0c;我会第一时间和你分享持续更多的开发知识&#xff0c;面试资源&#xff0c;学习方法等等。 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你将对象组合成树形结构来表示…

亿图图示下载安装教程EdrawMax Pro 13版超详细图文教程

亿图图示下载安装教程EdrawMax Pro 13版超详细图文教程&#xff1a; 亿图图示是一款功能强大的综合绘图软件&#xff0c;具有以下特点和功能 丰富的绘图类型&#xff1a;涵盖 210 余种办公绘图类型&#xff0c;包括流程图、思维导图、信息图、工业设计、组织架构图、平面设计…

k8s使用报错

报错内容&#xff1a; [rootk8s-master ~]# kubectl get nodes E0903 17:21:22.183325 4630 memcache.go:265] couldnt get current server API group list: Get "https://172.25.250.100:6443/api?timeout32s": dial tcp 172.25.250.100:6443: connect: connec…

多目标应用:基于环形拓扑的多目标粒子群优化算法(MO_Ring_PSO_SCD)的移动机器人路径规划研究(提供MATLAB代码)

一、机器人路径规划介绍 移动机器人&#xff08;Mobile robot&#xff0c;MR&#xff09;的路径规划是 移动机器人研究的重要分支之&#xff0c;是对其进行控制的基础。根据环境信息的已知程度不同&#xff0c;路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或…

SpringBoot2:配置类的一般写法

1、知识准备 首先&#xff0c;我们要知道&#xff0c;springboot的配置类的构造器&#xff0c;构造器参数&#xff0c;是从IOC容器中获取的。 2、案例说明 我随便找个springboot的一个自动配置类 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAuto…

阻塞队列-单锁实现

使用阻塞队列 当我们多个线程下 对 一个队列进行操作&#xff0c;队列满了的情况下&#xff0c;其他线程再次 offer&#xff0c;会一直阻塞等待 对一个队列进行出队操作的时候&#xff0c;队列空的情况下&#xff0c;会一直阻塞等待删除&#xff0c;直到队列有元素的时候&a…

Java-线程的生命周期7大状态

在 Java 中&#xff0c;线程的生命周期可以分为多个状态&#xff0c;这些状态描述了线程从创建到终止的整个过程。Java 线程的生命周期主要包括以下七大状态&#xff1a; 1.新建状态&#xff08;New&#xff09; 当一个线程对象被创建但尚未调用 start() 方法时&#xff0c;线…

Zabbix结合Grafana

一、Grafana简介 Grafana 是 Graphite 和 InfluxDB 仪表盘和图形编辑器。Grafana 是开源的&#xff0c;功能齐全的度量仪表盘和图形编辑器&#xff0c;支持 Graphite&#xff0c;InfluxDB 和 OpenTSDB。 Grafana 主要特性&#xff1a;灵活丰富的图形化选项&#xff1b;可以混合…

数分基础(06)商业分析四种类型简介

文章目录 1. 商业分析2. 四种类型2.1 描述性分析和诊断性分析2.1.1 加载Global_Superstore数据集2.1.2 描述性分析2.1.3 诊断性分析2.1.4 再进一步各地区的订单数量和平均订单金额按客户群体分析销售额和利润折扣率和利润产品类别和子类别的销售和利润 本小节小结 2.2 销售预测…