对栈的相关操作详解及堆区、栈区的理解

栈的定义和分类 

        栈是我们线性结构中的一种常见应用。在函数调用、内存分配等也常常跟栈打交道,栈可以简单的理解为是一种可以实现“先进后出”的存储结构。栈又分为静态栈和动态栈。静态栈以类似于数组方式存放,而动态栈以类似于链表的方式存放。

栈区(Stack)和堆区(heap)

         栈区主要用于存放局部变量、定义的形参,在定义时局部变量或形参时由系统自动分配,在函数结束时由系统自动回收存储单元。

         堆区主要通过new(常用于C++、Java中),malloc(用于C中)等动态开辟的存储块,函数结束时需要我们通过delete(c++中)、free(c中)手动释放

 举例说明:

void f(int n)
{
	int m;
	char ch;
	double *q=(double *)malloc(200);
}

其中的n,m,ch,q由栈区分配(由操作系统帮我们自动分配), 200由堆区分配(需要我们手动开辟一块存储单元)。
栈和堆表示的是分配数据的一种方式。静态的或局部变量它们是以压栈和出栈的方式分配内存的(这个就叫栈区),而动态内存它们是以一种叫堆排序的方式分配的内存(这个叫堆区)。笼统的讲:凡是静态分配的全部在栈里面分配, 凡是动态分配的全部在堆里面分配。

 

对栈进行操作的思路

         先讲下思路:我们知道链表主要通过头指针(指向头结点的地址)来对链表中的其它结点进行操作,而头结点本身并没有实际含义(既没有存放有效节点,也没有存放有效节点的个数),但通过它却可以方便我们对链表进行相关操作,如链表的遍历:

void TraverseList(PNODE pHead)
{
 PNODE p=pHead->pNext;//将头结点的指针域指向首结点(链表的第一个有效结点),并赋给指针变量p,此时p指向首节点的地址
 printf("遍历整个链表:");
 while(NULL!=p)//当p指向首节点的地址不为NULL(即链表不为空),循环输入个结点的值
 {
  //当p指向尾结点的地址时,可输出尾结点的值
  //但此时尾结点指针域为NULL,将跳出while循环
  printf("%d  ",p->data);
  p=p->pNext;//将p指向下一个结点的地址赋给p指针
 }
 printf("\n");

在这个对链表进行遍历的函数中,我们可以看到我们只需要一个头指针,就可以遍历整个链表。

同理,我们可以通过初始化造出一个空栈,产生头结点,进而对栈进行相关操作。

typedef struct Node //定义结点的数据类型
{
 int data;//数据域
 struct Node *pNext;//指针域
}NODE,*PNODE;
typedef struct Stack //定义一个Stack结构类型,它含有两个指针成员
{
 PNODE pTop;//指向栈顶元素
 PNODE pBottom;//指向栈底元素的下一个没有实际含义的元素
}STACK,*PSTACK;

伪算法:

       1.造空栈

        

             void init(PSTACK pS)

             {

                       动态产生一个头结点, 并让pTop指向该节点;

                       让pTop和pBottom都指向头结点 ;

                        再让成员pBottom或pTop所指向头结点的指针域为空

               }

          2.压栈(也叫进栈)

          void push(PSTACK pS,int val)

          {

                  创建一个新临时结点;

                  把val赋给该节点的数据域;

                  让该新节点的指针域指向栈顶;

                  最后让该新节点成为新栈顶;                    

             }

           3.出栈

            bool pop(PSTACK pS)

            {

                  先判断栈是否为空;

                  临时保存一份栈顶结点;

                 让栈顶指针指向下一个结点的地址;

                 释放预先保存的栈顶结点所占的内存;

            }

 

完整实例说明:

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct Node //定义结点的数据类型
{
	int data;//数据域
	struct Node *pNext;//指针域
}NODE,*PNODE;
typedef struct Stack
{
	PNODE pTop;//指向栈顶元素
	PNODE pBottom;//指向栈底元素的下一个没有实际含义的元素(头结点)
}STACK,*PSTACK;
void init(PSTACK);//产生一个没有实际含义的头结点,并让pTop和pBottom都指向该头结点
void push(PSTACK,int);//压栈(即进栈)
void traverse(PSTACK);//遍历
bool pop(PSTACK);//出栈
int length(PSTACK);//求栈的长度
bool clear(PSTACK);//清空栈

int main()
{
    STACK S;//STACK等价于struct Stack,为变量S分配内存,其中变量S含两个成员pTop,pBottom
	init(&S);//目的是造出一个空栈,产生一个头结点,以便对栈就行操作
	push(&S,1);
	push(&S,2);
	push(&S,3);
    traverse(&S);
	pop(&S);
	traverse(&S);
	printf("栈的长度为:%d\n",length(&S));
    clear(&S);
	printf("清空后:");
	traverse(&S);
	push(&S,7);
	push(&S,5);
	push(&S,9);
	printf("进栈后:");
    traverse(&S);
	return 0;
}

void init(PSTACK pS)//造空栈
{
	pS->pTop=(PNODE)malloc(sizeof(NODE));//产生头结点,并让pTop指向该头结点
	if(NULL==pS->pTop)
	{
		printf("动态内存分配失败!\n");
		exit(-1);//终止程序
	}
	pS->pBottom=pS->pTop; //让pTop和pBottom都指向头结点
    pS->pBottom->pNext=NULL;
}
void push(PSTACK pS,int val)
{
	PNODE pNew=(PNODE)malloc(sizeof(NODE));//创建新临时节点
    if(NULL==pNew)
	{
		printf("动态内存分配失败!\n");
		exit(-1);//终止程序
	}
	pNew->data=val;
	pNew->pNext=pS->pTop;
    pS->pTop=pNew;
    return; 
}

void traverse(PSTACK pS)
{
	PNODE p=pS->pTop;
	while(p!=pS->pBottom)
	{
		printf("%d  ",p->data);
		p=p->pNext;
	}
	printf("\n");
}
bool pop(PSTACK pS)
{
	if(pS->pBottom==pS->pTop)
	{
		printf("栈为空,无法进行出栈操作!\n");
		return false;
	}
	PNODE p=pS->pTop;
	printf("出栈元素为:%d\n",p->data);
	pS->pTop=p->pNext;
    free(p);
	p=NULL;
	return true;
}
int length(PSTACK pS)
{
	int len=0;
	if(pS->pBottom==pS->pTop)
	{
		printf("栈为空! ");
		return 0;
	}
	PNODE p=pS->pTop;
	while(p!=pS->pBottom)
	{
		++len;
		p=p->pNext;
	}
	return len;
}
bool clear(PSTACK pS)
{
	if(pS->pBottom==pS->pTop)
	{
		printf("栈为空,清空失败! \n");
		return false;
	}
	PNODE p=pS->pTop,q;
	while(p!=pS->pBottom)
    {
		q=p->pNext;
		pS->pTop=q;
		free(p);
		p=q;
	}
	pS->pTop=pS->pBottom;
    return true; 
}


注意

        1.free(p); 删除的是p指向的那个结点所占的内存,而不是删除p本身所占的内存;释放结点目的主要是为了防止内存泄露(这不像Java,有垃圾回收机制---由垃圾回收器自动帮你回收)。

         2.pBottom始终指向的是栈底元素的下一个没有实际含义的元素(头结点),头结点是我们人为造出来的,并不存放有效数据;添加struct Stack(含两个成员pTop、pBottom)这个结构体的目的主要是为了方便我们对栈进行操作。

        3.初始化时,让pTop和pBottom都指向头结点的目的:一是让它们初始化时所指向的内存地址相同,在整个程序运行过程中,让pBottom始终指向pTop的初始地址,也就是头结点的地址。这样就可以通过pS->pBottom==pS->pTop?是否成立来判断pTop是否指向初始化时原头结点(不存放有效数据的,为方便对栈进行操作的附加结点)的地址,即可以判断栈是否为空,因为如果栈含有有效元素的话,那么pTop必定存放的是新元素的地址。

         4.结构体Stack成员pBottom的好处主要有:一是在初始化造空栈时,指向头结点;二是可以在 pS->pBottom==pS->pTop成立时判断栈为空。 结构体Stack成员pTop的好处主要有:一是和pBottom一样,在初始化造空栈时,指向头结点;二是通过pTop来对栈中有效元素进行压栈、出栈、遍历等相关操作。

 

结束语

        有关对线性结构中的栈操作今天就写到这了,明天开始学习线性结构常见应用中的队列。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值