一、动态内存分配
如果要存储数量比较多的同类型或同结构的数据的时候,总是使用一个数组。比如说我们要存储一个班级学生的某科分数,总是定义一个float型数组:floatscore[30];但是,在使用数组的时候,总有一个问题困扰着我们:数组应该有多大?在很多的情况下,你并不能确定要使用多大的数组,比如上例,你可能并不知道该班级的学生的人数,那么你就要把数组定义得足够大。这样,你的程序在运行时就申请了固定大小的你认为足够大的内存空间。即使你知道该班级的学生数,但是如果因为某种特殊原因人数有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错误,甚至导致严重后果。那么有没有其它的方法来解决这样的外呢体呢?有,那就是动态内存分配。所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。从以上动、静态内存分配比较可以知道动态内存分配相对于景泰内存分配的特点:1、不需要预先分配存储空间;2、分配的空间可以根据程序的需要扩大或缩小。
二、如何实现动态内存分配及其管理
要实现根据程序的需要动态分配存储空间,就必须用到以下几个函数
1、malloc函数
malloc函数的原型为:void*malloc (unsigned int size),其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。
2、free函数
由于内存区域总是有限的,不能不限制地分配下去,而且一个程序要尽量节省资源,所以当所分配的内存区域不用时,就要释放它,以便其它的变量或者程序使用。这时我们就要用到free函数。
其函数原型是:voidfree(void *p),作用是释放指针p所指向的内存区。其参数p必须是先前调用malloc函数或calloc函数(另一个动态分配存储区域的函数)时返回的指针。给free函数传递其它的值很可能造成死机或其它灾难性的后果。
注意:这里重要的是指针的值,而不是用来申请动态内存的指针本身。例:int*p1,*p2;p1=malloc(10*sizeof(int));p2=p1;……free(p2) /*或者free(p2)*/malloc返回值赋给p1,又把p1的值赋给p2,所以此时p1,p2都可作为free函数的参数。malloc函数是对存储区域进行分配的。free函数是释放已经不用的内存区域的。所以由这两个函数就可以实现对内存区域进行动态分配并进行简单的管理了。
三、链表
链表:就是用一组任意的存储单元存储线性表元素的一种数据结构,链表又分为单链表、双向链表和循环链表等。我们先讲讲单链表,。
所谓单链表,是指数据接点是单向排列的。单链表有一个头节点h e a d,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,
其结构类型分为两部分:
1、数据域:用来存储本身数据
2、链域或称为指针域:用来存储下一个结点地址或者说指向其直接后继的指针。
Char name[30];
Struct node *next;
} stu;
这样就定义了一个单链表的结构,其中char name[20]是一个用来存储姓名的字符型数组,指针*next是一个用来存储其直接后继的指针。
定义好了链表的结构之后,只要在程序运行的时候向数据域中存储适当的数据,如有后继结点,则把链域指向其直接后继,若没有,则设置为NULL。
链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为N U L L。
链表中的各节点在内存的存储地址不是连续的,其各节点的地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可以跳跃式分配地址。
•单链表的创建过程有以下几步:
1 )定义链表的数据结构。
2 )创建一个空表。
3 )利用m a l l o c( )函数向系统申请分配一个节点。
4 )将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新节点接到表尾。
5 )判断一下是否有后续节点要接入链表,若有转到3 ),否则结束。
•单链表的输出过程有以下几步
1)找到表头。
2)若是非空表,输出节点的值成员,是空表则退出。
3 )跟踪链表的增长,即找到下一个节点的地址。
4)转到2 )。
例如
Typedef struct node{
Char name[30];
Struct node *next;
} stu;
这样就定义了一个单链表的结构,其中char name[20]是一个用来存储姓名的字符型数组,指针*next是一个用来存储其直接后继的指针。
定义好了链表的结构之后,只要在程序运行的时候向数据域中存储适当的数据,如有后继结点,则把链域指向其直接后继,若没有,则设置为NULL。
链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为N U L L。
链表中的各节点在内存的存储地址不是连续的,其各节点的地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可以跳跃式分配地址。
•单链表的创建过程有以下几步:
1 )定义链表的数据结构。
2 )创建一个空表。
3 )利用m a l l o c( )函数向系统申请分配一个节点。
4 )将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新节点接到表尾。
5 )判断一下是否有后续节点要接入链表,若有转到3 ),否则结束。
•单链表的输出过程有以下几步
1)找到表头。
2)若是非空表,输出节点的值成员,是空表则退出。
3 )跟踪链表的增长,即找到下一个节点的地址。
4)转到2 )。
例题: 学生成绩管理系统
1.录入学生信息
2.打印学生信息
3.保存学生信息
4.读取学生信息
5.统计所有学生人数
6.查找学生信息
7.修改某个学生的信息
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct _Student{
char name[10];
int age;
int number;
int score;
}Student;
typedef struct _Node{
Student stu;//学生信息
struct _Node * next;//指向下一个学生
}Node;
void searchBynumber();
void searchByname();
Node * head=NULL;//定义第一个学生
//1.录入学生信息
void inputStudent()
{
Node * p; //p指向最后一个学生
p=head;
while (head!=NULL&&p->next!=NULL)
{
p=p->next;
}
//为新的学生分配一个空间
Node * newnode=(Node *)malloc(sizeof(Node));
newnode->next=NULL;
if(head==NULL)
{
head=newnode;
p=head;
}
else
{
p->next=newnode;//p的下一个节点为newnode
}
//输入新的学生数据
printf("\n请输入学生信息(姓名 学号 年龄 成绩):");
scanf("%s %d %d %d",newnode->stu.name,&newnode->stu.number,&newnode->stu.age,&newnode->stu.score);
printf("\n数据添加成功!\n");
}
//2.打印学生信息
void printStudent()
{
printf("\n打印所有学生信息");
Node *p;
p=head;//指向第一个学生
while (p!=NULL)
{
printf("\n学号:%-4d 姓名:%-10s 年龄:%-4d 成绩%:%-4d",p->stu.number,p->stu.name,p->stu.age,p->stu.score);
p=p->next;
}
}
//3.保存学生信息
void saveStudent()
{
FILE *fp;
Node *p;
p=head;
fp=fopen("E:/lianbiao.txt","w");
if(fp!=NULL)
{
while(p!=NULL)
{
fprintf(fp,"%s %d %d %d\n",p->stu.name,p->stu.number,p->stu.age,p->stu.score);
p=p->next;
}
printf("保存成功!");
}
fclose(fp);
}
//4.读取学生信息
void readStudent()
{
FILE *fp;
Node *q=head;
fp=fopen("E:/lianbiao.txt","r");
if(fp != NULL)
{
while(q!=NULL)
{
fscanf(fp,"%s, %d, %d ,d",q->stu.name,&q->stu.age,&q->stu.number,&q->stu.score);
q=q->next;
}
}
else
printf("打开失败!");
fclose(fp);
q=head;
while(q!=NULL)
{
printf("姓名:%-10s 学号:%-4d 年龄:%-4d 成绩: %-4d\n",q->stu.name,q->stu.age,q->stu.number,q->stu.score);
q=q->next;
}
}
//5.统计所有学生人数
int countStudent()
{
int count=0;
Node *p;
p=head;
while(p!=NULL)
{
count++;
p=p->next;
}
return count;
}
//6.查找学生信息
void findStudent()
{
while(1)
{
printf("\n查找方式:\n");
printf("1、按姓名查找\n");
printf("2、按学号查找\n");
printf("3、返回主菜单");
int i;
printf("\n请选择查找方式:");
scanf("%d",&i);
switch(i)
{
case 1:searchByname();
break;
case 2:searchBynumber();
break;
default:
return;
break;
}
}
}
//按学号查找
void searchBynumber()
{
int i;
Node *p;
int count=0;
printf("请输入要查找的学生学号:");
scanf("%d",&i);
for(p=head;p!=NULL;p=p->next)
{
if(p->stu.number==i)
{
printf("%s %d %d %d",p->stu.name,p->stu.number,p->stu.age,p->stu.score);
count++;
break;
}
else
continue;
}
if(count==0)
printf("没有该学生!");
}
//按姓名查找
void searchByname()
{
char name[10];
Node *p;
int count=0;
printf("请输入要查找的学生姓名:");
scanf("%s",name);
for(p=head;p!=NULL;p=p->next)
{
if(strcmp(p->stu.name,name)==0)
{
printf("%s %d %d %d",p->stu.name,p->stu.number,p->stu.age,p->stu.score);
count++;
break;
}
else
continue;
}
if(count==0)
printf("没有该学生!");
}
//7.修改某个学生的信息
void modifyByname()
{
char name[10];
Node *p=head;
printf("请输入要修改的信息的学生姓名:\n");
scanf("%s",name);
while(p!=NULL)
{
if(strcmp(p->stu.name,name)==0)
{
printf("请输入新的学生信息(姓名 学号 年龄 成绩):\n");
scanf("%s %d %d %d",p->stu.name,&p->stu.number,&p->stu.age,&p->stu.score);
printf("修改成功!\n");
break;
}
p= p->next;
}
}