C语言入门(二十二)堆和链表

堆和链表 

我们经常在题目中有要求,输入一个整数,然后以这个整数作为数组的元素个数,下面的程序代码是错误的。

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;
}
}

链表问题比较复杂,但又是很重要的概念。上面说的输入,查找,删除,插入等功能一定要理解,可以参考别的一些资料看看。

上面说的单链表,但是单链表有一个缺点,就是无法反向操作,当某一个链因破坏而断裂,则整个链就被破坏而无法恢复。双链表可以弥补这个缺点,所谓双链表是指每个节点有两个指针项,一个指针指向其前面的节点,而另一个指针指向后面的节点。关于双链表的使用相对要复杂一些,这里就不介绍了,可以找其他一些资料看看。

时间: 2024-08-04 22:21:32

C语言入门(二十二)堆和链表的相关文章

Python3快速入门(十二)——NumPy

Python3快速入门(十二)--NumPy 一.NumPy简介 1.NumPy简介 NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,同时对数组运算提供了大量的数学函数库.Numpy 是一个运行速度非常快的数学库,内部解除了CPython的GIL(全局解释器锁),运行效率极好,主要用于数组计算,是大量机器学习框架的基础库,NumPy主要包括如下:(1)强大的N维数组对象 ndarray(2)广播功能函数(3)整合 C/C++/

【Git入门之十二】DIY Git

[Git入门之十二]DIY Git - JackyStudio - 博客频道 - CSDN.NET Git的配置是很有学问的,如果弄懂它,将对你帮助很大. 1.用户配置 这是全局的. ? [cpp]?view plaincopy ? #设置用户名?? $Snbsp;git?config?--global?user.name?"Jacky"?? ?? #设置邮箱?? $Snbsp;[email protected]?? ? 2.设置默认编辑器 在需要输入文本信息时调用,比如之前的reba

Swift入门(十二)——利用Extension添加逆序输出字符串方法

Swift好像没有自带逆序输出字符串的方法,于是决定通过拓展(Extension)给String类添加一个逆序输出字符串的reverse方法. 首先新建一个Swift文件,命名规则不太清楚,于是暂且模仿OC叫做String+Operation吧,然后实现我们需要拓展的方法.下面先贴上代码,然后解释一下这段代码. //String+Operation.swifft import Foundation //逆序输出swift中的字符串 extension String{ func Reverse()

UWP入门(十二)--数据绑定用法

原文:UWP入门(十二)--数据绑定用法 主要几个元素: Template DataTemplate ItemSource 数据绑定是一个数据提取的方法,能使数据和UI上的控件紧密相连,下面的Demo是这样的: 有许多书的集合,书 类中有图片.标题.作者和ID,把它成现在GridView 控件上,每次点击GridView 的时候动态显示书本信息 github 代码 效果图: 原理图: 1. Template 为GridView 创建一个Template,决定每个独立的图书对象如何呈现在屏幕上 <

Project Server 2013新手入门 (十二)特定工作组

很多时候我们需要那种分层次的组织结构来个部门分配任务,然后部门领导再给员工分配任务,这里就用到了了一个功能特定工作组.而创建一个全新的工作组的话,就要用到查阅表格. 1.新建查阅表格 1)在PWA中,选择"服务器设置",在"企业数据"下,选择"企业自定义域和查阅表格". 2)在"企业自定义域和查阅表格"页面,选择"自定义域的查阅表格"(如果企业自定义域比较多可能得向下翻阅滚动条才能找到) 3)在出现的页面中

C语言栈队列实现二-十/二-八进制转换

C语言栈队列实现二-十/二-八进制转换 2015-04-05 Lover雪儿 1 //利用栈来求取二进制数的十进制与八进制的结果 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <math.h> 5 6 #define STACK_INIT_SIZE 20 //初始栈大小 7 #define STACK_INCREMENT 10 //扩充栈时每次增加的内存 8 9 typedef char ElemType;

苹果新的编程语言 Swift 语言进阶(十二)--选项链

选项链是使用选项来查询和调用其属性.方法或下标的一个过程,如果选项包含一个值,则属性.方法.下标的查询和调用成功,否则,调用返回nil. 选项链能用在任何类型的选项来检查对其一个属性.方法.下标的查询和调用是否成功. 选项链可以作为强制展开的替代方式使用,但选项链的使用更加安全,不会触发一个运行时错误. 在调用一个选项的属性.方法或下标方法时,通过在该选项值的后面放置一个(?)标记来规定一个选项链.这与在选项值后放置一个(!) 来强制展开选项的值非常相似.主要的不同是在选项值为nil时选项链能够

(转)Inno Setup入门(十二)——Pascal脚本(1)

本文转载自:http://blog.csdn.net/yushanddddfenghailin/article/details/17250917 事件函数(1) Inno Setup支持以下函数和过程. function InitializeSetup(): Boolean; 该函数在安装程序初始化时调用,返回False 将中断安装,True则继续安装,测试代码如下: function InitializeSetup(): Boolean; begin Result := MsgBox('安装程

(转)Inno Setup入门(二十二)——Inno Setup类参考(8)

本文转载自:http://blog.csdn.net/yushanddddfenghailin/article/details/17268473 列表框 列表框(ListBox)是Windows应用程序中重要的输入手段,其中包括多个选项用户可以从其中选择一个或者多个,程序根据用户的选择做出相应的处理,列表框在外观上和存储框类似,但是行为却有很大的不同,列表框中项一般是预先给定的,而存储框则可以让用户进行输入,并且列表框中的项被选择之后也会触发事件.Pascal脚本中列表框的类由TlistBox实

C++语言学习(十二)——C++语言常见函数调用约定

C++语言学习(十二)--C++语言常见函数调用约定 一.C++语言函数调用约定简介 C /C++开发中,程序编译没有问题,但链接的时候报告函数不存在,或程序编译和链接都没有错误,但只要调用库中的函数就会出现堆栈异常等现象.上述现象出现在C和C++的代码混合使用的情况下或在C++程序中使用第三方库(非C++语言开发)的情况下,原因是函数调用约定(Calling Convention)和函数名修饰(Decorated Name)规则导致的.函数调用约定决定函数参数入栈的顺序,以及由调用者函数还是被