链表中为何使用二级指针

前言

在学习数据结构时,在链表初始化或者销毁链表的时候,经常使用二级指针或者一级指针的引用,这是为什么呢?同样是指向内存单元的地址,为什么就不能使用一级指针呢?使用一级指针去初始化或者是销毁链表的时候,究竟会发生什么呢?到底什么时候该用二级指针,什么时候该用一级指针?

如果你对这些问题有疑问,可以参考本篇文章,以下是我个人对这些问题的理解,如有问题,欢迎随时联系我。

参数的调用方式

我们通常使用的函数调用方式无非两种,一种是传值调用,一种是传址调用
谈起指针我们可能瞬间就会把它和传址调用联系在一起,但实际上,对于指针来讲,它也存在着这两种调用方式,传值调用和传值调用

传值调用

传值调用是指在调用参数时,不是对原参数进行操作,而是创建参数的拷贝并对其进行操作,这种调用有利于保护数据。

传址调用

传址调用的过程中把函数外部创建的变量的内存地址传递给函数参数,这种调用可以让函数和函数外边的变量建立起联系,函数内部可以直接操作函数外部;

传引用调用

适用于C++,不适用于C语言

示例说明

注意:
传递一级指针变量本身等价于在传递指针变量的值,虽然有指针参与其中,但在函数内部,也只是创建了指针的copy,无非就是把传过来的实参的值给指针的copy用一用,并没有对实参(原指针变量)进行操作

#include <iostream>    
#include <string.h>    
using namespace std;    
    
void fun1(char* str)    
{
    
        
    str = new char[5];    
    strcpy (str, "test string");    
}    
    
void fun2(char** str)    
{
    
        
    *str = new char[5];    
    strcpy (*str, "test string");    
}    
    
int main()    
{
    
        
    char* s = NULL;        
    cout << "call function fun1" << endl;    
    fun1 (s);    
    if (!s)    
        cout << "s is null!" << endl;    
    else    
        cout << s << endl;    
    
    cout << "call function fun2" << endl;    
    fun2 (&s);    
    if (!s)    
        cout << "s is null!" << endl;    
    else    
        cout << s << endl;    
    return 0;    
}    
————————————————
版权声明:本文为CSDN博主「踏莎行hyx」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012234115/article/details/39717215

输出结果:
在这里插入图片描述

使用二级指针/一级指针创建链表时的对比

主函数中作此调用

int main()
{
    
    
    LinkList L;
    ElemType e;
    Status i;
    int j, k;
    //InitList1(L);   //一级指针方式创建表头,失败
    InitList2(&L);  //二级指针方式创建表头,成功
 }

使用二级指针创建链表

//初始化表头,用二级指针
Status InitList2(LinkList *L) //等价于Node **L
{
    
    
    *L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
    if (!(*L))                           /* 存储分配失败 */
        return ERROR;
    (*L)->next = NULL; /* 指针域为空 */

    return OK;
}

用图片说明更为直观:
函数内部可以直接操作函数外部
在这里插入图片描述
简明描述为:
在这里插入图片描述

如果没有头结点:
在这里插入图片描述

使用一级指针创建链表会成功吗

//初始化表头,用一级指针(此方式无效)
Status InitList1(LinkList L) //等价于Node *L
{
    
    
    L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
    if (!L)                             /* 存储分配失败 */
        return ERROR;
    L->next = NULL; /* 指针域为空 */

    return OK;
}

用图片说明更为直观:

在这里插入图片描述
很明显,把传过来的实参的值给指针的copy用一用,并没有对实参(原指针变量)进行操作,这样创建的链表是毫无意义的,main.c后面再使用L时,用的依旧是个垃圾值,是有隐患的。

销毁链表时二级指针和一级指针的对比

main.c中

printf("销毁链表\n");
//DestroyList1(L);   //一级指针方式销毁链表,失败,且出现满屏乱码
DestroyList2(&L);  //二级指针方式销毁链表,成功

使用二级指针销毁链表

//销毁链表,使用二级指针
Status DestroyList2(LinkList *L)
{
    
    
    LinkList p, q;
    p = (*L)->next; /*  p指向第一个结点 */
    while (p)       /*  没到表尾 */
    {
    
    
        q = p->next;
        free(p);
        p = q;
    }
    free(*L); //头结点彻底没有掉才是销毁
    *L = NULL;
    return OK;
}

用图片说明更为直观:
在这里插入图片描述
简单来说:
销毁链表就是让头指针为空,然后这个链表就彻底湮没在内存中了
在这里插入图片描述

使用一级指针销毁链表会成功吗

//销毁链表,使用一级指针(此方式无效)
Status DestroyList1(LinkList L)
{
    
    
    LinkList p, q;
    p = L->next; /*  p指向第一个结点 */
    while (p)    /*  没到表尾 */
    {
    
    
        q = p->next;
        free(p);
        p = q;
    }
    free(L);
    L = NULL;
    return OK;
}

用图片说明更为直观:
在这里插入图片描述
可见这种方式的确很危险,实际测试中也的确出现了乱码

总结

1.初始化链表头部指针需要用二级指针或者一级指针的引用。

2.销毁链表需要用到二级指针或者一级指针的引用。

3.插入、删除、遍历、清空结点用一级指针即可。

完整代码

#include "stdio.h"
#include "stdlib.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20    /* 存储空间初始分配量 */
typedef int Status;   /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */
Status visit(ElemType c)
{
    
    
    printf("%d ", c);
    return OK;
}
typedef struct Node
{
    
    
    ElemType data;
    struct Node *next;
} Node;
typedef struct Node *LinkList; /* 定义LinkList */

//初始化表头,用一级指针(此方式无效)
Status InitList1(LinkList L) //等价于Node *L
{
    
    
    L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
    if (!L)                             /* 存储分配失败 */
        return ERROR;
    L->next = NULL; /* 指针域为空 */

    return OK;
}

//初始化表头,用二级指针
Status InitList2(LinkList *L) //等价于Node **L
{
    
    
    *L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
    if (!(*L))                           /* 存储分配失败 */
        return ERROR;
    (*L)->next = NULL; /* 指针域为空 */

    return OK;
}

//初始化表头,用一级指针引用
Status InitList3(LinkList &L) //等价于Node *&L
{
    
    
    L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
    if (!L)                             /* 存储分配失败 */
        return ERROR;
    L->next = NULL; /* 指针域为空 */

    return OK;
}

//清空链表,使用二级指针
Status ClearList1(LinkList *L)
{
    
    
    LinkList p, q;
    p = (*L)->next; /*  p指向第一个结点 */
    while (p)       /*  没到表尾 */
    {
    
    
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL; /* 头结点指针域为空 */
    return OK;
}

//清空链表,使用一级指针
Status ClearList2(LinkList L)
{
    
    
    LinkList p, q;
    p = L->next; /*  p指向(这里的第一个结点只头结点) */
    while (p)    /*  没到表尾 */
    {
    
    
        q = p->next;
        free(p);
        p = q;
    }
    L->next = NULL; /* 头结点指针域为空 */
    return OK;
}

//销毁链表,使用一级指针(此方式无效)
Status DestroyList1(LinkList L)
{
    
    
    LinkList p, q;
    p = L->next; /*  p指向第一个结点 */
    while (p)    /*  没到表尾 */
    {
    
    
        q = p->next;
        free(p);
        p = q;
    }
    free(L);
    L = NULL;
    return OK;
}

//销毁链表,使用二级指针
Status DestroyList2(LinkList *L)
{
    
    
    LinkList p, q;
    p = (*L)->next; /* p指向头结点(第一个结点) */
    while (p)       /*  没到表尾 */
    {
    
    
        q = p->next;
        free(p);
        p = q;
    }
    free(*L); //头结点彻底没有掉才是销毁
    *L = NULL;
    return OK;
}

//销毁链表,使用一级指针引用
Status DestroyList3(LinkList &L)
{
    
    
    LinkList p, q;
    p = L->next; /*  p指向第一个结点 */
    while (p)    /*  没到表尾 */
    {
    
    
        q = p->next;
        free(p);
        p = q;
    }
    free(L);
    L = NULL;
    return OK;
}
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L, int i, ElemType *e)
{
    
    
    int j;
    LinkList p;        /* 声明一结点p */
    p = L->next;       /* 让p指向链表L的第一个结点 */
    j = 1;             /*  j为计数器 */
    while (p && j < i) /* p不为空或者计数器j还没有等于i时,循环继续 */
    {
    
    
        p = p->next; /* 让p指向下一个结点 */
        ++j;
    }
    if (!p || j > i)
        return ERROR; /*  第i个元素不存在 */
    *e = p->data;     /*  取第i个元素的数据 */
    return OK;
}

//在中间插入元素,用二级指针
Status ListInsert1(LinkList *L, int i, ElemType e)
{
    
    
    int j;
    LinkList p, s;
    p = *L;
    j = 1;
    while (p && j < i) /* 寻找第i个结点 */
    {
    
    
        p = p->next;
        ++j;
    }
    if (!p || j > i)
        return ERROR;                   /* 第i个元素不存在 */
    s = (LinkList)malloc(sizeof(Node)); /*  生成新结点(C语言标准函数) */
    s->data = e;
    s->next = p->next; /* 将p的后继结点赋值给s的后继  */
    p->next = s;       /* 将s赋值给p的后继 */
    return OK;
}
//在中间插入元素,用一级指针
Status ListInsert2(LinkList L, int i, ElemType e)
{
    
    
    int j;
    LinkList p, s;
    p = L;
    j = 1;
    while (p && j < i) /* 寻找第i个结点 */
    {
    
    
        p = p->next;
        ++j;
    }
    if (!p || j > i)
        return ERROR;                   /* 第i个元素不存在 */
    s = (LinkList)malloc(sizeof(Node)); /*  生成新结点(C语言标准函数) */
    s->data = e;
    s->next = p->next; /* 将p的后继结点赋值给s的后继  */
    p->next = s;       /* 将s赋值给p的后继 */
    return OK;
}
//删除一个元素,用二级指针
Status ListDelete1(LinkList *L, int i, ElemType *e)
{
    
    
    int j;
    LinkList p, q;
    p = *L;
    j = 1;
    while (p->next && j < i) /* 遍历寻找第i个元素 */
    {
    
    
        p = p->next;
        ++j;
    }
    if (!(p->next) || j > i)
        return ERROR; /* 第i个元素不存在 */
    q = p->next;
    p->next = q->next; /* 将q的后继赋值给p的后继 */
    *e = q->data;      /* 将q结点中的数据给e */
    free(q);           /* 让系统回收此结点,释放内存 */
    return OK;
}
//删除一个元素,用一级指针
Status ListDelete2(LinkList L, int i, ElemType *e)
{
    
    
    int j;
    LinkList p, q;
    p = L;
    j = 1;
    while (p->next && j < i) /* 遍历寻找第i个元素 */
    {
    
    
        p = p->next;
        ++j;
    }
    if (!(p->next) || j > i)
        return ERROR; /* 第i个元素不存在 */
    q = p->next;
    p->next = q->next; /* 将q的后继赋值给p的后继 */
    *e = q->data;      /* 将q结点中的数据给e */
    free(q);           /* 让系统回收此结点,释放内存 */
    return OK;
}
/* 初始条件:顺序线性表L已存在 */
/* 操作结果:依次对L的每个数据元素输出 */
Status ListTraverse(LinkList L)
{
    
    
    LinkList p = L->next;
    while (p)
    {
    
    
        visit(p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}
int main()
{
    
    
    LinkList L;
    ElemType e;
    Status i;
    int j, k;
    //InitList1(L);   //一级指针方式创建表头,失败
    //InitList2(&L);  //二级指针方式创建表头,成功
    InitList3(L); //一级指针引用方式创建表头,成功
    for (j = 1; j <= 7; j++)
        ListInsert2(L, 1, j);
    printf("一级指针方式在L的表头依次插入1~7后:");
    ListTraverse(L);

    ListInsert1(&L, 3, 12);
    printf("二级指针方式在L的中间插入12后:");
    ListTraverse(L);

    ListInsert2(L, 5, 27);
    printf("一级指针在L的中间插入27后:");
    ListTraverse(L);

    GetElem(L, 5, &e);
    printf("第5个元素的值为:%d\n", e);

    ListDelete1(&L, 5, &e); /* 删除第5个数据 */
    printf("二级指针方式删除第%d个的元素值为:%d\n", 5, e);
    printf("依次输出L的元素:");
    ListTraverse(L);

    ListDelete2(L, 3, &e); /* 删除第3个数据 */
    printf("一级指针方式删除第%d个的元素值为:%d\n", 3, e);
    printf("依次输出L的元素:");
    ListTraverse(L);

    printf("二级指针方式清空链表\n");
    ClearList1(&L);
    printf("依次输出L的元素:");
    ListTraverse(L);

    for (j = 1; j <= 7; j++)
        ListInsert2(L, j, j);
    printf("在L的表尾依次插入1~7后:");
    ListTraverse(L);

    printf("一级指针方式清空链表\n");
    ClearList2(L);
    printf("依次输出L的元素:");
    ListTraverse(L);

    printf("销毁链表\n");
    //DestroyList1(L);   //一级指针方式销毁链表,失败,且出现满屏乱码
    DestroyList2(&L); //二级指针方式销毁链表,成功
    DestroyList3(L);  //一级指针引用方式销毁链表,成功

    return 0;
}

参考来源

本文参考了以下博文,结合自己的理解,总结记录了相关知识,特此感谢
https://blog.csdn.net/u012234115/article/details/39717215
https://blog.csdn.net/DX_Jone/article/details/102817995

猜你喜欢

转载自blog.csdn.net/qq_28816873/article/details/106576375