引言###
在數據結構的學習過程中,有時候會遇到一些一時無法理解的問題,深究起來卻是語言的底層的語法機制所限制.
就例如在鏈表的構建中,鏈表的初始化和銷毀為何需要使用一個二級指針,而不是只需要傳遞一個指針就可以了,其問題的關鍵就在於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. 總結###
- 參數傳遞時,在需要改變指針指向的時候需要傳遞二級指針,例如初始化和銷毀鏈表
- 一二級指針的圖片對比如下圖