二叉樹創建為什么用二級指針


最近看二叉樹的插入(創建)用的是二級指針,一開始有點困惑,再難的東西它也有個最簡單的原因。

一、理解二級指針

例子1 首先看一個簡單的

#include <iostream>
using namespace std;
int change(int b)
{
    b=10;
    return b;
}
int main()
{
    int a=5;
    change(a);
    cout<<a<<endl;//輸出5
    int c=0;
    c=change(a);
    cout<<c<<endl;//輸出10
}

為什么輸出a=5呢?C語言中函數參數傳遞只能是值傳遞。a這個變量的值,傳遞給b(函數change的局部變量),局部變量b被賦值成5。然后b這個變量 的內容,用10替換5,b就變成10。函數返回值是10。所以c被賦值為函數的返回值10。函數的返回值和對變量的修改沒有關系。函數返回值只是函數的運 算結果。b並沒有獲得對a的修改權限。b和a是不同的兩個變量,a只是把值的內容給了b,之后a和b沒有關系。重要的一點是,如果要修改a的值,b必須獲得對a的修改權限。

例子2 如何獲得對調用者變量的修改權限?

#include <iostream>
using namespace std; int change(int *b) { *b=10; return *b; } int main() { int a=5; int *p=&a; change(p); cout<<a<<endl;//輸出10
   int c=0; c=change(p); cout<<c<<endl;//輸出10 }

這個程序分為以下幾步:

1、為int類型的變量a申請一片地址,初始化為5。(變量a的地址就不再變化了)

2、創建一個指向int類型的指針p,初始化為指向a(p的值是變量a的地址)。

3、為函數chang創建一個指向int類型的指針b。

4、把p的值給b(因為p和b指向的類型都是int,兩者兼容,可以直接賦值)。這樣b也指向a了(因為變量a的地址唯一,b的內容也是a的地址)

5、把b指向的內容修改成10。(也就是a被修改成10)

6、函數返回10,賦值給c。

由例1和例2總結出:若要通過函數B修改函數A中的某個變量a。需要獲得變量a的地址,如果a是普通變量,需要獲得一級指針。如果a是指針,需要獲得二級指針。重點是看需要修改的變量是什么,再去獲得它的指針。

例3 理解地址

#include <iostream>
using namespace std; int main() { int *str= NULL; int **p=NULL; p=&str; cout<<str<<endl; cout<<p<<endl; }

輸出:

0

0x61fe98

p->str->NULL

(int **->int*->int)

NULL是一個宏,#define NULL (void*)0,在C++里面被直接被定義成了整數立即數類型的0。

任何變量都會被分配內存空間。str指向的是NULL,那么str的值就是NULL的地址(NULL的地址被宏定義為0)。但是str在內存中也是有地址的,p的值是str的地址(這里是0x61fe98)。

例4 內存分配

#include <stdio.h> #include <stdlib.h> #include <iostream>
using namespace std; void GetMemory( int *p ) { p = (int *) malloc( 100 ); cout<<p<<endl; } int main() { int *str = NULL; GetMemory(str); cout<<str; return 0; }

輸出

0x12125f8

0

p的值最初被初始化為0,進行p = (int *) malloc( 100 ); 之后,p的值是內存隨機分配的100個字節地址的首地址。str的值還是0。

對程序進行修改:

#include <stdio.h> #include <stdlib.h> #include <iostream>
using namespace std; void GetMemory( int **p ) { *p = (int *) malloc( 100 ); } int main() { int *str = NULL; int **p2=&str; //等價於int **p2; p2=&str;
 GetMemory(p2); cout<<str; return 0; }

輸出

0xe425f8

在main中定義一個指向str的指針p2。把str的地址傳給p。這樣GetMemory函數中的p也指向str(因為變量str的地址是唯一的!),這樣操作*p也就是操作str。

例5 最后看一道牛客網上的一道題http://www.nowcoder.com/profile/826954/myFollowings/detail/1002396

void GetMemory( char *p ) {   p = (char *) malloc( 100 ); //申請100個字節的存放char類型的連續區域
} void Test( void ) {   char *str = NULL;   GetMemory( str );   strcpy( str, "hello world" );   printf( str ); }

這道題答案是str是NULL,如果在main中調用Test,運行會出錯。分析一下:

1、Test中,創建一個指向char類型的指針str,str被初始化為0。

2、函數Test中執行GetMemory( str );

3、創建一個指向char類型的指針p。

4、把str的值(這里是0)傳給p,指針變量p的值也被初始化成0。

5、把申請到的100個字節的內存強制轉換成存放char類型,把所分配內存空間的首地址賦值給p(這時p的值從0變成某地址)。

但是這時候str還是指向NULL。

回到例1說的:若要通過函數GetMemory修改函數Test中的變量str,需要獲得變量str的地址,而不是str的值。

PS:

C語言中的malloc函數(沒有C++中new好用啊):

malloc 函數 void *malloc( unsigned int size)

在內存的動態存儲區中分配一塊長度為"size" 字節的連續區域。

如果分配成功,則返回所分配內存空間的首地址,否則返回NULL,申請的內存不會進行初始化。

類型說明符表示把該區域用於何種數據類型。(類型說明符*)表示把返回值強制轉換為該類型指針。例如: pc=(char *) malloc (100); 

 

二、二叉樹的創建為什么用二級指針

感謝http://lidawn.github.io/pointer-on-pointer/這篇博客。這篇文章,概括一下:

調用者的變量需要被修改內容,這里是root(指向BTreeNode類型的指針),root需要指向一個新插入的節點,也就是需要修改root的值。所以 應該傳入指向root的地址。這樣在被調用的函數中,對*BST的操作等價於操作root。否則BST如果是和root類型一樣的BTreeNode類型 的指針,BST和root位於兩個不同的內存,BST只是被初始化為root的值,之后對BST的操作不會影響root。

//以下代碼來自《大話數據結構》6.9節 //二叉樹的二叉鏈表結點結構定義
define char TElemType typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; }BiTNode,*BiTree; //為BiTNode取別名BiTNode,為BiTNode*取別名BiTree

void CreateBiTree(BiTree *T)//操作*T即可,*T是指向BiTNode的指針
{ TElemType ch; scanf("%c",&ch); if(ch=='#') *T=NULL; else { *T=(BiTNode*)malloc(sizeof(BiTNode)); //這里原來是*T=(BiTree)malloc(sizeof(BiTNode));修改之后便於理解
        if(!=*T) exit(OVERFLOW); //我理解是如果*T還是0(相當於*T還是指向NULL),表示內存分配失敗,就退出
        (*T)->data=ch;//*T指向的節點的data分配內容,即生成根節點
        CreateBiTree(&(*T)->lchild);//創建&(*T)->lchild臨時變量,傳入CreateBiTree,構造左子樹
        CreateBiTree(&(*T)->rchild);//創建&(*T)->rchild臨時變量,傳入CreateBiTree,構造右子樹 //相當於 // BiTNode **p1; // p1=&((*T)->lchild);//不能直接p1=&lchild // CreateBiTree(p1); // BiTNode **p2; // p2=&((*T)->rchild);//不能直接p2=&rchild // CreateBiTree(p2);
 } }
操作*T相當於操作雙親節點的lchild或rchild(lchild或rchild的地址作為實參傳遞給形參T)
對於樹根,沒有雙親,最初傳給T的就是NULL。
函數遞歸本質也是函數調用,之所以CreateBiTree(&(*T)->lchild) ,是因為要傳當前節點的lchild(*T)->lchild的地址&(*T)->lchild給被調用者CreateBiTree。 
這里很重要的是:T是BiTNode**類型。是為了考慮函數的調用者。比如在main中要調用CreateBiTree函數。
 
如果CreateBiTree用BiTNode*類型的形參
void CreateBiTree(BiTNode *T){......} int main(){ struct BiTNode *p=NULL; CreateBiTree(p); }
這樣T會指向一棵二叉樹,但是p還是指向NULL。
 
所以應該傳入二級指針
void CreateBiTree(BiTNode **T){......} int main(){ struct BiTNode **p=NULL; struct BiTNode *b=NULL; p=&b; CreateBiTree(p); }
這樣操作*T也就是操作main中的b。最后b也會指向一棵二叉樹。
 
或者也可以返回指針
BiTNode * CreateBiTree(BiTNode *T)
{
    ......
    return T;
}
int main(){
    struct BiTNode *p=NULL;
    p=CreateBiTree(p);
}
把T(最終指向創建好的二叉樹的樹根,樹根的類型也是BiTNode ), 返回給BiTNode *類型的p。這樣p也指向創建好的二叉樹的樹根。


免責聲明!

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



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