链表初始化为何使用二级指针的解释(指向指针的指针)
引言
在数据结构的学习过程中,有时候会遇到一些一时无法理解的问题,深究起来却是语言的底层的语法机制所限制.
就例如在链表的构建中,链表的初始化和销毁为何需要使用一个二级指针,而不是只需要传递一个指针就可以了,其问题的关键就在于c语言的参数传递的方式是值传递
那么,这篇文章就来聊一聊在链表的初始化中一级指针的传递和二级指针的区别.
一级指针和二级指针的区别
1.前提知识:c语言中参数传递的方式是值传递和地址传递
- 值传递:传递的是实参的副本,即形参是一个新开辟的类型一样,里面的内容一样,地址不一样的一个变量,主函数和被被调用函数用的是不一样的内存空间
- 地址传递:传递的是一个地址,实际上主函数和被调用函数共用的是同一块内存空间,被调用函数依然需要一个新的指针来保存起来这块地址
2.传递一级指针:无法对原指针指向第地址进行修改
一级指针实例:
#include <stdio.h> #include <stdlib.h> #define MaxSize 100 typedef int ElemType; typedef struct SingleNode{ ElemType data; struct SingleNode *next; }SingleNodeList,*Linklist; void LinkedListInit(SingleNodeList *head){//用一个指针head接收传入的地址 Linklist p; if((head=(SingleNodeList *)malloc(sizeof(SingleNodeList)))==NULL){ exit(1);//给head分配内存,改变了head指针指向的地址(注意这里的head只是LinkedListInit的head,不是主函数那个) } head->next=NULL; }//这个函数结束后head被销毁了,主函数的那个head不变; int LinkedList_PushFront(SingleNodeList *head,ElemType x){//2单链表头插入 SingleNodeList *q; if((q=(struct SingleNode *)malloc(sizeof (struct SingleNode)))==NULL){ exit(1); } q->data=x; q->next=head->next;//头节点的数据域与指针域赋值 head->next=q;//头节点加入链表 return 1; } int LinkedList_PopFront(SingleNodeList *head,ElemType *x){//3单链表头删除 SingleNodeList *p=head,*q; if(p->next==NULL){ printf("There is no data in the Linkedlist to delete.\n"); *x = -12345;//未成功删除则在x指向单元赋特定值 return 0; } p=head->next; q=p; head->next=p->next; *x=q->data; free(q); return *x;//请填写多行代码 } int LinkedListGet_current(SingleNodeList *p,ElemType *x){//4取当前指针指数据 *x =p->data; return 1; } int LinkedListUpdata_current(SingleNodeList *p,ElemType x){//5修改当前指针数据 p->data=x; return 1; } int LinkedListShow(SingleNodeList *head){//6打印单链表 SingleNodeList *p=head; if(p->next==NULL){ printf("There is no data in the Linkedlist to print.\n"); return 0; } while(p->next!=NULL){ printf("%d ",p->next->data); p=p->next; } printf("\n"); return 1; } void LinkedListDestroy(SingleNodeList **head){//7释放链表 SingleNodeList *p=*head,*q; while(p!=NULL){ q=p; p=p->next; free(q); } *head=NULL; } int LinkedListLength(SingleNodeList *head){//8求单链表长度 SingleNodeList *p=head; int size=0; while(p->next!=NULL){ size++; p=p->next; } return size; } int main(){ SingleNodeList *head,*p; ElemType i,x; int switch_num; scanf("%d",&switch_num); switch(switch_num){ case 1: LinkedListInit(head); //传入指针变量head的地址 LinkedList_PushFront(head,1); LinkedList_PushFront(head,3); LinkedList_PushFront(head,2); LinkedListShow(head); break; } LinkedListDestroy(&head); return 0; }
传递流程如图所示
从图中可以看出,main函数中我们定义了 一个指针head,假设它的地址是0x10010,但是还没给它初始化,也就是说它存的地址是随机的,我们也假设它存的是0x12306
在main函数中,我们把head这个指针作为参数传递进去初始化函数(值传递),按照值传递的原则,初始化函数首先开辟了一个*head指针,它的地址是0x12345(与main函数的0x10010不一样),但是它的内容是是和主函数的head是一样的,都是指向0x12306这个地址
在初始化的过程中,我们用malloc函数对初始化函数内的head指针分配内存空间,也就是改变了head指针的值,由未初始化的随机值0x12306改变成了0x10086
也就是说,由于这个head是作用在初始化函数内的,mallo作用的不是主函数的head,初始化函数结束后,这个head指针就被销毁掉了,主函数中的head不受影响,初始化失败,而分配了内存不能使用,造成了内存泄漏
3. 传递二级指针:对指针指向的内容进行操作,head销毁后无影响
二级指针传递实例:
#include <stdio.h> #include <stdlib.h> #define MaxSize 100 typedef int ElemType; typedef struct SingleNode{ ElemType data; struct SingleNode *next; }SingleNodeList,*Linklist; void LinkedListInit(SingleNodeList **head){//1初始化有头节点的单链表 Linklist p; if((*head=(SingleNodeList *)malloc(sizeof(SingleNodeList)))==NULL){ exit(1); } (*head)->next=NULL; } int LinkedList_PushFront(SingleNodeList *head,ElemType x){//2单链表头插入 SingleNodeList *q; if((q=(struct SingleNode *)malloc(sizeof (struct SingleNode)))==NULL){ exit(1); } q->data=x; q->next=head->next;//头节点的数据域与指针域赋值 head->next=q;//头节点加入链表 return 1; } int LinkedList_PopFront(SingleNodeList *head,ElemType *x){//3单链表头删除 SingleNodeList *p=head,*q; if(p->next==NULL){ printf("There is no data in the Linkedlist to delete.\n"); *x = -12345;//未成功删除则在x指向单元赋特定值 return 0; } p=head->next; q=p; head->next=p->next; *x=q->data; free(q); return *x;//请填写多行代码 } int LinkedListGet_current(SingleNodeList *p,ElemType *x){//4取当前指针指数据 *x =p->data; return 1; } int LinkedListUpdata_current(SingleNodeList *p,ElemType x){//5修改当前指针数据 p->data=x; return 1; } int LinkedListShow(SingleNodeList *head){//6打印单链表 SingleNodeList *p=head; if(p->next==NULL){ printf("There is no data in the Linkedlist to print.\n"); return 0; } while(p->next!=NULL){ printf("%d ",p->next->data); p=p->next; } printf("\n"); return 1; } void LinkedListDestroy(SingleNodeList **head){//7释放链表 SingleNodeList *p=*head,*q; while(p!=NULL){ q=p; p=p->next; free(q); } *head=NULL; } int LinkedListLength(SingleNodeList *head){//8求单链表长度 SingleNodeList *p=head; int size=0; while(p->next!=NULL){ size++; p=p->next; } return size; } int main(){ SingleNodeList *head,*p; ElemType i,x; int switch_num; scanf("%d",&switch_num); switch(switch_num){ case 1: LinkedListInit(&head); LinkedList_PushFront(head,1); LinkedList_PushFront(head,3); LinkedList_PushFront(head,2); LinkedListShow(head); break; } LinkedListDestroy(&head); return 0; }
如图所示,如果传递的是二级指针就不一样了,首先我们在main函数中的操作是和一级指针差不多,只是传递的时候传递的不是一个指针变量,而是这个指针变量的地址(地址传递),但是在初始化函数中我们接收这个地址的是用一个二级指针,也就是用一个head指针指向传递主函数那个head指针的地址,从而来进行对初始化函数中head指针内容的改变去影响到主函数中的head的指向
如图所示,我们在mian函数中传递了一个head的地址,也就是0x10010,这个地址是指向0x12306指针的地址
在初始化函数中,我们用一个另外的head指针接受这块地址,也就是个二级指针,第一级是0x10010,指向0x12305),第二级是0x12345,指向0x10010
在初始化函数中,malloc函数操作的对象是head指针内的内容,也就是0x10010这块指针(主函数中的head指针),这样成功改变了主函数中的head指向的地址,而在销毁初始化函数的head指针的时候,主函数的的head指针不受影响
4. 总结
- 参数传递时,在需要改变指针指向的时候需要传递二级指针,例如初始化和销毁链表
- 一二级指针的图片对比如下图