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

小结

个人认为利用指针,能更好地理解链表的思想
虽然学习和消化的时间比较长,但是是值得的
下一节,还有不用指针的模拟链表,但是个人认为少了那种精髓

posted @ 2021-11-18 20:31  RetenQ  阅读(170)  评论(0编辑  收藏  举报