徹底理解鏈表中為何使用二級指針或者一級指針的引用


 徹底理解鏈表中為何使用二級指針或者一級指針的引用

http://blog.csdn.net/u012434102/article/details/44886339

 

struct _node 

 

void*data; 

struct_node *prior; 

struct_node *next; 

}

typedef_node Node;   //給這個_node結構體定義一個別名,任何使用_node的地方都可以用Node來替換

typedef_node* PNode;   //給這個指向_node結構體的指針類型_node*定義一個別名,任何使用_node*的地方都可以用PNode來替換

 

typedefstruct_node 

   void*data; 

   struct_node *prior; 

   struct_node *next; 

} Node,*PNode; 


PNode中P其實就是pointer的意思,以后你可以直接這么使用他:

 

PNode a;

相當於

Node *a;

又相當於:

struct_node *a;

在用c/c++寫數據結構程序時,鏈表和二叉樹中經常需要用到二級指針或者一級指針的引用,那么什么時候用什么時候不用呢? 
先看一個簡單的c++鏈表操作程序:

#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; }

 

結果: 
這里寫圖片描述

得出結論:

1,初始化鏈表頭部指針需要用二級指針或者一級指針的引用。

2,銷毀鏈表需要用到二級指針或者一級指針的引用。

3,插入、刪除、遍歷、清空結點用一級指針即可。

分析: 
1,只要是修改頭指針則必須傳遞頭指針的地址,否則傳遞頭指針值即可(即頭指針本身)。這與普通變量類似,當需要修改普通變量的值,需傳遞其地址,否則傳遞普通變量的值即可(即這個變量的拷貝)。使用二級指針,很方便就修改了傳入的結點一級指針的值。 如果用一級指針,則只能通過指針修改指針所指內容,卻無法修改指針的值,也就是指針所指的內存塊。所以創建鏈表和銷毀鏈表需要二級指針或者一級指針引用。

2,不需要修改頭指針的地方用一級指針就可以了,比如插入,刪除,遍歷,清空結點。假如頭指針是L,則對L->next 及之后的結點指針只需要傳遞一級指針。

3,比如一個結點p,在函數里要修改p的指向就要用二級指針,如果只是修改p的next指向則用一級指針就可以了

函數中傳遞指針,在函數中改變指針的值,就是在改變實參中的數據信息。但是這里改變指針的值實際是指改變指針指向地址的值,因為傳遞指針就是把指針指向變量的地址傳遞過來,而不是像值傳遞一樣只是傳進來一個實參副本。所以當我們改變指針的值時,實參也改變了。

仔細看函數InitList2(LinkList *L) 可以發現,在該函數中改變了指針的指向,也就是改變了指針自身的值。對比一下按值傳遞,這里的"值"是一個指針,所以我們要想指針本身的改變可以反映到實參指針上,必須使用二級指針。

下面通過看一個例子來理解:

#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; }

結果: 
這里寫圖片描述

分析:

在fun1中,當調用str = new char[5]時,str和s已經沒什么關系了,相當於在fun1中復制了一個指針,這個指針指向的空間存儲了字符串“test string”,但s仍指針NULL。當調用fun2時,因為是二級指針,s指向str,這里*str = new char[5],*str就是s,所以給*str分配空間就是給s分配空間。這樣解釋應該就很清楚了。

畫圖為例:

fun1執行時 
這里寫圖片描述
fun2執行時 
這里寫圖片描述
如圖所示,在fun1種str是s的拷貝,給str分配空間跟s沒有關系,在fun2種str是二級指針,指向s,能夠通過控制*str從而給s分配空間。

后記

用框圖表示鏈表中二級指針或者一級指針的使用更加直白了。

1,二級指針創建頭指針。

a.只有頭指針,沒有頭結點 
這里寫圖片描述
b,有頭指針,也有頭節點 
這里寫圖片描述
c,而如果不用二級指針,直接傳一個一級指針,相當於生成L的拷貝M,但是對M分配空間與L無關了。 
這里寫圖片描述
2,二級指針銷毀頭指針 
這里寫圖片描述
無論有沒有頭節點都要用二級指針或者一級指針的引用傳參來銷毀。

3,二級指針與一級指針方式插入結點 
這里寫圖片描述
傳二級指針就是在從鏈表頭指針開始對鏈表操作,傳一級指針只不過是對頭結點L生成了一個拷貝M,M的next指向的仍然是L的next,因此,后面的操作仍然是在原鏈表上操作。

4,二級指針與一級指針方式刪除結點 
這里寫圖片描述
刪除的原理與插入一樣。

注意:

在沒有傳入頭結點的情況下必須使用二級指針,使用一級指針無效。

例如:

void insert(Node *p) { //do something to change the structure } void fun(Node *T) { Node *p; insert(p) //OK,the head T is in } int main() { Node *T; fun(T); //OK,the head T is in }

因為fun函數里傳入了數據結構的頭指針(鏈表,二叉樹都可以),在這個函數里面的insert函數形參可以是一級指針。

但是如果在main函數里直接單獨對數據結構中某一個結點操作就不能用一級指針了。

void insert1(Node *p) { //do something to change the structure } void insert2(Node **P) { //do something to change the structure } int main() { Node *p; insert1(p); //error insert2(&p); //OK }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM