欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

栈、堆、队列

程序员文章站 2022-06-07 21:45:40
...

1 数据结构–栈和队列


1. 栈

1.1 栈的定义 
栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。如下图所示: 
栈、堆、队列 
1.2 栈的特点:后进先出的特点(Last in first out);

1.3 栈的声明: 
stack< int > stk;

1.4 栈的操作

    s.empty()               如果栈为空返回true,否则返回false  
    s.size()                返回栈中元素的个数  
    s.pop()                 删除栈顶元素但不返回其值  
    s.top()                 返回栈顶的元素,但不删除该元素  
    s.push()                在栈顶压入新元素  

1.5 栈的补充知识 
栈是线性表,因此线性表的存储结构对栈也是适用的。通常栈有顺序栈和链栈两种存储结构。 
1)顺序栈 :有上溢和下溢的概念。顺序栈可以比作是一个盒子里装了很多书,第一本拿出的书就是你最后放的书。当书本放不下了,这时就是上溢。上溢就是栈顶指针指出栈的外面。下溢本身就表示空栈。 
2)链栈没有上溢,并且链栈没有头结点。由于栈的特点,后进先出,所以所有操作都是在头结点完成的。 
栈、堆、队列

2 队列

2.1 队列定义 
队列(Queue)是一种运算受限的线性表,它的与栈不同的地方在于,是两头都受到限制,插入只能在表的一端进行(只进不出),此处称之为队头(Front);而删除只能在表的另一端进行(只出不进),这一端称之为队尾(rear)。

2.2 队列的特点:先进先出(排队原则)

2.3 队列的声明 
queue < int> q;

2.4 队列的操作

    q.empty()               如果队列为空返回true,否则返回false  
    q.size()                返回队列中元素的个数  
    q.pop()                 删除队列首元素但不返回其值  
    q.front()               返回队首元素的值,但不删除该元素  
    q.push()                在队尾压入新元素  
    q.back()                返回队列尾元素的值,但不删除该元素  

2.5 队列的补充知识 
队列也存在顺序存储和链式存储两种存储结构,前者称顺序队列,后者为链队。 
1)顺序队列: 在一段连续的存储空间,由一个队列头指针和一个尾指针表示这个队列,当头指针和尾指针指向同一个位置是,队列为空。当出队操作时,头指针向空间尾部增加一个位置;入队时,尾指针向前增加一个位置。 
顺序队列示意图如下: 
栈、堆、队列

记住:元素进队,尾指针后移,元素出队,头指针后移。


2 程序的内存分配

2.1 序言 
一个由C/C++编译的程序占用的内存分为以下几个部分 
1)栈区(stack)– 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构的栈。 
2)堆区(heap)–堆区的数据一般由程序员分配释放。 
3)全局区(静态区)(static)–全局变量和静态变量的存储是放在一块区域的。初始化的全局变量和静态变量放在一块区域,未初始化的全局变量和静态变量放在相邻的区域。–程序结束后由系统自动释放。 
4)文字常量区–常量字符串等常量放在这里。程序结束释放。 
5)程序代码区–存放函数体的二进制代码。

具体分析如下: 
栈、堆、队列 
由上图可以看出,此可执行程序在存储时分为代码区(text)、数据区(data)和未初始化数据区(bss)。 
(1)代码区(text segment),存放CPU执行的机器指令(machine instructions)。通常,代码区是可共享的(即另外的执行程序可以调用它),因为对于频繁被执行的程序,只需要在内存中有一份代码即可。

(2)全局初始化数据区/静态数据区(initialized data segment/data segment)。该区包含了在程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。 
例如,一个不在任何函数内的声明(全局数据),使得变量maxcount根据其初始值被存储到初始化数据区中: 
栈、堆、队列 
声明一个静态数据,如果是在任何函数体外声明,则表示其为一个全局静态变量,如果在函数体内(局部),则表示其为一个局部静态变量。另外,如果在函数名前加上static,则表示此函数只能在当前文件中被调用。 
栈、堆、队列

(3)未初始化数据区。亦称BSS区(uninitialized data segment),存入的是全局未初始化变量。BSS这个叫法是根据一个早期的汇编运算符而来,这个汇编运算符标志着一个块的开始。BSS区的数据在程序开始执行之前被内核初始化为0或者空指针(NULL)。例如一个不在任何函数内的声明: 
栈、堆、队列

下面展示C程序的内存布局: 
下图所示为可执行代码存储时结构和运行时结构的对照图。一个正在运行着的C编译程序占用的内存分为代码区、初始化数据区、未初始化数据区、堆区和栈区5个部分。 
栈、堆、队列

(1)代码区(text segment)。代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现。

(2)*全局初始化数据区/静态数据区(Data Segment)。只初始化一次*。

(3)未初始化数据区(BSS)。在运行时改变其值。

(4)栈区(stack)。由编译器自动分配释放,存放函数的参数值、局部变量的值等。每当一个函数被调用,该函数返回地址和一些关于调用的信息,比如某些寄存器的内容,被存储到栈区。

(5)堆区(heap)。用于动态内存分配。堆在内存中位于bss区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS回收。

记忆方式: 
堆区和栈区是最基础的两种内存分配结构,堆区是程序员手动申请释放,如malloc;栈区是系统自动释放; 
还有三种内存结构比较特殊。分别是代码区和未初始化全局数据区(BSS)和全局初始化的数据区(还包括静态数据区)

分成这么多个区域,主要基于以下考虑: 
一个进程在运行过程中,代码是根据流程依次执行的,只需要访问一次,当然跳转和递归有可能使代码执行多次,而数据一般都需要访问多次,因此单独开辟空间以方便访问和节约空间。 
临时数据及需要再次使用的代码在运行时放入栈区中,生命周期短。 
全局数据和静态数据有可能在整个程序执行过程中都需要访问,因此单独存储管理。 
堆区由用户*分配,以便管理。 
2.2 举例描述

int a = 0; //a在全局已初始化数据区 
char *p1; //p1在BSS区(未初始化全局变量) 
main() 

int b; //b在栈区 
char s[] = “abc”; //s为数组变量,存储在栈区, 
//”abc”为字符串常量,存储在已初始化数据区 
char *p1,p2; //p1、p2在栈区 
char *p3 = “123456”; //123456\0在已初始化数据区,p3在栈区 
static int c =0; //C为全局(静态)数据,存在于已初始化数据区 
//另外,静态数据会自动初始化 
p1 = (char *)malloc(10);//分配得来的10个字节的区域在堆区 
p2 = (char *)malloc(20);//分配得来的20个字节的区域在堆区 
free(p1); 
free(p2); 
}

2.3 栈和堆申请方式的区别 
栈:由系统自动分配 
ep: int b ; //系统自动在栈中为b开辟空间 
堆:程序员自己申请空间,指明大小 
ep: p1 =(char * )malloc (10); //c中申请空间 
p2=new char[10]; //C++中申请空间 
注意:p1,p2本身在栈中,只是申请的空间在堆中。

2.4 申请后系统的响应 
栈:栈的剩余空间大于申请空间,系统提供内存,反之提示栈溢出。 
堆:程序申请,寻找第一个空间大于申请空间的堆结点,删除空闲结点并将该结点分配给程序。代码中的delete语句才能正确释放本内存空间。

2.5 申请大小的限制 
栈:栈顶的地址和栈的最大空间是系统预先规定好的,如果申请空间超过栈的剩余空间,将提示overflow。因此,栈空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。

2.6 申请效率的比较: 
栈:由系统自动分配,速度较快。 
堆:由new 分配的内存,速度慢,易产生碎片,但是方便。

2.7 堆和栈中的存储内容 
栈:第一个进栈的是主函数中下一条指令,然后是函数各参数,然后是局部变量。注意静态变量不入栈。函数结束后,局部变量先出栈,然后是参数,最后是地址。 
堆:由程序员安排。

2.8 小结 
打比喻总结堆和栈在内存存储方面的区别: 
栈就像区饭店吃饭,只点菜(申请),吃菜(使用)和付钱。其余的工作都是由系统完成,速度快。 
堆就是自己动手做饭,麻烦但*。

[附]:

堆栈向上增长和向下增长

问题解决:

栈、堆、队列

堆栈增长演示:

栈、堆、队列

    上图显示了堆栈 向上增长和向下增长的区别。

    如果堆栈是向下增长,也就是从高地址向低地址增长,那么在任务刚开始创建后,堆栈是空的。如图中例子,栈顶在为TaskStk[0][511],栈底为在TaskStk[0][0]。相反,如果堆栈是向上增长的,栈顶在为TaskStk[0][0],栈底为在TaskStk[0][511]。

那么,如果我们向堆栈中压入数据,例如推入0x0012ff78后,堆栈变化如下图:

栈、堆、队列

            如图,压栈后,若堆栈向下增长,在原来栈顶位置插入数据0x0012ff78,然后栈顶位置向低地址方向移4个字节,指向TaskStk[0][510]。若堆栈向上增长,在原来栈顶位置压入0x0012ff78,栈顶变为TaskStk[0][1]。

注意:

     堆栈中向上增长----低地址向高地址方向增长。

向下增长----高地址向低地址方向增长。

    堆栈中数据插入、删除遵循 后进先出的规则,因此插入数据向上增长和向下增长需要注意插入的方向