链表中为何使用二级指针_链表初始化为什么要二级指针-程序员宅基地

技术标签: 链表  C语言学习  指针  数据结构  

前言

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

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

参数的调用方式

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

传值调用

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

传址调用

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

传引用调用

适用于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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28816873/article/details/106576375

智能推荐

MATLAB车道线检测技术分析_车道路线识别matlab-程序员宅基地

文章浏览阅读942次,点赞22次,收藏21次。车道线检测的应用场景具有时序信息特性,为了利用时序特征通常会引入RNN模块,加上Encoder-Decoder的形式已经成为CNN特征提取的标配,所以一般的做法是对Encoder提取的Features进行进一步加工,提取连续帧带来的历史信息。VPGNet:一共20k张图片,包含白天(非雨天、雨天、大雨天)、夜晚的数据,同时包含了各种车道线类型,以及其他不同类型的车道标识(左转箭头、直行箭头、斑马线等等),如下图。TuSimple:一共72k张图片,位于高速路,天气晴朗,车道线清晰,特点是车道线以点来标注;_车道路线识别matlab

kds官方android客户端,电子厨打设置(KDS/ADS)-程序员宅基地

文章浏览阅读1.3k次。KDS应用场景KDS和ADS设置使用方案:厨房模式(KDS) 配菜模式(ADS) 呼叫广告模式(TV)电子菜牌模式KDS(kitchen display system)ADS(assign display system)准备工作安卓电子厨打客户端硬件要求:各类安卓平板及安卓一体机(安卓4.4.2以上,7寸屏以上)收银设备的IP,做KDS的设备IP,做ADS的设备IP必须在同一个网段(接同一个路由上..._kitchen display system

Nginx + Consul + Upsync实现动态负载均衡_consul+nginx-upsync-module-程序员宅基地

文章浏览阅读1.7k次。各组件作用:ConsulWeb:Consul的客户端可视化界面,管理负载均衡配置的信息ConsulServer:Consul服务端,用于存放负载均衡配置Nginx:以间隔时间动态读取ConsulServer配置Upsync:新浪微博开源的基于Nginx实现动态配置的三方模块。Nginx-Upsync-Module的功能是拉取Consul的后端server的列表,并动态更新Nginx..._consul+nginx-upsync-module

ECharts动态加载堆叠柱状图的实例-程序员宅基地

文章浏览阅读1.2k次。一、引入echarts.js文件(下载页:http://echarts.baidu.com/download.html)二、HTML代码:<div style="width: 100%; height: 400px;" id="main"></div>三、js代码(获取数据以及对数据的处理方法):function loadData(callb..._echarts 横向堆叠柱状图

S2 第二学期的第二本书的第六章上机和简答题_public student(string num,string password,string n-程序员宅基地

文章浏览阅读805次。1.继承[csharp] view plain copy using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespac_public student(string num,string password,string name,string age,string birt

通过Llamaindex分析用户舆情信息技术调研_get_response_synthesizer-程序员宅基地

文章浏览阅读230次。通过Llamaindex分析用户舆情信息技术调研_get_response_synthesizer

随便推点

CISCO路由器交换机简介及Packet+Tracer使用说明-程序员宅基地

文章浏览阅读571次。附录一 路由器和交换机产品简介 (一)路由器 思科公司的产品被网络用户广泛的使用,对它们的典型产品及其特性的了解可对网络设备有一定大致的认识,以下主要对Cisco1800系列、Cisco2600系列、Cisco 2800系列、Cisco 3700 系列模块化和固定配置的路由器产品进行简单介绍。首先以"S26C-12007XK ","CD26-BHP-12..._cisco packet tracer路由器与转发器

XHTML_xhtml 中正确标记折行-程序员宅基地

文章浏览阅读2.2k次。XHTML 是以 XML 格式编写的 HTML。什么是 XHTML? XHTML 指的是可扩展超文本标记语言 XHTML 与 HTML 4.01 几乎是相同的 XHTML 是更严格更纯净的 HTML 版本 XHTML 是以 XML 应用的方式定义的 HTML XHTML 是 2001 年 1 月发布的 W3C 推荐标准 XHTML 得到所有主流浏览器的..._xhtml 中正确标记折行

计算机图形图像处理在教学中的应用,计算机图形图像处理案例教学法运用-程序员宅基地

文章浏览阅读534次。摘要:笔者根据计算机图形图像处理课程与中职学生学习的特点,分析了目前中职学校计算图形图像处理课程教学中存在的问题,针对如何提高中职学生对计算机图形图像处理课程的学习兴趣和解决实际问题的能力,提出了案例教学法在该课程中的具体实施办法,并对其实践进行了进一步的讨论。关键词:计算机图形图像处理;案例教学;中职当今世界电子商务发展迅速,计算机平面设计这门技术在很多领域都得到广泛应用。《Photoshop图..._图像分类在教育中的应用

python资源文件嵌入exe_pyinstaller将资源文件打包进exe中-程序员宅基地

文章浏览阅读921次。在网上看了很多博客,终于找到了符合自己智商可理解的打包资源文件方法,现引用如下https://www.cnblogs.com/darcymei/p/9397173.htmlhttps://blog.csdn.net/sinat_27382047/article/details/81304065"""终于把资源文件加载进去了,就是当exe文件移植后,它运行的时候会产生一个临时文件夹,把资源文件存储到..._pyinstaller如何将_internal添加进exe

H3C模拟器配置vlan-程序员宅基地

文章浏览阅读3.3k次,点赞4次,收藏7次。Valn 11组网需求• 交换机GE_2上的VLAN 5 和VLAN 10 为Primary VLAN,其上层端口GigabitEthernet1/0/1需要允许VLAN 5 和VLAN 10 的报文携带VLAN Tag 通过。• 交换机GE_2 的下行端口GigabitEthernet1/0/2 允许Secondary VLAN 2 通过,GigabitEthernet1/0/3 允许Sec..._新华3模拟器vlan配置

img撑满全屏的方法(img非背景图)_img 铺满-程序员宅基地

文章浏览阅读4.7w次,点赞8次,收藏15次。我有一个模板,想按常规做一个div里面放置一个img图片,并且让图片铺满容器,自适应容器大小。HTML结构代码如下(在这个盒模型上,我已经放置了一些不重要的样式)。div style="height:270px;width:400px;border:2px black solid;"> a href="http://www.paipk.com">img src="..." alt="拍_img 铺满

推荐文章

热门文章

相关标签