一、链表的概念
1.链表是什么?
链表是C语言中一种独特的数据结构,就是将若干个结构体变量,通过指针连接起来构成的数据结构
2.结点是什么?
链表中的结构体变量称为链表的结点
3.链表结点由什么构成?
在链表中既要存储数据,也要存储下一个结点的地址,因此链表结点通常由数据域和指针域组成
最简单的链表结点,只包含一个数据域和一个指针域
创建一个包含3个结点的简单链表,每个结点只包含一个整型数据成员和一个指针成员
首先定义一个链表结点结构体:
创建一个包含3个结点的简单链表,每个结点只包含一个整型数据成员和一个指针成员
首先定义一个链表结点结构体。
struct node //链表结点结构体类型
{
int data; //数据域
struct node *next; //指针域
}
struct node a, b, c; //链表结点结构体变量
解释:指针域next的类型是struct node*(指向struct node类型的指针类型),因为next
要指向下一个结点(struct node类型)
4.有了链表结点,如何构成链表呢?
访问链表时总是从第一个结点开始顺序访问,因此需要定义一个专门的指针,用于指向
链表的第一个结点,并称之为头指针。
struct node *head;
head = &a; //令头指针指向第一个结点
然后,依次在链表结点的数据域中存入数据, 并通过指针域将链表结点连接起来。
scanf("%d",&a.data);
a.next = &b; //令第一个结点的指针域指向第二个结点
scanf("%d",&b.data);
b.next = &c;
scanf("%d",&c.data);
c.next = NULL; //令末结点的指针域为空指针
在上述程序中,我们采用定义变量(静态内存分配)的方式创建链表结点。这种链表称为静态链表
但是一般情况我们选择动态
二、关于动态内存的知识补充
1.动态内存分配函数malloc
#include<stdlib.h>//头文件
原型:
void *malloc(unsigned int n)
功能:
申请分配长度为n字节的连续内存,若成功返回分配内存的首地址,若失败,返回NULL
为了提高通用性,我们可以用sizeof运算符求出内存长度,例如:sizeof(int)
2.如何申请分配一个整型数据的内存?
malloc(sizeof(int));实现
找到动态分配的内存区
int *p ;
p=(int *)malloc(sizeof(int)) ;//强制为int*,与p的类型一致
这个强转其实是没必要的,因为void*与所有的其他指针类型都是赋值兼容的。
一定要使用free释放内存
void free(void *p);
三、创建一个动态链表
因为链表是顺序访问,对链表来说只能从第一个结点开始依次访问
因此,需要将其第一个结点的地址保存起来。
如何保存第一个结点的地址呢?
分两种情况
情况1
不带头结点的链表
如果将第一个结点的地址存储到一个专门的指针变量中,那么这种链表称为不带头结点
的链表,这个专门的指针称为头指针。
情况2
带头头结点的链表
如果将第一个结点的地址存储到一个专门的结点的指针域中,那么这种链表称为带头结点的链表。
这个专门的结点称为头结点,头结点的数据域中不存储任何有效数据。
创建一个带头结点的动态链表
编写一个函数,创建一个带头结点的动态链表,用于存储从键盘输入的一批学生的高考总分(以任意负
数作为结束标志),并以链表的头指针作为函数的返回值。最后在主函数中调用该函数。
1.首先定义结点结构体类型
struct node
{
int data;
struct node *next;
};
typedef struct node NODE;//定义类型别名
2.创建一个新的链表(主要包括两个步骤:创建新结点;将新结点连接到链表中的当前末结点之后)
故:需定义三个指针变量
头指针head
末结点p
新结点指针q
NODE *head,*p,*q;
3.动态创建一个新结点,并使头指针指向头结点
head=malloc(sizeof(NODE));
4.使末结点指针p指向头结点(相当于空链表的末结点)
p=head;
5.输入一个成绩并暂存,若是负数则将末结点的指针域赋值为空指针NULL,作为链表的结束标志。
6.动态创建一个新结点,并使q指向它,将已输入的数据存入新结点的数据域中。
q=malloc(sizeof(NODE));
q->data=成绩;
7.将新节点连接到链表中,使得当前末结点的指针域指向新结点。
p->next=q
8.调整末结点指针,使其指向新节点
p=q;
9.转向第五步(循环)
10.将末结点的指针域赋值为空指针NULL,作为链表的结束标志。
动态链表创建的完整可运行代码如下:
#include<stdio.h>
#include<stdlib.h>
struct node{ //创建结点类型
int data;
struct node*next;
};
typedef struct node NODE;
int main()
{
int t;
NODE *head,*q,*p; //需要一个头结点指针,末结点指针,新结点指针
head=malloc(sizeof(NODE)); //创建头结点,让head指针指向头结点
p=head; //末结点指针p指向末结点
while(1){
printf("输入成绩:");
scanf("%d",&t);
if(t<0) break;
q=malloc(sizeof(NODE)); //q指向新节点
q->data=t; //t存入新结点数据域中
p->next=q; //末结点指针p的指针域指向q新结点
p=q; //末结点指针p指向新的末结点
}
p->next=NULL; //处理完毕,指针的末结点指针域指向NULL
printf("头指针=%p",head); //返回头指针的
free(head),free(q); //释放内存,防止内存泄漏
return 0;
}
四、遍历一个动态单链表
带头结点的单向链表的遍历步骤:
通过头指针找到头结点
若头结点的指针域为NULL,则为空链表
否则,跟踪链表结点的指针域,找到下一个结点并输出其数据域的值
直到遇到链表的结束标志为止
例题:
现在有一个单向链表,要求编写一个函数,输出每个链表结点中数据域的值。最后在主函数中调用该函数
要遍历,我们需要创建一个动态单链表creat()函数创建;然后我们遍历链表print()函数遍历
#include<stdio.h>
#include<stdlib.h>
struct node{ //创建结点类型
int data;
struct node*next;
};
typedef struct node NODE;
void* creat(){ //建立单链表函数
int t;
NODE *head/*头指针*/,*p/*末指针*/,*q/*新指针*/;
head=malloc(sizeof(NODE)); //指向头结点
p=head; //指向末结点
while(1){
printf("请输入成绩:");
scanf("%d",&t);
if(t<0) break;
q=malloc(sizeof(NODE)); //建立新结点
q->data=t; //新数据存储在新节点的数据域中
p->next=q; //末结点的指针域指向新节点
p=q; //新节点更新成末结点
}
p->next=NULL; //设置链表结束标志
return head;
}
void print(NODE*head){ //建立遍历(打印)链表的函数
NODE *p;
p=head->next; //p指向头结点的后继结点
if(p==NULL) //如果这是一个空链表的话
printf("此链表为空!\n");
else{
printf("此链表数据的值:\n");
while(p!=NULL){ //判断是否到达链表末尾
printf("%d\t",p->data);
p=p->next;
}
}
return ; //返回主调函数
}
int main()
{
NODE*h;
h=creat();
print(h);
return 0;
}