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來替換
typedef
struct
_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 }