Skip to main content

moregeek program

c++ 动态内存开辟_玄鸟轩墨的博客-多极客编程

写在前面

我们知道C++是支持C语言的,也就是说,C语言里面的malloc等函数都可以在C++中使用,但是C++有支持了另外两个关键字,这是很有用的,我们需要看看C++的动态内存.

C/C++ 内存分布

我记得,在初识C语言那里就和大家分享了程序虚拟地址空间的概念,无论是C语言的nalloc函数,还是我们现在要分享的new,都是在堆区开辟空间,这一点是我们要首先记得的。

C++ 动态内存开辟_c++

C语言内存管理方式

C语言是通过函数来经行动态的内存开辟的,标准库里面提供三个函数,这里我就不加赘述了,大家应该都是知道的。我么看看用法就可以了。

#include <stdio.h>
#include <assert.h>

int main()
{
// malloc 开辟空间 不初始化
int* p1 = (int*)malloc(sizeof(int)* 4);
assert(p1);

//calloc 开辟空间 初始化 为 0
int* p2 = (int*)calloc(4, sizeof(int));
assert(p2);
// 追加 空间
p1 = (int*)relloc(p1, sizeof(int)* 8);

free(p1);
free(p2);
return 0;
}

C++内存管理方式

C++是支持C语言的,也是说C++是可以使用这些函数的,但是除了这些函数外,C++有增加了new和delete这个两个关键字,分别对标的malloc/calloc和free,而且C++的方式比C的好用.

C++为何增加了new 和 delete

我们都知道,C语言的结构体里面不支持函数,所以大佬们提出了类的概念,出现了class,又害怕自己有时后可能忘记初始化和清除掉内存,就出现了构造函数和析构函数,让编译器自动调用,可以说,所有的事物的出现都是为了我们更好的使用语言,new和delete也似乎如此,C语言的动态内存开辟是有一定的麻烦的,而且对于自动类型很不友好,后面我们就会比较他们的优劣.

我们还发现一个很直接问题,每一次开辟空间我们都要强制类型转换,而且还需要判断内存是不是究竟开出来了,这也太麻烦了,new却不会出现这种事,如果没有开辟出,编译器会抛异常,我们就不需要再自己手动检测了.

new 一个对象

这样,我先和大家演示内置类型,自定义类型那里我准备专门和malloc比较一下.

#include <iostream>
using namespace std;

int main()
{
int* p1 = new int;
*p1 = 10;
cout << *p1 << endl;
return 0;
}

C++ 动态内存开辟_c++_02

我们也知道,再C++中,内置类行也被作为类了,我们可以再new的时候对它进行初始化.

int main()
{
int* p = new int(0);
cout << *p << endl;

return 0;
}

C++ 动态内存开辟_构造函数_03

new 一个数组

new一个数组更是简单,我们直接写出来就可以了.

int main()
{
int* p = new int[10]; // new 一个 10 个int 类行的空间
return 0;
}

C++ 动态内存开辟_c++_04

我们也可以在new空间的时候进行实例化,不过要显示实例化

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

C++ 动态内存开辟_构造函数_05

delete

大家可能发现,我上面都没有释放空间,这会造成内存泄漏,这里我们用另一个关键字delete,这里就比较简单了.

大家可能疑惑delete[],这里我们记住就可以了,如果你要清除数组的空间,最好使用这种方式,或许对于内置类行,使用delete也可以,但是对于自定义类行可能会报错,这里我也放在后面谈.

int main()
{
int* p1 = new int;
int* p2 = new int[10]{1,2,3};

delete p1;
delete[] p2;
return 0;
}

C++ 动态内存开辟_c语言_06

malloc & new

我们需要对比一下malloc和new它们之间的区别,这样就可以知道C++为何这么喜欢new了.

内置类型

我们先下一个结论,它们两个对于内置类行除了报错之外是没有任何区别的,都不会经行初始化,这里我们现不谈报错的信息,异常和没有和大家分享.

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

int* p2 = (int*)malloc(sizeof(int)* 10);
assert(p2);

delete[] p1;
free(p2);
return 0;
}

C++ 动态内存开辟_c++_07

自定义类型

对于自定义类型,它们的差别可大了去了.

我们先来准备一个类

class A
{
public:
A(int a = 0,int b=0)
:_a(a)
, _b(b)
{
cout << "构造函数" << endl;
}
~A()
{
cout << "析构函数" << endl;
}
private:
int _a;
int _b;
};

malloc是直接开辟空间,对于里面的构造函数是不会调用的,free的时候也不会调用析构函数

int main()
{
A* aa = (A*)malloc(sizeof(A));
free(aa);
return 0;
}

C++ 动态内存开辟_c语言_08

new 和 delete会分别调用构造函数和析构函数,完成初始化

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

C++ 动态内存开辟_c++_09

operator new与operator delete函数

这里一看像是new和delete的重载,记住,这不是,就是名字有点奇怪罢了.这是C++里面的全局函数,它的使用方法和malloc一样,而且作用也是有一样的,不会调用构造函数和析构函数.

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

int main()
{
A* aa = (A*)operator new(sizeof(A));
operator delete (aa);
return 0;
}

C++ 动态内存开辟_c++_10

原理

通过源码我们就会发现,实际上operator new与operator delete函数 本质上是malloc和free的封装,就是报错的信息有点不同,封装的报错的信息是异常.

operator new 的原理是 malloc

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}

operator delete 原理

void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse); // 注意 C语言的free 就是这个函数
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}

为何出现这两个函数

这两个函数不是给我们调用的,是为了new的底层调用的,我们new一个对象,就相当于call operator new 和 call 对象的构造函数,这才是它们出现的原因.

大家可以看看反汇编.

C++ 动态内存开辟_c++_11

delete & delete[]

这个我们可以这么理解,对于内置类型,它们就没必要讨论的,作用差不多.但是对于自定义类型就有很大的问题.

  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

大家先看看结果.

delete[] 析构相应的的次数

int main()
{
A* aa = new A[3];
delete[] aa;
return 0;
}

C++ 动态内存开辟_构造函数_12

delete 析构一次,还会报错

int main()
{
A* aa = new A[3];
delete aa;
return 0;
}


内存池

这里我想提一个概念,我们都知道malloc和new都是在堆上开辟空间,如果我们要是多次的去开辟空间,效率是不是有点慢,想一想,我们一次开辟一次,开了个上千次,每次都要去申请,我们在想,能不能单独的划分出一块区域,专门提供我们想要的对象来开辟空间,这就是内存池最初的想法,大家可能会感到疑惑,内存池和堆有什么不同吗,简单来说,内存池离你近,可以提高效率。我们可以这么类比,堆就像每吨你在学校吃饭就和你老爸要钱,每顿都要,那么内存池就像月初你直接和你爸要好这个月的生活费,一月要一次,肯定是后者的效率比较高的。

那么我们该如何使用内存池,标准库里面也提供了一个,这里我们需要在类内重写operator new与operator delete函数函数,大家先来了解一下用法就可以了,我们先不来细究,后面可能会有一个高并发内存池的项目要和大家分享,不过这个时间就有点长了。

struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _data;

// 申请空间的是后去内存 池
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List
{
public:
List()
{
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};

int main()
{
List l1;
return 0;
}

C++ 动态内存开辟_构造函数_13

定位 new

我们已经知道了,使用operator new开辟出的空间是不会初始化的,而且现在我们是无法通过对象来显式调用构造函数的,这也就意味着我们要是向修改成员变量,一定会破坏封装.但是C++这里也提供了一个定位new的技术可以帮助我们再次实例化,我们先来看看用法.

class A
{
public:
A(int a = 0)
:_a(a)
{
}
private:
int _a;
};

int main()
{
A* a = (A*)operator new(sizeof(A));

// 定位 new
new(a)A (1);
return 0;
}

C++ 动态内存开辟_c语言_14

从这里我们就可以知道了,定位new有下面两种用法

  • new(要初始化的指针) 指针解引用对应的类行 直接调用默认构造函数
  • new(要初始化的指针) 指针解引用对应的类行 (构造函数要传的参数) 调用相应的构造函数

©著作权归作者所有:来自51CTO博客作者玄鸟轩墨的原创作品,请联系作者获取转载授权,否则将追究法律责任

c++ 初识类(中)_玄鸟轩墨的博客-多极客编程

写在前面我们已经初步的认识到到类的内容了,但是那些都是基础的,今天我们来分享一些比较细节的内容,也是对我知识的一个梳理,难度比较大,所以这个博客我可能写的不太好,到时候有什么问题,可以直接在评论区留言,我看到立马回复,要是我也不懂的,就去给你们查资料,还请谅解.类的6个默认成员函数这个博客主要就是和大家分享成员函数(方法),里面的内容比较多,我一个一个来吧,我们现在来看道例题,作为今天的开篇.请问

[ c++ ] string类常见接口及其模拟实现_小白又菜的博客-多极客编程

上一篇博文内容:string类的构造,拷贝,赋值拷贝,及其模拟实现​​[ C++ ] string类之构造,拷贝,赋值 及其模拟实现​​本篇内容:string类的常见接口及其模拟实现,我将会从遍历,增,删,查,改5个方面对常见,常用的接口进行模拟实现string类。该思维导图是本篇博文主要内容1、遍历1.1 下标+operator [ ]这种方法是最好理解的,我们使用下标将字符串的内容逐字符输出。

每日算法刷题day3-起始时间转换、二次方根、while连续输入、斐波那契思路_wx62e40d60030b6的博客-多极客编程

8.游戏时间2读取四个整数 A,B,C,D,用来表示游戏的开始时间和结束时间。其中 A 和 B 为开始时刻的小时和分钟数,C 和 D 为结束时刻的小时和分钟数。请你计算游戏的持续时间。比赛最短持续 1分钟,最长持续 24 小时。输入格式共一行,包含四个整数 A,B,C,D。输出格式输出格式为 ​​O JOGO DUROU X HORA(S) E Y MINUTO(S)​​,表示游戏共持续了 X 小

每日算法刷题day4-完全数、分情况输出、平方矩阵、斐波那契数列匹配输出_wx62e40d60030b6的博客-多极客编程

每日算法刷题Day4-完全数、分情况输出、平方矩阵、斐波那契数列匹配输出⭐每日算法题解系列文章旨在精选重点与易错的算法题,总结常见的算法思路与可能出现的错误,与笔者另一系列文章有所区别,并不是以知识点的形式提升算法能力,而是以实战习题的形式理解算法,使用算法。13. 完全数一个整数,除了本身以外的其他所有约数的和如果等于该数,那么我们就称这个整数为完全数。例如,6 就是一个完全数,因为它的除了本身

代码的表示_wx6304721ccefc0的博客-多极客编程

   在代码中会有许许多多的表示,会有表示16进制数字的%x,表示地址的%p,表示单精度浮点形的%f,表示双精度浮点形的%lf,表示字符的%c,表示整数的%d。#include<stdio.h>int main(){ char ch = 'A'; printf("%c\n",ch);//%c表示字符,\n表示换行 return 0;}#include<stdio.h>

qt学习第七天_五个板栗的博客-多极客编程

一、数据库操作      Qt 提供了 QtSql 模块来提供平台独立的基于 SQL 的数据库操作。这里我们所说的“平台独立”,既包括操作系统平台,有包括各个数据库平台。另外,我们强调了“基于 SQL”,因为 NoSQL 数据库至今没有一个通用查询方法,所以不可能提供一种通用的 NoSQL 数据库的操作。Qt 的数据库操作还可以很方便的与 model/view 架构进行整合。通常来说,我们对数据库

c++ 初识类(中)_玄鸟轩墨的博客-多极客编程

写在前面我们已经初步的认识到到类的内容了,但是那些都是基础的,今天我们来分享一些比较细节的内容,也是对我知识的一个梳理,难度比较大,所以这个博客我可能写的不太好,到时候有什么问题,可以直接在评论区留言,我看到立马回复,要是我也不懂的,就去给你们查资料,还请谅解.类的6个默认成员函数这个博客主要就是和大家分享成员函数(方法),里面的内容比较多,我一个一个来吧,我们现在来看道例题,作为今天的开篇.请问

【java入门】第六天 运算符的介绍_qq62fded605da02的博客-多极客编程

 算术运算符1、+、-、*、%、/ 属于二元运算符。%是取模运算符。就是我们常说的求余数操作。2、算术运算符中++(自增),--(自减)属于一元运算符。二元运算符的运算规则:        整数运算:             如果两个操作数有一个为long,则结果也为long。             没有long时,结果为int。即使操作数全为short,byte,结果也是int       

#yyds干货盘点# 面试必刷top101:合并二叉树_风的博客-多极客编程

1.简述:描述已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。例如:两颗二叉树是:                                                                    Tree 1                                                

行为型设计模式之责任链模式_积跬步,至千里。的博客-多极客编程

@TOC 责任链模式 责任链模式(Chain of Responsibility Pattern)属于行为型模式。 它是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。 应用场景 责任链模式主要是解耦请求与处理,客户只需将请求发送到链上即可,无需关心请

#yyds干货盘点# leetcode算法题:搜索二维矩阵_灰太狼_cxh的博客-多极客编程

题目:编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:每行中的整数从左到右按升序排列。每行的第一个整数大于前一行的最后一个整数。 示例 1:输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3输出:true示例 2:输入:matrix = [[1,3,5,7],[10,11,16,20]

求求你们了,mybatis 批量插入别再乱用 foreach 了,5000 条数据花了 14 分钟。。_码农小宋的博客-多极客编程

近日,项目中有一个耗时较长的Job存在CPU占用过高的问题,经排查发现,主要时间消耗在往MyBatis中批量插入数据。​​mapper configuration是用foreach循环做的,差不多是这样。(由于项目保密,以下代码均为自己手写的demo代码)​​<insert id="batchInsert" parameterType="java.util.List"> inse