C++动态内存new和delete

C++动态内存是C++灵活、炫酷的一种操作。学好它,能让自己编程逼格上一个level。

在学习动态内存之前,我们先要了解C++是怎么划分内存的:

  • 栈:在函数内部声明的所有变量都将占用栈内存。栈是由编译器自动分配和释放的,由系统分配。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。大名鼎鼎的GC(Garbage Collection)垃圾回收机制在堆内存上进行的。

这里的栈和堆和数据结构中的栈和堆不是一个概念,不要搞混。内存在物理上其实都是相同的,所以可以说,栈内存和堆内存是系统抽象出来的。

栈本身的空间很小,但是读取速度很快,仅次于寄存器读取。栈上的变量在函数(方法)执行完后,就会被回收。

堆是程序运行过程中,程序自己向系统申请的,申请和销毁都需要时间,堆内存分配存储会花更多时间。所以堆的效率明显低于栈。但是堆的优点在于,编译器不必知道要从堆里分配多少内存空间,也不必知道存储的数据要在堆里停留多长的时间,因此用堆保存数据时会得到更大的灵活性。

那么看到这里,再结合今天的标题,我们可以知道,C++动态内存就是向系统申请堆内存,我们使用new运算符申请,系统将返回所分配的堆内存的地址(这句话很重要)。如果不再需要这块动态分配的内存空间,可以(必须)使用delete运算符,删除之前分配的内存。

具体实现

下面是使用 new 运算符来为任意的数据类型动态分配内存的通用语法:

new data-type;

在这里,data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。光分配不行啊,我们还要对这个数据进行操作啊,如下。

假设我们有一个指向int类型的指针,申请内存如下:

int* p = NULL; // 初始化指针,指向空
p = new int; // 为变量请求内存

这里,就可以看出我之前加粗的话的作用了,new返回的是申请分配的堆内存的地址。所以我们在动态内存中,不同于以往要么直接对变量操作、要么利用引用操作,我们都是通过指针来获取地址,在“地址层”对数据进行访问。

此后,你便可以利用p指针来操作堆内存里的数据了。

// ...略
int main(){
int* p = new int;
*p = 520;
cout << "Value of p is: " << *p << endl; delete p; return 0;
}

事实上,new不仅分配了内存,还创建了对象。(可以参考其他语言的内存分配,尤其是Java)

如果我们不再需要使用这个动态分配的内存时,务必要用delete运算符释放它占用的内存,不然会造成十分严重的内存泄漏问题。也就是说new和delete必须配对使用。

delete p; // 删除具体的指针

这里再多说一句,delete的操作,是删除了p所指的目标,释放了它所占的堆空间,而不是删除p本身(指针p本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放,指针p的真正释放是随着函数调用的结束而消失),释放堆空间后,p成了"空指针"。如果我们在delete p后没有进行指针p的制空(p=NULL)的话,其实指针p这时会成为野指针,为了使用的安全,我们一般在delete p之后还会加上p=NULL这一语句。

所以我们在数据结构中,经常会看见这样的操作:

temp = p;  // temp和p指向同一块堆内存
p = p->right; // p指向别的内存
delete temp; // 删除最初p指向的那块堆内存的数据

数组的动态内存分配

基本数据类型的动态内存分配是很简单很少用的,动态内存更多用在数组、对象的分配上,这里我们先说数组。

假设我有n个苹果,每个苹果的质量不同,要存进数组里,我可能会这么写:

int n = 0;
cin >> n;
int apples[n];

乍一看一点问题没有,但是编译器是不会通过的。我们只能通过动态内存来对未知大小的数组分配空间。

int n;
cin >> n;
int* p = NULL; // 初始化指针
p = new int[n]; // 为变量p申请内存

这样就一点问题都没有了。如果要删除,则

delete [] p;	// 删除p指向的数组

注意一下删除的格式,有点奇怪。下面拿一维数组举例子:

int* array = new int[n];
for (int i = 0; i < n; i++){
cin >> array[i]; // 注意不是 *array[i]!!!
// 或者 cin >> *(array + i);
}
delete [] array;

这里很多人问为什么不是*array[i],应该是把里面的值取出来啊!其实不是,详情可以参见指针与数组的关系

这里简单说下,大家都知道数组名是数组的首地址,如果有int a[10];,我们甚至可以分开理解,就把a看成是这个数组的指针。数组a[2]是取出第三个元素,那么指向数组的指针p同样也是p[2]取出。所以很多时候指针和数组是等同的。

但是,数组作为数组还是有其尊严的。数组的首地址是不可以改变的!上述例子,如果我想通过移动array来操作动态内存中的数组是绝对不可以的,详见数组的禁忌 。所以单纯指向数组的指针ptr,我们可以用ptr++这种操作来实现指针在数组内的游走。但是绝对不可以直接移动数组名这种方式来操作。所以动态内存分配的数组,并不像栈中的数组一样有“实形”,它的地址就是它的本身。所以上述例子中,严禁用改变array的方式来操作动态分配的数组。这样导致了数组首地址的改变。

同时值得说一句,动态数组,如果不进行计数或者开始就知道大小,没有很好的办法知道数组的长度。

对象的动态内存分配

有过其他面向对象语言学习的都知道对象的创建用到了new,如Java的对象创建大致如下。

Animal tiger = new Animal();

现在我们知道了,因为Java的GC机制,tiger这个对象是被创建在堆上的。我们甚至可以说,Java没有栈上的对象。而C++不一样,C++是支持栈上的对象的。我们现在为了防止对象被销毁,需要在堆上创建对象,便采取动态内存。

Animal* p = NULL; // 创建指向Animal类的指针
p = new Animal; // p赋予了新建对象的起始地址

当然,我们也可以调用类的构造函数来创立对象。

Animal* p = new Animal();  // 没有传入参数的构造函数
Animal* p = new Animal("Tiger"); // 传入了参数的构造函数

由于C++没有垃圾回收机制,我们必须在不使用改对象时,删除它。(这下你理解了为什么大家都说C#,Java是自动挡,C++是手动挡车了)

delete p; // 会调用类的析构函数

C++动态内存new和delete(超详细)的更多相关文章

  1. jdk动态代理和cglib动态代理底层实现原理超详细解析(jdk动态代理篇)

    代理模式是一种很常见的模式,本文主要分析jdk动态代理的过程 1.举例 public class ProxyFactory implements InvocationHandler { private ...

  2. c++动态内存管理与智能指针

    目录 一.介绍 二.shared_ptr类 make_shared函数 shared_ptr的拷贝和引用 shared_ptr自动销毁所管理的对象- -shared_ptr还会自动释放相关联对象的内存 ...

  3. C++—动态内存管理之深入探究new和delete

    C++中程序存储空间除栈空间和静态区外,每个程序还拥有一个内存池,这部分内存被称为自由空间(free store)或堆(heap).程序用堆来存储动态分配的对象,即,那些程序运行时分配的对象.动态对象 ...

  4. C++动态内存管理之深入探究new和delete

    C++中程序存储空间除栈空间和静态区外,每个程序还拥有一个内存池,这部分内存被称为自由空间(free store)或堆(heap).程序用堆来存储动态分配的对象,即,那些程序运行时分配的对象.动态对象 ...

  5. C++编程学习(八)new&delete动态内存分配

    前段时间楼主忙着期末大作业,停更了一段,今天刚好在做机器人课程的大作业时,和同组的小伙伴利用python做了工业机器人的在线编程,突然想起来很久没有阅读大型工程了,马上补上- 接下来的几篇博客主要是博 ...

  6. C++ Primer : 第十二章 : 动态内存之动态内存管理(new和delete)

    C++语言定义了两个运算符来分配和释放动态内存:运算符new分配内存,运算符delete释放new分配的内存. 运算符new和delete 使用new动态分配和初始化对象 在自由空间分配的内存是无名的 ...

  7. [008]new、delete及动态内存分配

    1.new和delete都会用,这里只声明一点: C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针. 比如下面的代码: #include<iostream> using ...

  8. 动态内存分配(new)和释放(delete)

    在之前我们所写过的程序中,所必需的内存空间的大小都是在程序执行之前就已经确定了.但如果我们需要内存大小为一个变量,其数值只有在程序运行时 (runtime)才能确定,例如有些情况下我们需要根据用户输入 ...

  9. 深入理解C++ new/delete, new []/delete[]动态内存管理

    在C语言中,我们写程序时,总是会有动态开辟内存的需求,每到这个时候我们就会想到用malloc/free 去从堆里面动态申请出来一段内存给我们用.但对这一块申请出来的内存,往往还需要我们对它进行稍许的“ ...

随机推荐

  1. flask 设置https请求 访问flask服务器

    学习过程中想要学教程中一样,做个假的微信公众号推送,不过去了微信开发文档怎么一直说需要https的请求(教学中没有说需要https,一直是http) 但是我的服务器只能使用http请求访问,如果硬是要 ...

  2. Redis集群搭建及选举原理

    redis集群简述 哨兵模式中如果主从中master宕机了,是通过哨兵来选举出新的master,在这个选举切换主从的过程,整个redis服务是不可用的.而且哨兵模式中只有一个主节点对外提供服务,因此没 ...

  3. [日志分析]Graylog2采集Nginx日志 主动方式

    这次聊一下Graylog如何主动采集Nginx日志,分成两部分: 介绍一下 Graylog Collector Sidecar 是什么 如何配置 Graylog Collector Sidecar 采 ...

  4. js Number方法总结

    Number构造属性 Number.EPSILON // 两个可表示(representable)数之间的最小间隔. Number.MAX_SAFE_INTEGER // JavaScript 中最大 ...

  5. 0919-The Standard of Code Review

    The primary purpose of code review is to make sure that the overall code health of Google’s code bas ...

  6. 【TIJ4】第五章全部习题

    第五章习题 5.1 package ex0501; //[5.1]创建一个类,它包含一个未初始化的String引用.验证该引用被Java初始化成null class TestDefaultNull { ...

  7. http详解和分析

    1.http是什么? http 是一种超文本传输协议原名是这个Hypertext Transfer Protocol -- HTTP/1.1 可以百度查看http的RFC文档编号为RFC-2616 连 ...

  8. 【简说Python WEB】Flask应用的文件结构

    目录 [简说Python WEB]Flask应用的文件结构 1.文件结构的目录 2.配置程序--config.py 3.app应用包 4.剥离出来的email.py 5.蓝本(BLueprint)的应 ...

  9. Python Django撸个WebSSH操作Kubernetes Pod

    优秀的系统都是根据反馈逐渐完善出来的 上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,生成一个临时访问地址,详细信息可以 ...

  10. Python-hashlib、OS、Random、sys、zipfile模块

    # print(sys.version) #python 版本 # print(sys.path) # print(sys.platform) #当前什么系统 # print(sys.argv) #当 ...