链表中为何使用二级指针
本篇目录
- 前言
- 参数的调用方式
- 传值调用
- 传址调用
- 传引用调用
- 示例说明
- 使用二级指针/一级指针创建链表时的对比
- 主函数中作此调用
- 使用二级指针创建链表
- 使用一级指针创建链表会成功吗
- 销毁链表时二级指针和一级指针的对比
- 使用二级指针销毁链表
- 使用一级指针销毁链表会成功吗
- 总结
- 完整代码
- 参考来源
前言
在学习数据结构时,在链表初始化或者销毁链表的时候,经常使用二级指针或者一级指针的引用,这是为什么呢?同样是指向内存单元的地址,为什么就不能使用一级指针呢?使用一级指针去初始化或者是销毁链表的时候,究竟会发生什么呢?到底什么时候该用二级指针,什么时候该用一级指针?
如果你对这些问题有疑问,可以参考本篇文章,以下是我个人对这些问题的理解,如有问题,欢迎随时联系我。
参数的调用方式
我们通常使用的函数调用方式无非两种,一种是传值调用,一种是传址调用。
谈起指针我们可能瞬间就会把它和传址调用联系在一起,但实际上,对于指针来讲,它也存在着这两种调用方式,传值调用和传值调用。
传值调用
传值调用是指在调用参数时,不是对原参数进行操作,而是创建参数的拷贝并对其进行操作,这种调用有利于保护数据。
传址调用
传址调用的过程中把函数外部创建的变量的内存地址传递给函数参数,这种调用可以让函数和函数外边的变量建立起联系,函数内部可以直接操作函数外部;
传引用调用
适用于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
链表中为何使用二级指针相关推荐
- 取消对 null 指针“l”的引用。_彻底理解链表中为何使用二级指针或者一级指针的引用...
在用c/c++写数据结构程序时,链表和二叉树中经常需要用到二级指针或者一级指针的引用,那么什么时候用什么时候不用呢? 先看一个简单的c++链表操作程序: [cpp] view plaincopy/* ...
- 个元素前面 个元素放在第i 链表将第j_彻底理解链表中为何使用二级指针或者一级指针的引用...
在用c/c++写数据结构程序时,链表和二叉树中经常需要用到二级指针或者一级指针的引用,那么什么时候用什么时候不用呢? 先看一个简单的c++链表操作程序: [cpp] view plaincopy/* ...
- c语言const 修饰二级指针,C++中const修饰二级指针(从类型‘int**’到类型‘const int**’的转换无效)...
先上代码: void func(const int ** arg) { } int main(int argc, char **argv) { int **p; func(p); return 0; ...
- 表变量是什么_为什么要使用二级指针?
笔者能力有限,如果文中出现错误的地方,欢迎各位朋友给我指出来,我将不胜感激,谢谢~ 概念 提到指针,我们都知道指针是用来存储一个变量的地址.所以,当我们定义了一个指向指针的指针的时候(pointer ...
- 为什么要使用二级指针?
概念 提到指针,我们都知道指针是用来存储一个变量的地址.所以,当我们定义了一个指向指针的指针的时候(pointer to pointer),我们也称之为二级指针,那针对于这个二级指针来说,第一级指针存 ...
- 【C 语言】二级指针案例 ( 字符串切割 | 返回 自定义二级指针 作为结果 | 每个 一级指针 指向不同大小内存 | 精准分配每个 一级指针 指向的内存大小 )
文章目录 一.二级指针案例 ( 返回自定义二级指针 | 精准控制内存大小 ) 二.完整代码示例 一.二级指针案例 ( 返回自定义二级指针 | 精准控制内存大小 ) 博客 [C 语言]二级指针案例 ( ...
- 【C 语言】二级指针作为输入 ( 指针数组 | 将 二级指针 作为函数输入 | 抽象函数业务逻辑 )
文章目录 一.打印 指针数组 中指针指向的字符串 二.字符串排序 三.代码示例 一.打印 指针数组 中指针指向的字符串 打印 指针数组 中指针指向的字符串 : 指针退化问题 : 传入二级指针 , 同时 ...
- 关于链表中经常用到的二级指针
在说明之前,先来看一个栗子: 1 //前略 2 typedef struct Node { 3 int data; 4 struct Node *lchild, *rchild; 5 } *BiTre ...
- 一级指针和二级指以及(void**)在双链表中的应用
因为函数参数是按值传递的,所以要想改变变量,必须传递地址. 二级指针实际上就是指针变量的地址,如果传递二级指针,函数声明必须写**. (void**)&必须是本质上就是指针变量的地址才可以做这 ...
最新文章
- 基于Angularjs+jasmine+karma的测试驱动开发(TDD)实例
- [LeetCode] Plus One
- mysql 转义字符6_MySQL的转义字符
- 计算几何 —— 欧拉公式
- 一个小白的转行Python的经历!
- 【LeetCode】剑指 Offer 59 - II. 队列的最大值
- opencv实现多个图拼接成一个图
- Matlab系列教程_数值计算_求和(积)_求累加(积)
- 电子元器件如何检测和筛选
- Vue项目:电商后台管理管理系统
- 【noi.ac #1779】D
- [线性代数]矩阵(mooc秦静老师讲解)
- 《软件测试》第十二章 文档测试
- [Kafka][错误: 找不到或无法加载主类 Files\Java\jdk1.8.0_101\lib\dt.jar;C:\Program]
- win10任务栏,如何做到图标居中
- nomasp 博客导读:Lisp/Emacs、Algorithm、Android
- SV实验3 子系统验证和测试点划分
- 一年成为Emacs高手(像神一样使用编辑器)
- Python获取与处理文件路径/目录路径
- IjkVideoView播放视频(支持avi格式的视频)