堆,栈,队列

2 篇文章 0 订阅
1 篇文章 0 订阅
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

以上是从数据结构角度来看,从操作系统角度来看,所有的数据结构都是对虚拟内存的操作,堆是堆,栈是栈,栈指的是C语言函数所使用的自动有函数回收的虚拟内存空间,而堆则有操作系统堆管理器来管理的那部分虚拟内存,从C语言角度来看,使用malloc函数动态分配的内存,就是堆内存。
 

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种: (1)静态链接在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。 (2)动态链接在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。 对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。 经过上述五个过程,C源程序就最终被转换成可执行文件了。缺省情况下这个可执行文件的名字被命名为a.out。

 

 

栈,堆,静态区,是内存开辟的三个专属区,C语言的内存分配也就只有这三种方式
 
1.内存在栈上创建(栈结构)
如函数里定义的变量int i; char str[80],还有保存函数的所有信息的内存都是在栈上创建的
这块内存是连续的,就好比是一个数组,所以你在内存分配的时候,会发现变量地址是连续的
 
2.内存在堆上创建(链表结构)
这块内存是有很多内存块组成,很像鞭炮一样串在一根绳子上,但这些内存块的大小不一样,存储在链表结构中的结点中,当你用malloc动态申请是,编译器会根据你内存块的大小从表头一次检索,直到链表中的内存块大于等于你所申请的内存大小时,返回该快内存,如果链表上的内存块大于你所申请的内存时,会将多余内存回收到链表结构,这也就是为什么动态申请内存容易造成内存碎片的产生原因。所以分配内存时你会发现他们的地址不连续
3.内存在静态区创建   如全局变量,static变量
这块内存也是连续的,也像一个数组,但它跟栈上创建内存唯一的区别是,内存作用时间不一样,静区内存作用时间是整个“程序”运行时间,栈上内存作用时间是整个“函数”的运行时间,注意“程序”和“函数”的区别
而堆内存作用时间范围是0到整个“程序”运行时间,如果你要在小于整个“程序”运行时间内释放这块内存的话,就要使用free,所以是手动申请手动释放,你自己可以控制,但是写代码的好习惯习惯是程序中有几个malloc就有几个free,这样可以防止内存泄露

 

 

 

 

int n,array[n]; scanf(%d,&n); 在Turbo C中,不允许出现动态数组。
那么如果必须需要这样时,就只能使用链表了。
一、堆
 
堆是一种动态存储结构,实际上就是数据段中的自由存储区,它是C语言中使用的一种名称,常常用于动态数据的存储分配。堆中存入一数据,总是以2字节的整数倍进行分配,地址向增加方向变动。堆可以不断进行分配直到没有堆空间为止,也可以随时进行释放、再分配,不存在次序问题。
 
所谓动态数组是指在程序运行期间确定其大小的,如常用到的动态数组,它们是在程序执行过程中动态进行变化的,即在程序开始部分没有说明大小,只有在程序运行期间用堆的分配函数为其分配存储空间,分配的大小可根据需要而定,这些数据使用过后,可释放它们占用的堆空间,并可进行再分配。
 
堆和栈在使用时相向生长,栈向上生长,即向小地址方向生长,而堆向下增长,即向大地址方向,其间剩余部分是自由空间。使用过程中要防止增长过度而导致覆盖。
 
一般的程序我们都是使用小内存模式,它的内存分配如下:
 
________________ 
| 
代码段
|
————————
| 
| 
数据段
| 
|
————————
| 
| BSS段
| 
|
————————
| 
| 
| 
|----------------
| 
自由空间
 
|----------------
| 
| 
|
————————
| 
| 
远堆
| 
|----------------
| 
|________________
| 
自由空间
 
在堆和栈之间、以及远堆地址的后面都是自由空间,总共是64K。
 
堆管理函数:
1.
得到堆和栈之间的自由空间大小的函数小数据内存模式:unsigned coreleft(void); 大数据内存模式:unsigned long coreleft(void); 对于远堆,可以用farcoreleft()函数。
2.
分配一个堆空间函数
void malloc (unsigned size); 该函数将分配一个大小为size字节的堆空间,并返回一个指向这个空间的指针。由于这个指针是void型的,因此当将它赋给其他类型的指针时,必须对该指针进行强制类型转换。例如info是一个结构类型指针,即:
struct addr *info; 将由malloc()函数返回的指针赋给info时,必须进行类型转换:
info=(struct addr *)malloc (sizeof(record)); malloc()函数所分配的堆空间将不进行初始化。在调用malloc()函数时,若当时没有可用的内存空间,该函数便返回一个NULL指针。
3.
分配一个堆空间,其大小为能容纳几个元素,没有元素长度为size的函数
void calloc(unsigned n,unsigned size); 该函数将分配一个容量为n*size大小的堆空间,并用0初始化分配的空间。该函数将返回一个指向分配空间的指针,没有空间可用时,则返回一个NULL指针。
4.
重新分配堆空间函数void *realloc(void *ptr,unsigned newsize); 该函数将对由ptr指向的堆空间重新分配,大小变为newsize。
5.
释放堆空间函数void free(void *ptr); 
下面举一个关于堆和栈的综合例子:
void push(int); 
int pop(); 
int *pi,*tos; 
 
main() 
{ 
int v; 
pi=(int *)malloc(50*sizeof(int)); 
if(!pi) 
{ 
printf(allocation failure\n); 
exit(0); 
} 
tos=pi; 
do 
{ 
printf(please input value,push it;enter 0 then pop;(enter -1 then stop)\n); 
scanf(%d,&v); 
if(v!=0) push(v); 
else printf(pop this is it %d\n,pop()); 
} 
while(v!=-1); 
} 
 
void push(int i) 
{ 
pi++; 
if(pi==(tos+50)) 
{ 
printf(stack overflow\n); 
exit(0); 
} 
*pi=i; 
} 
 
int pop() 
{ 
if(pi==tos) 
{ 
printf(stack underflow\n); 
exit(0); 
} 
pi--; 
return *(pi+1); 
} 
 
程序分配100字节的堆空间,转换成int型赋给pi,当pi为NULL时,表示没有可用的空间了,则显示allocation failure。输入一个整数,压入栈中,当超过50时,则显示stack overflow.当输入0时,则把栈中的数据弹出。这个程序也演示了栈的后进先出的特点。
 
二、链表
堆是用来存储动态数据的。动态数据最典型的例子就是链表。
形象的说:将若干个数据项按一定的原则前后链接起来,没有数据项都有一个指向下一个数据的指针,则这些数据项靠指针链成一个表,最后的一个数据没有指针(指针为NULL),这就是链表。可以看出链表放在存储器中,并不一定象数组一样,连续存放,也可以分开存放。由于链的各节点均带有指向下一个节点的地址,因而要找到某个节点,必须要找到上一个节点,如此类推,则可由第一个节点出发找到目的点。链表在数据库建立和管理中用得比较普遍。
链表中的每个节点都具有相同的结构类型,它们是由两部分组成,即数据部分(它们包含一些有用的信息),另一部分就是链的指针。下面就定义一个通信链节点的数据结构:
struct address 
{ 
char name[30]; 
char street[40]; 
char city[20]; 
char state[10]; 
char zip[6]; 
struct address *next; /*pointer to next entry*/ 
}list_entry; 
该结构中前五个成员是该节点的信息部分,最后一个成员是指向同一个结构类型的指针。即next又指向一个同样结构类型的节点。
 
1.
建立链表
建立链表时,首先要将第一个节点的内容存入堆中,为此要将堆中能存入该节点内容的内存区域首地址赋给一个指针。我们可以用malloc()函数来分配内存区域。如info是一个指针:
info=(struct address *)malloc(sizeof(list_entry)); 当第一个节点存入有info指出的内存区后,再执行该函数,便得到狭义个节点的存储地址info,此时将该info赋给上一个节点的next,并将该节点内容存入info指出的内存区,这样两个节点就链接起来了。此过程反复多次,就可不断的将节点加入链表的尾端。
#include stdlib.h 
#include alloc.h 
#include stdio.h 
#include string.h 
 
struct address 
{ 
char name[30]; 
char street[40]; 
char city[20]; 
char state[10]; 
char zip[6]; 
struct address *next; 
}list_entry; 
 
void inputs(char *,char *,int); 
void dls_store(struct address*); 
 
main() 
{ 
struct address *info; 
int i; 
for(i=0;i<5;i++) 
{ 
info=(struct address *)malloc(sizeof(list_entry)); 
inputs(enter name:,info->name,30); 
inputs(enter street:,info->street,40); 
inputs(enter city:,info->city,20); 
inputs(enter state:,info->state,10); 
inputs(enter zip:,info->zip,6); 
dls_store(info); 
} 
} 
void inputs(char *prompt,char *s,int count) 
{ 
char p[255]; 
do 
{ 
printf(prompt); 
gets(p); 
if(strlen(p)>count) printf(\n too long \n); 
} 
while(strlen(p)>count); 
strcpy(s,p); 
} 
 
void dls_store(struct address *in) 
{ 
static struct address *last=NULL; 
if(!last) last=in; 
else last->next=in; 
in->next=NULL; 
last=in; 
} 
 
inputs()
函数比较简单,就不说明了。
dls_store()函数是将输入的节点地址写到上一个节点的next指针项。其中定义的结构指针last是一个静态变量,初始值为NULL,这意味着在编译时将为该变量分配一个固定的存储空间以存放其值。因初始值为NULL,这样在第一次调用该函数时,由于它代表一个空指针,因而把由malloc()分配的第一个节点地址赋给它,使last指向该节点,第二次调用时,静态变量last已指向第一个节点地址。如此反复调用,便建立起了n次调用产生的
n个节点的链了(本题n=5)。
2.
链数据的插入和删除
对于一个已排序好的链表
(
假设是生序
)
,现在想插入一个数据进去,可能有三种情况:
(1).
比首项数据还小,即插入的数据作为首项出现:
 
这种情况我们的处理方法是:把该数据作为第一项,指针指向原先的首项即可。设原先首项为top,待插入的数据为in,则:
 in->next=top; 即可让该数据作为链表的头。
(2).比最后一项大,即插入的数据作为最后一项出现:
 这也很好办,设原先最后一项为old,则:
old->next=in; 
in->next=NULL; 
(3).作为中间某一项出现:前面是old,后面是top,则: 
old->next=in; 
in->next=top; 
如果想删除一个数据,也可能是出现在开头,中间和结尾。
例如想删除in这个数据,它原先的前面是old,后面是top,即原先的链表是这样:
old->next=in; 
in->next=top; 
现在删除in,只需把old指向top即可: 
old->next=top->next; 
/*
删除节点函数
*/ 
void delete(struct address *info,struct address *old) 
{ 
if(info) 
{ 
if(info==start) start=info->next; /*
删除的是第一个节点
*/ 
else 
{ 
old->next=info->next; /*
被删除节点前的指针指向下一个节点
*/ 
last=old; /*
若节点是链表尾,则该节点前的节点指针指向
NULL*/ 
} 
free(info); /*
释放删除节点占用空间
*/ 
} 
} 
 
/*
查找链表中是否有该数据
*/ 
struct address *search(struct address *top,char *n) 
{ 
while(top) 
{ 
if(!strcmp(n,top->name)) return top; /*
找到要删除的节点指针
*/ 
top=top->next; /*
继续找
*/ 
} 
return NULL; /*
没有找到
*/ 
} 
 
/*
链表的输出
*/ 
void display(struct address *top) 
{ 
while(top) 
{ 
printf(top->name); 
top=top->next; 
} 
} 
链表问题比较复杂,但又是很重要的概念。上面说的输入,查找,删除,插入等功能一定要理解,可以参考别的一些资料看看。
上面说的单链表,但是单链表有一个缺点,就是无法反向操作,当某一个链因破坏而断裂,则整个链就被破坏而无法恢复。双链表可以弥补这个缺点,所谓双链表是指每个节点有两个指针项,一个指针指向其前面的节点,而另一个指针指向后面的节点。关于双链表的使用相对要复杂一些,这里就不介绍了,可以找其他一些资料看看。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值