2.3.1 使用指针实现的链表
链表(指针)
在存储一大波数的时候,如果使用数组,有时会感到数组显得不太灵活
我们可以在C语言中使用指针和动态分配函数malloc来实现链表
关于指针,这里就不赘述了,默认已经了解相关知识
指针实现
malloc
malloc 函数的作用就是从内存中申请分配指定字节大小的内存空间
malloc(4); //这样就申请了四个字节大小的内存空间
如果不知道字节大小,那么使用sizeof()查看就好了
malloc 函数的返回值是void*,也就是未确定类型的指针,它可以被强制转换为任何其它类型的指针
int *p ;
p = (int *)malloc(sizeof(int)) ;
比如这样我们就得到了一个整型的指针,它可以存放整数
指针变量存放的是一个内存空间的首地址(第一个字节的地址)
但是这个空间占用了多少个字节,用来存储什么类型的数据,则是由指针类型标明的
下面让我们来实战试试这玩意
代码实例
#include <stdio.h>
#include <stdlib.h>
int main(){
int *p ; //定义一个指针
p = (int *)malloc(sizeof(int)) ; //指针p来获取动态分配的内存空间地址
*p = 10 ; //向指针p所指向的内存空间存入10
printf("%d",*p); //输出指针p所指向的内存中的值
getchar();getchar();
return 0 ;
}
现在我们尝试使用链表
构建链表
我们把链表存储数据的地方叫做结点,每一个结点有两个部分组成
一部分用来存储具体数值,另一部分存储下一个结点的位置
struct node{
int data ;
struct node *next ;
}
OK,我们已经定义了一个相关的结构体,那么下一步我们来构建链表
1.首先,我们需要一个头指针head来指向链表的最开始的地方(链表还没有建立的时候头指针为空/指向空结点)
2.创建一个结点,设置所需要的两个部分。这里顺便一提,访问结构体内部成员使用->
而非点.
,因为我们要访问的p是一个指针
3.设置头指针
代码实例
#include <stdio.h>
#include <stdlib.h>
//创建一个结构体表示链表的结点类型
struct node{
int data ;
struct node *next ;
}
int main(){
//相关变量
struct node *head,*p,*q,*t ;
int i,n,a ;
scanf("%d",&n);//读入多少数
head = NULL ; //头指针为空
for(i=1;i<=n,i++){
//利用循环读入n个数
scanf("%d",&a);
//动态申请一个空间来存放一个结点,并且使用临时指针p来指向这个结点
p = (struct node *)malloc(sizeof(struct node));
p->data = a ; //把刚才读到的数据存储到当前结点的data域中
p->next = NULL ; //设置当前结点的后继指针指向空。也就是当前结点的一个结点为空
if(head == NULL){
head = p ; //如果是第一个创建的结点,则把头指针指向他
}else{
q->next = p ;//如果不是第一个,那么将上一个结点的后继指针指向当前结点
}
q = p ; //把q也指向这个结点
}
//下面试一下输出链表所有数
t = head ;
while(t!=NULL){
printf("%d",t->data);
t = t->next ; //继续下一个结点
}
getchar();getchar();
return 0 ;
}
如上所示,我们得到了一个链表。但是只得到链表是不够的,我们还需要加减数据
链表数据的加减
这里以加数据为例子,删数据同理。方法如下:
1.创建一个临时指针,并且从链表头部一直往下遍历
2.按照条件筛选位置,成功后,将对应的指针修改。让这个被添加的数据处于原本前后两个地址的中间
增加数据的代码实现如下:
//下面来读数据
scanf("%d",&a) ;
t = head ; //t是临时指针,从链表的头部开始遍历
while(t!=NULL){
//没有到达链表尾部之前,我们都进行循环遍历
if(t->next == NULL || t->next->data > a){
//如果当前结点是最后一个结点,或者我们找到了需要插入的位置
//这里以“按顺序插入”为例子
p = (struct node *)malloc(sizeof(struct node)) ;
//动态申请一个空间,来存放新结点
p->data = a ;
p->next = t->next ;
//新增结点的后续指针,当然是要指向当前指针后继指针指向的点
t->next = p ;
//当前结点的后继指针指向新增结点
break; //插入完毕时退出循环
}
t = t->next ;
}
含插入的完整代码
#include <stdio.h>
#include <stdlib.h>
//创建一个结构体表示链表的结点类型
struct node{
int data ;
struct node *next ;
}
int main(){
//相关变量
struct node *head,*p,*q,*t ;
int i,n,a ;
scanf("%d",&n);//读入多少数
head = NULL ; //头指针为空
for(i=1;i<=n,i++){
//利用循环读入n个数
scanf("%d",&a);
//动态申请一个空间来存放一个结点,并且使用临时指针p来指向这个结点
p = (struct node *)malloc(sizeof(struct node));
p->data = a ; //把刚才读到的数据存储到当前结点的data域中
p->next = NULL ; //设置当前结点的后继指针指向空。也就是当前结点的一个结点为空
if(head == NULL){
head = p ; //如果是第一个创建的结点,则把头指针指向他
}else{
q->next = p ;//如果不是第一个,那么将上一个结点的后继指针指向当前结点
}
q = p ; //把q也指向这个结点
}
//下面我们尝试插入数据
scanf("%d",&a) ;
t = head ; //t是临时指针,从链表的头部开始遍历
while(t!=NULL){
//没有到达链表尾部之前,我们都进行循环遍历
if(t->next == NULL || t->next->data > a){
//如果当前结点是最后一个结点,或者我们找到了需要插入的位置
//这里以“按顺序插入”为例子
p = (struct node *)malloc(sizeof(struct node)) ;
//动态申请一个空间,来存放新结点
p->data = a ;
p->next = t->next ;
//新增结点的后续指针,当然是要指向当前指针后继指针指向的点
t->next = p ;
//当前结点的后继指针指向新增结点
break; //插入完毕时退出循环
}
t = t->next ; //继续下一个结点
}
//下面试一下输出链表所有数
t = head ;
while(t!=NULL){
printf("%d",t->data);
t = t->next ; //继续下一个结点
}
getchar();getchar();
return 0 ;
}
小结
个人认为利用指针,能更好地理解链表的思想
虽然学习和消化的时间比较长,但是是值得的
下一节,还有不用指针的模拟链表,但是个人认为少了那种精髓