C語言函式傳遞指標引數的問題詳解

weixin_33890499發表於2018-05-10
7709443-65d485e181ae776e

C語言函式傳遞指標引數的問題

一個問題是,我們想用一個函式來對函式外的變數v進行操作,比如,我想在函式裡稍微改變一下這個變數v的值,我們應該怎麼做呢?又或者一個常見的例子,我想利用swap()函式交換兩個變數a,b的值,我們應該怎麼做呢(好吧,博主是覺得這個問題十分經典)。

如果你真的理解C語言中【函式】這個工具的本質,我想你稍微仔細的思考一下,可能就不會來檢視博主的這篇文章,對函式來說,它所傳遞的任何引數僅僅是原來引數的一個拷貝,所以,對任何企圖通過void swap(int a,int b)來交換a,b值或者想通過void alter(int v)來改變v的值,都是徒勞的。

C語言裡,改變值只能通過指標(地址)方式進行傳遞,或許你會說傳遞陣列不是也可以改變值麼,實際上,傳遞陣列就是傳遞指標(或許對陣列來說,這個指標有點特別)//注意:C裡沒有引用,C++裡才有

我們先來看一下有趣的swap函式,它用於交換a,b兩個變數

code case 1

#include

voidswap(inta,intb)

{

    inttemp=a;

    a=b;

    b=temp;

}

intmain()

{

    inta=4,b=5;

    swap(a,b);

    printf("a = %d ,b = %d\n",a,b);

    return0;

}

不出意料的,我們會知道這段程式碼其實並不能得到我們想要的結果,它並不能交換兩個變數a,和b,的值,這是為什麼?

我們不妨修改這段程式碼,在main()和swap()裡分別列印a和b的地址,看看到底發生了什麼;我們修改程式碼如下:

code case 2

#include

voidswap(inta,intb)

{

    printf("address in swap():%p %p\n",&a,&b);

    inttemp=a;

    a=b;

    b=temp;

}

intmain()

{

    inta=4,b=5;

    printf("address in main():%p %p\n",&a,&b);

    swap(a,b);

    printf("a = %d ,b = %d\n",a,b);

    return0;

}

它的執行結果為:

address in main():0061FF2C 0061FF28

address in swap():0061FF10 0061FF14

a = 4,b = 5

顯然,在兩個函式裡,它們的地址並不相同,這意味著,它們並不是相同的儲存空間,改變swap裡的值,實際上僅僅只改變了swap()裡面的a和b的值罷了,一旦swap執行完,swap裡的a和b的儲存空間立即釋放掉,對於main()裡的a和b,沒有半點影響。

7709443-43b3b8bf7b78f858

小編推薦一個學C語言/C++的學習裙【 六二七,零一二,四六四 】,無論你是大牛還是小白,是想轉行還是想入行都可以來了解一起進步一起學習!裙內有開發工具,很多幹貨和技術資料分享!

那麼在C語言裡如何才能交換兩個變數的值呢?

方法是通過指標傳參,看下面的程式碼

code case 3

#include

voidswap(int*a,int*b)

{

    printf("address in swap():%p %p\n",a,b);

    inttemp=*a;

    *a=*b;

    *b=temp;

}

intmain()

{

    inta=4,b=5;

    printf("address in main():%p %p\n",&a,&b);

    swap(&a,&b);

    printf("a = %d ,b = %d\n",a,b);

    return0;

}

執行結果為:

address in main():0061FF2C 0061FF28

address in swap():0061FF2C 0061FF28

a = 5,b = 4

這樣,就把a,b的值交換了!

等等,我們分析一下它的原理,它究竟做了哪些變化呢,在swap函式裡,我們將a和b的地址給了swap函式,作為形參,在swap函式中,a和b是指向兩個int 型別的指標,它們接受了main裡面a和b的地址,也就是a=&a (in main());b=&b (in main());所以對*a實際上就是對a(in main())操作啦;

那麼,聰明的你肯定能想到,在swap()函式裡變數a和b的地址肯定和main裡a和b的地址是不同的,swap裡的a,b的地址是指標的地址(在swap裡a,b是指標),而它們的值是在main()裡面a和b的地址;

我們不妨列印一下swap裡a,b和地址就明白了;

code case 4

#include

voidswap(int*a,int*b)

{

    printf("address in swap(),the value of a and b:%p %p\n",a,b);

    printf("address in swap(),the address of a and b:%p %p\n",&a,&b);

    inttemp=*a;

    *a=*b;

    *b=temp;

}

intmain()

{

    inta=4,b=5;

    printf("address in main(),the address of a and b:%p %p\n",&a,&b);

    swap(&a,&b);

    printf("a = %d ,b = %d\n",a,b);

    return0;

}

執行結果:

address in main(),the address of a and b:0061FF2C 0061FF28

address in swap(),the value of a and b:0061FF2C 0061FF28

address in swap(),the address of a and b:0061FF10 0061FF14

a = 5,b = 4

通過結果我們知道,在swap裡,指標a和b的值和main()裡的a和b的地址是一樣的,那麼對*a進行的各種賦值實際上就是對main()裡的a的各種操作,它們代表同一儲存空間的的值;但是對swap裡的a,和b的地址,和main裡的是不一樣的,這是顯然的,a只是一個容納&a地址的變數罷了,它是swap裡重新分配的一塊記憶體,並且,它的型別和main裡的a,b型別完全不同,它是一個指標型別;

用比喻的方法來講,在多行書架上,每行各自放了一本書,現在我想把a,b這兩本書交換一下位置,我想讓你幫我交換一下,你要怎麼做呢?好了,我告訴你a書在第一行書架上,b在第四行書架上,現在,你可以做了吧,你首先會取出第一行的a書,將其拿出放在左手,然後用右手取出位於第四行的b書,放在第一行上,再將位於左手上的a書放到第四行上,至此,交換完成。

仔細想想,在這個過程中你不就相當於充當了swap這個函式的作用嗎?我告訴了你a,b的地址,你真正交換了它

當然,我想探討的並不是只有這些,在文章一開始,我就引入了這樣的話題,我們先看一下這段程式碼問題:

#include

#include

typedef struct LNode

{

    intdata;

    struct LNode *next;

}LNode;

voidInitLinkList(LNode *L)

{

    L=(LNode *)malloc(sizeof(LNode));

    L->data=0;

    L->next=NULL;

}

intmain()

{

    LNode *L=NULL;

    InitLinkList(L);

    printf("%p\n",L);

    return0;

}

問:該程式碼能否正確初始化一個連結串列頭結點?

我想,如果你能正確理解前面的幾個例子,那麼,你的答案一定回答的是NO,該InitLinkList並不能真正初始化一個連結串列頭結點,在函式裡我們的確是給L分配了記憶體,初始化了結點,但是,InitLinkList()裡的L並不是main()裡的L,雖然名稱是一樣的,但是InitLinks()的L是區域性的(所以,其實你寫成a,b,c,d都沒關係),傳進來的只是一個LNode*副本,這個副本和外面的L的內容是一樣的,但是變數不是同一個,當這個子函式執行完後,main()裡的L還是原來的L。

(注意!在InitLinkList函式中通過malloc分配的記憶體是通過堆來劃分的,這意味著函式呼叫完畢後,記憶體不能自動釋放,將會造成記憶體洩漏,並且,此程式碼中malloc申請的記憶體是懸浮的)

但是,在大多數時候,我們卻的確是需要這樣一個函式來為我們做這些事情,那麼,應該怎麼修改呢?

修改程式碼如下

#include

#include

typedef struct LNode

{

    intdata;

    struct LNode *next;

}LNode;

LNode * InitLinkList(LNode *L)

{

    L=(LNode *)malloc(sizeof(LNode));

    L->data=0;

    L->next=NULL;

    returnL;

}

intmain()

{

    LNode *L=NULL;

    L=InitLinkList(L);

    printf("%p\n",L);

    return0;

}

執行結果

?

1006D1588

改過後的InitLinkList初始化了頭結點,並把生成節點的地址傳遞給上一層的main中的L,所以得到了正確的結果

(實際上,寫成InitLinkList(LNode *L)可能不是一種必要的方式,完全可以寫成void,這兩者是等價的)

對比交換a,b值那樣,我們也可以這樣改

#include

#include

typedef struct LNode

{

    intdata;

    struct LNode *next;

}LNode;

voidInitLinkList(LNode **L)

{

    (*L)=(LNode *)malloc(sizeof(LNode));

    (*L)->data=0;

    (*L)->next=NULL;

}

intmain()

{

    LNode *L=NULL;

    InitLinkList(&L);

    printf("%p\n",L);

    return0;

}

執行結果

?

1006B1588

(注:採用此種方式是及其複雜的,因為這是一個二級指標,會增加理解難度,所幸的是,c++中的引用,可以避免這個問題)

想一下,為什麼,

在swap(int *a,int *b)中,a,b是指標變數,它們自身有個地址&a,&b,a,b的內容是存放傳遞過來的變數地址(也即是main()裡a,b的地址),用*a和*b就是取當前所存放地址的值,也就是說,a,b指向main裡a,b的記憶體塊地址,*a和*b相當於是直接對main()裡的a,b其操作,所以能到達交換(改變值)的目的是顯然的。

在這裡main裡的L是一個指標型別,本身就指向某塊記憶體,而L這個變數的地址作為內容傳遞給initLinkList()函式,那麼子函式裡面的L的內容不就是main()裡L地址麼,那麼,*L不就是main裡L的內容麼,也就是說,對*L操作就是對main()裡的L進行操作。

相關文章