CPP学习之——为什么使用指针(8.9.1-8.9.3)

一 概述

  • 很多初学者都会问,既然通过变量名就可以访问数据,为什么还要使用繁琐而又容易出错的指针呢?
  • 这是因为在操作大型数据和类时,由于指针可以通过内存地址直接访问数据,从而避免在程序中复制大量的代码,因此指针的效率最高。

二 指针的用途

一般来说,指针会有三大用途:

  • 处理堆中存放的大型数据
  • 快速访问类的成员数据和函数
  • 以别名的方式向函数传递参数

三 堆和栈

3.1 概述

  • 一般来说,程序就是与数据打交道,在执行某一功能的时候,将该功能所需的数据加载到内存中,然后在执行完毕的时候释放掉该内存
  • 数据在内存中的存放共分以下几个形式

3.2 数据在内存中的存放形式

  1. 栈区(stack)—由编译器自动分配并且释放,该区域一般存放函数的参数值,局部变量的值等
  2. 堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收
  3. 寄存器区—用来保存栈顶指针和指令指针
  4. 全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放
  5. 文字常量区—常量字符串就是放在这里的,程序结束后由系统释放
  6. 程序代码区—存放函数体的二进制代码

3.3 总结

  • 函数参数和局部变量存放在栈中,当函数运行结束并且返回时,所有的局部变量和参数就被系统自动清除掉了,而且由于全局变量被所有的类成员和函数所共享,所以它的值很容易被修改。使用堆可以一举解决这两个问题
  • 堆是采用匿名的方式来保存数据的
  • 你只能通过指针才能访问到这些匿名的数据
  • 因此它的安全性是最好的
  • 同时,由于堆区中的内存是由程序员来分配和释放的,所以,它的自由度也是最高的

四 栈和堆的不同

4.1 概述

很多时候我们都把栈和堆放在一起说,比如堆栈,其实他们是不同的,至于为什么把他们混合在一起说,这是历史问题,这里就不深究了。

4.2 栈和堆的不同

4.2.1 内存申请方式上的不同

  • 栈:由系统自动分配,例如我们在函数中声明一个局部变量int a,那么系统就会自动在栈中为变量a开辟空间。
  • 堆:需要程序员自己申请,因此也需要指明变量的大小

4.2.2 系统响应的不同

  • 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将提示overflow,也就是栈溢出
  • 堆:系统收到程序申请空间的要求后,会遍历一个操作系统用于记录内存空闲地址的链表,当找到一个空间大于所申请空间的堆结点后,就会将结点从记录内存空间地址的链表中删除。并将该结点的内存分配给程序,然后在这块内存区域的首地址处记录分配的大小,这样我们在使用delete来释放内存的时候,delete才能正确地识别并删除该内存区域的所有变量。另外,我们申请的内存空间与堆结点上的内存空间不一定相等,这时系统就会自动将堆结点上多出来的那一部分内存空间回收到空间链表中。

4.2.3 空间大小的不同

  • 栈:在windows下,栈是一块连续的内存的区域,它的大小是2M,也有的说是1M,总之,该值是一个编译时就确定的常数。是由系统预先根据栈顶的地址和栈的最大容量定义好的。假如你的数据申请的内存空间超过了栈的空间,那么就会提示overflow。因此,别指望栈能存储比较大的数据。
  • 堆:堆是不连续的内存区域。各块区域由链表将它们串联起来,关于链表的知识将在后面的章节讲解。这里只需要知道链表将各个不连续的内存区域连接起来,这些串联起来的内存空间叫做堆,它的上限是由系统中有效的虚拟内存来定的。因此获得的空间比较大,而且获得空间的方式比较灵活。

4.2.4 执行效率的不同

  • 栈:栈由系统自动分配,因此速度较快。但是程序员不能对其进行操作
  • 堆:堆是由程序员分配的内存,一般速度比较慢,而且容易产生内存碎片,不过使用起来很方便

4.2.5 执行函数的不同

4.2.5.1 栈
概念
  • 在函数调用时,第一个进栈的是被调用函数下一行的内存地址。其次是函数的参数,假如参数多余一个,那么次序是由右向左。最后才是函数的局部变量。
  • 由于栈的先进后出原则,函数结束时正好与其相反,首先是局部变量先出栈,然后是参数,次序是从左向右,这是所有的变量都已出栈,指针自然地指到第一个进栈的那行内存地址,也就是被调用函数的下一行内存地址。程序根据该地址跳转到被调用函数的下一行自动执行。
  • 至于栈内数据为什么要先进后出,这个原理可以用叠盘子来做比喻,你讲一个盘子放在另一个盘子之上依次将它们叠高,取走的时候必然是从最上面的盘子开始,你不可能直接抽出最下面的盘子,因为傻子也会知道那样做会摔碎所有的盘子。
示例
  • 函数
  • 变量进入栈
  • 变量退出栈
结论
  • 我们看到栈的生长是向着内存地址减小的方向增长的,即:假如再有数据进入栈,那么就会放置到101这个位置,数据不断地增长,那么内存地址就会不断减少
  • 另外,由于栈的先进后出原则,所以它永远都不可能产生内存碎片,因为在上面的盘子没有拿完之前,下面的盘子是不可能抽出的。它们排列的如此有序,弹出时也非常有序,碎片想要产生也是非常艰难的。
4.2.5.2 堆
  • 堆是一大堆不连续的内存区域,在系统中由链表将它们串接起来,因此在使用的时候必须由程序员来安排。
  • 它的机制是很复杂的,有时候为了分配一块合适的内存,程序员需要按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有满足条件的空间,那么就需要想系统发出申请增加一部分内存空间,这样就才有机会分配到足够大小的内存,然后将计算后的数值返回。
  • 显然,堆的运行效率比栈要低的多,而且也容易产生碎片。但是好处是堆可以存储相当大的数据,并且一些细节也可以由程序员来安排

五 总结

  • 从上面可以看出,栈的内存小,但是效率高,不过存储的数据只在函数内有效,超出函数就消失了。堆的可存储空间非常大,但是容易产生内存碎片,效率也较低,好处是灵活性比较强。
  • 比如说我们需要创建一个对象,能够被多个函数所访问,但是又不想使其成为全局的,那么这个时候创建一个堆对象无疑是良好的选择
  • 由于堆和栈各有优缺点,因此好多时候我们是将堆和栈结合使用的,比如在存储一些较大数据的时候,我们将数据存放到堆中,却将指向该数据的指针放到栈中。这样可以有效地提高程序的执行速度,避免一些不该有的碎片。不过,一般来说,假如不是特大的数据,我们都是使用栈,比如:函数调用过程中的参数,返回地址,和局部变量都存放到栈中。这样可以大大加快程序的运行速度。