一.动态变量与静态变量
静态变量:编译时直接分配存贮空间
动态变量:没有显示声明,没有名字,不给分配空间,一般通过指针标识。
1.程序的内部存储结构:
首先是目标代码区,然后是静态存储区,接着是库程序代码区,剩余的空间就是栈区和堆区,从剩余空间两端各自向中间延申。
2.管理动态变量
(1)申请内存空间
T*ptr;
ptr=(T*)malloc(sizeof(T));
注:T说明申请空间要保存的数据对象的类型,*标明是一个指针,malloc()函数申请sizeof(T)大小的内存,并返回这个内存区域的首地址。在这里使用了一个强制类型转换保证将其转化成T类型的指针。
(2)释放内存空间
free(ptr);
注意:以下情况不要使用free();
1)ptr无值
2)ptr值为NULL
3)ptr的空间不是malloc()申请来的
4)对一次申请的存储区多次释放
二.链表
先来看一个实例,考虑这样的一个程序,输入一年内看过的所有电影,并储存其信息(片名、导演、主演、片长、种类、评级),使用一个链表来组织数据:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define TSIZE 45
/*->是C语言和C++语言的一个运算符,叫做指向结构体成员运算符,用处是使用一个指向结构体或对象的指针访问其内成员。*/
struct film{
char title[TSIZE];
int rating;
struct film*next;
};
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // look for newline
if (find) // if the address is not NULL,
*find = '\0'; // place a null character there
else
while (getchar() != '\n')
continue; // dispose of rest of line
}
return ret_val;
}
int main(void){
struct film*head=NULL;
struct film*prev,*current;
char input[TSIZE];
/*收集并存储信息*/
puts("Enter first movie title:");
while(s_gets(input,TSIZE)!=NULL&input[0]!='\0')
{
current=(struct film*)malloc(sizeof(struct film));
if (head==NULL)
head=current;
else
prev->next=current;
current->next=NULL;
strcpy(current->title,input);
puts("Enter your rating<0-10>:");
scanf("%d",¤t->rating);
while (getchar()!='\n')
continue;
puts("Enter next movie title(empty line to stop):");
prev=current;
}
/*显示电影列表*/
if(head==NULL)
printf("No data entered.") ;
else
printf("Here is the movie list:\n");
while (current!=NULL)
{
printf("Movie:%s Rating:%d\n",current->title,current->rating);
current=current->next;
}
/*完成任务,释放已经分配的内存*/
current=head;
while(current!=NULL)
{
current=head;
head=current->next;
free(current);
}
printf("Bye!\n");
return 0;
}
这个程序实现了两个功能,构造链表并存储数据,显示链表中的内容。
1.显示链表
先来看链表的结构
struct film{
char title[TSIZE];
int rating;
struct film*next;
};
在这里有三个成员,其中next成员就是保存下一个链表的地址。
并且我们已经设置了链表头指针的指向:
struct film*head=NULL;
从而如果要设置当前指向第一个结构的指针,我们只需要让film类型的指针current和head有相同的指向。
current=head;
从而我们可以访问结构内的成员,然后再根据存储在next成员中的信息来让current指向下一个结构。
实现:
current=current->next;
重复上述过程,直到指向最后一个元素,此时next的值已经被设定成NULL.
2.创建链表
和动态变量一样,创建一个链表,我们只需要分配空间,储存地址,写入信息即可。
首先,链表的首个地址应该放在head里,后面的链表元素依次放在next成员里就可以了。
后面调用next很简单,但是要确定我们处理的是不是第一个结构,因而我们考虑用head的值来进行判断。
从而我们有这样的代码:
if (head==NULL)
head=current;//程序开始时初始化指针为NULL,从而判断是否为NULL确定是不是第一个元素
else
prev->next=current;
在这里指针prev指向了上次分配的结构。
接下来,为链表的成员设置合适的值,尤其是把next成员设置成NULL,表明当前结构是链表的最后一个结构,然后给title,rating赋值。
current->next=NULL;
strcpy(current->title,input);
puts("Enter your rating<0-10>:");
scanf("%d",¤t->rating);
最后为下一次输入做准备,让prev指向当前结构,因为下次输入时当前结构会成为正在输入结构的前一个结构,从而设置:
prev=current;
最后调用free()释放内存即可。
三.链表操作
下面给出一些链表操作:
创建链表
遍历链表
在链表中检索
插入
删除
交换位置
首先,我们假设有这样的ADT类型,声明一个简单的单项链表:
typedef ... items;
typedef struct cell{
items data;
struct cell*next;
}celltype;
typedef celltype*pcelltype;
pcelltype top,rear;
(1)下面考虑创建一个单向的链表,也就是用一项一项的数据逐步建立行程一个链表,可以分成向链表头加入数据和向尾部加入数据两种方式,因而定义:
void push(items x){
pcelltype p;
p=(pcelltype)malloc(sizeof(celltype));
p->data=x;
p->next=top;
top=p;
}//向链表头部添加数据
p是一个指针,首先给data元素赋x,给p的next元素赋链表的链头指针top,也就代表p的下一个元素是原来的头部,而从头部加入数据代表p是新的头,从而让top=p,实现了从头部加入数据的操作。
/*假设rear为表尾部指针,将新的元素从尾部加入链表,方法如下*/
void InRear(items x){
pcelltype p;
p=(pcelltype)malloc(sizeof(celltype));
p->data=x;
p->next=NULL;
if (rear==NULL){//空链
rear=top=p;
}else{
rear->next=p;//非空链表
rear=p;
}
}
(2)单向链表的遍历
遍历:从头到尾将链表中的所有数据项都加工一次。
p=top;
while(p!=NULL)
{ //加工p->data
p=p->next;
}
可以再加入一个指针保留其前驱的位置。
p0=NULL;//指向前驱
p=top;
while(p!=NULL){
//加工p-data
p0=p;
p=p->next
}
(3)插入
假设我们现在有p0和p是相邻的链表元素,P0是p的前驱,考虑在中间插入r,则只需要定义好r的数据项,将p0中的next成员修改成r的地址,让r中的next指向p,就可以成功插入r。
r->next=p;
p0->next=r;//插入已经完成
p0=r;//让p0仍然是p的前驱
(4)删除
删除p所指向的链表
p=p->next;
free(p0->next);
p0->next=p;
实际上很容易理解,删除p所指向的,也就代表删除后p指向删除项后面的,因此第一句就是这样,然后释放p原指向的项的内存,因为p的指向已经被修改,所以在这里用p0的next元素来查找它的地址,释放后再让next保存p现在的位置就可以了。
(5)交换
通过地址的对换来实现链表元素的交换。
一方面可以把基本数据部分交换,但是在数据量大的时候比较浪费。
将p所指向的项与q交换:
/*交换p->next,q->next*/
g=p->next;
p->next=q->next;
q->next=g
/*交换p0->next,q0->next*/
p0->next=q;
q0->next=p;
/*交换p,q*/
p=p0->next;
q=q0->next;
我们首先来看图a,指出了要交换数据项的链表(p指向的链表和q指向的链表分别是2,5)
2的next本来应该指向3,5的next应该指向6,但是在前5句完成之后,2的next指向了6,5的next指向了3,1指向5,4指向2.
在6-7行执行后,我们注意到,此时p指向p0的next,p0指向1,1指向5,即p指向5
同理,q指向q0的next,也就是4的next(2),实现了2和5的对换,即p指向5,q指向2,而其关系不变,也就是p0是p的前驱,q0是q的前驱。
再来回顾以下交换,实际上在这里我们一共需要四个指针,各自指向如下:
p0-1,p-2;q0-4,q-5
最基本的想法就是改成
p0-1,p-5;q0-4,q-2,与此同时各自相邻的指针仍然是相邻的。
首先就可以确定,由于p0/p相邻,则p0指向的1的next成员修改后应该保存的是5的地址,而q是此前指向5的指针;同理,q0指向的4的next成员应该在修改后保存的是2的地址,这是可以明确的,就是4,5两行代码。
由于交换的是基本数据,我们不需要交换pq各自的next成员,也就是说5的next应该是3,2的next是6的地址。这一步放在4,5前面进行。
然后保证各自相邻的顺序用p0/q0赋值即可。
(6)单向链表中检索
p0=NULL;/*前驱位置*/
p=top;/*当前位置*/
while(p!=NULL&&p->key!=key0){//查找等于给定关键字的数据节点,找到就返回相应的指针,否则带回NULL
p0=p;//非空且不等
p=p->next;
}