一、二重指針傳參意義(函數體內malloc申請內存泄漏與變量引用的就近原則)

譚浩強C程序設計講到,如果想交換兩個變量a,b的值,我們會這麼做:

func(int* a,int* b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = *a;
}
func(&a,&b);

因爲C語言單向“值傳遞”就這樣規定的,想改變外部實參a和b的值,你就要傳實參的地址纔行,沒有可理解的,你就是要按照規定來,就像沒一個漢字讀什麼,都是最初的規定者規定的。

指針就有這個特權,當函數參數是一個一重指針的時候,那麼傳入的就應該是變量的地址。

那麼在函數的內部就可以直接通過修改該地址中存放的值。這樣就能修改到外部變量的值了。

現在進一步思考,如果函數內部不是修改變量的值,而是指針的值及變量地址的值,又會怎樣呢?沒錯這回就得用到二重指針。

可能有人會問,修改指針的值是想幹嘛?修改指針的值的另一種說法就是:

1、將A指針指向另一個B指針變量

1.1 譬如int c=5;int* A=NULL;int *B=c;你現在想將A指向指針B,一般我們直接使用A=B;即可,如果你想封裝成函數呢?這時就要傳入指針A的地址,才能改變A的指向。

func(int *B,int** A)
{
    (*A)=B;
}
void main()
{
    int c=5;int* A=NULL;int *B=c;
    func(B,&A);//這樣其實等同於A=B;你會問爲什麼一行代碼可以解決的還需要函數封裝?是爲了模塊化,是因爲這個例子比較簡單
}

1.2 如果你形參定義成int *A,傳入func(A,B)則無法改變指針A的指向。比如 int* p = a,如果需要將其封裝成函數,就會造成在函數中修改指針值的情況。假設一個功能函數需要爲指針分配一個新的空間:

func(int* a)
{
    a = malloc(10);
}
void main()
{
    int* p;
    func(p);
}

func(p)執行完了之後,指針p仍然沒有分配到新的空間。變的只是函數內部臨時變量指針a的值,p並沒有改變。

要想改變P的值:有如下2種方式:

(1)那麼要傳入的參數就是指針的地址值:&p 。func(&p);這樣函數內部就能修改外部指針的值。

func(int** a)
{
    (*a) = malloc(10);
}
void main()
{
    int *p = NULL;
    func(&p);
}

(2)使用函數返回值,因爲函數返回時,返回值還是有效的,所以我們在該函數外部定義一個指針p,去接收這個func函數返回的臨時指針,接受完之後,該func函數就結束了,裏面的臨時變量和返回值都內存釋放了,因爲他們是放在棧上,而malloc申請的內存不會釋放,因爲在堆上,但是指針變量a會釋放,這是就會造成內存泄漏,因爲現在指向malloc的指針a出函數體後,a會釋放,所以這時需要外部通過一個指針去接收,釋放是也用這個指針p。

char *func(void)
{
    char *a=(char *)malloc(100);
    return a;
}
void main()
{
    char *p=NULL;
    p = Getmemery();
    strcpy(p,"hello world");
    printf("%s",p);
    free(p);
}

總結:

函數內部修改外部變量的值,需要一重指針;

函數內部修改外部指針變量的值,需要二重指針;

2 變量的引用過程

如下代碼中,定義了一個全局指針變量B和一個main函數內的局部變量B,首先要知道這是可以的,因爲變量作用域不同,main函數中的臨時變量B當main函數結束後就會釋放,而全局變量的作用域是整個文件中的所以函數,這時問題就來了

(1)**A的打印值是多少?

第13行指針B指向變量c

第16行將指針(*A)指向指針B       //注意A是一個二重指針,所以將(*A)看成一個整體就是一重指針,也就是將二重指針進行                                                             一次解引用就變成了一重指針

第15行打印(**A)                          //**A就是兩次解引用後,變成了變量的數組,就是一重指針(*A)的解引用

實際上**A的值爲8,變量的引用過程,類似於棧,就是說編譯器從變量引用處以最近邏輯距離開始搜索,找到後則開始使用,然後停止搜索;如果查找失敗則編譯出錯。舉個例子,就是優先引用函數內部的,其次引用同一個源文件中全局的,如果函數內部、同一個源文件中沒有,再搜索包含的頭文件(譬如另一個文件中external int *p聲明,然後其他文件包含了這個文件)

如下代碼:func功能函數形參沒有出現指針B,而func裏面卻使用了B,則這個B就會使用全局變量B,即第5行代碼的指向。雖然第14行執行時,第13行對B進行了操作,但沒有意義,因爲func形參沒有B,所以他會找全局變量B,你會問不是說就近原則嗎?13行與14行纔是最近的。要知道這裏的就近是在函數定義時,你想啊,爲什麼func函數裏面使用了指針變量B能通過?是因爲main函數裏面12行代碼定義的嗎,不是的,而是第5行代碼定義了,所以func纔不會報錯,所以就近原則是指第5/6行。而如果將func改成void func(int *B,int **A),就不一樣了,因爲形參B出現,這是第13行傳入的B就有意義了,func函數裏面出現的B就會聽從形參傳過來的變量,而形參中的變量是在13行定義的,就近原則是指第13/14行。

1   #include "stdio.h"
2   #include <string.h>
3   #include <unistd.h>

4   int C = 8;
5   int *B = &C;                
6   void func(int *d,int **A);


7   void main()
    {
8	   int c = 6;
9	   int *d[5] = {NULL};
10	   int **A = NULL;
11	   A = d;
12	   int *B;
13	   B = &c;
14	   func(B,A);
15	   printf("%d%d",**A,*A[1]);

    }
    void func(int *d,int **A)
    {
16	   A[0] = B;
17	   A[1] = B;
    }

 

3 爲什麼我會使用**A,與*A[1],因爲他們能替換,*A == A[0],A是二重指針,所以A[0]一重指針,對一重指針解引用不就是*A[0]嗎

如果代碼如下:首先func(int *B,int **A),這時全局B無效,第4行改成了一重指針A,所以我們要傳入&A,即指針取地址,就是二重指針

疑問1:爲什麼A[0] = B,B是指針,第4行定義的A是指針,A[0]是啥?

#include "stdio.h"
#include <string.h>
#include <unistd.h>

1int C = 8;
2int *B = &C;
void func(int *d,int **A);


void main()
{
3	int c = 6;
4	int *A = NULL;
5	int *B;
6	B = &c;
7	func(B,&A);
8	printf("%d%d\n",A[0],A[1]);
9	printf("%p\n",A);	
	printf("%p\n",&A[1]);

}
void func(int *B,int **A)
{
10	A[0] = B;
11	A[1] = B;
}

這時要注意,A[0]中的A是一個二重指針,他是形參,是外部的指針A取地址傳入的,不等價,所以A[0] = B後,外部的指針A被改變了,因爲我們傳了指針A的地址進去,儘管第4行指針A是一個一重指針,要改變他的指向就要就要傳入他的地址,記住這個就行了,而A[1] = B卻無效,A[1]只是函數內臨時分配的一個指針變量,因爲我們傳入了指針A的地址,可以說A[1]已經存在了,就在&A賦值時定義了,但是他在外部沒有分配空間,而第10行A[0]就是外部傳入的一重指針A,而第8行中的A[0]與第10行的A[0]不一樣,第8行就是代表第4行的指針A的解引用,也可以寫成*A,而第8行的A[1]的值是未知的,打印後,得到如下圖,指針是後續的,但是裏面的值是隨機的,func函數裏面的A[1]指向了指針B,但是A[1]本身是一個臨時變量,沒有再外部定義實體,沒有外部實體與之對應,而A[0]對應的就是指針A。所以打印A[1]的值時是隨機的,而指針的地址&A[1]與A是連貫的。

4 創建二叉樹時傳參問題

(1)如下代碼中,Createtree4(BinaryTree* p,char *str,int *index),我們傳入的是二重指針p,和一重指針index,爲什麼?因爲我們要改變一重指針(*p)的指向以及變量(*index)的值,所以傳入他們的地址進入才能改變,一重指針的地址就是二重指針。

typedef struct BtNode
{
    struct BtNode *leftchild;
    struct BtNode *rightchild;

    char Ltag;char Rtag;
    ElemType data;
}BtNode,*BinaryTree;

void Createtree4(BinaryTree* p,char *str,int *index)
{    

    if(str[*index] != '#')
    {

        i++;
        (*p) = Buynode();
        (*p)->data = str[*index];
        *index += 1;
        printf("T->data = %c\n",(*p)->data);
        Createtree4(&(*p)->leftchild,str,index);

        Createtree4(&(*p)->rightchild,str,index);        
    } 
}

(2)如下代碼中序遍歷進行中序線索化,傳入的是一重指針p,爲什麼?因爲線索化二叉樹之前已經創建過二叉樹了,也就是下面的代碼是以上面的代碼爲基礎,所以線索化不需要再使用malloc申請內存了,不需要改變一重指針的指向,p一直指向的是之前創建二叉樹時指向的malloc申請的內存地址,線索化只是改變這個內存地址裏面元素的值,所以一重指針的傳入正是爲了改變值對吧,比如p->Ltag = 1;等操作

void InTree(BinaryTree p)
{
    if(p)
    {
        InTree(p->leftchild);
        if(!p->leftchild)
        {
           p->Ltag = 1;
        }
        InTree(p->rightchild);
    }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章