C語言函式傳遞指標引數的問題詳解
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,沒有半點影響。
小編推薦一個學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進行操作。
相關文章
- 【C語言】函式的概念和函式的呼叫(引數傳遞)C語言函式
- C語言 函式指標C語言函式指標
- 傳遞指標引數(函式內部給指標賦值)示例指標函式賦值
- C語言 將函式(有參、無參)作為引數傳遞C語言函式
- 對 “C語言指標變數作為函式引數” 的個人理解C語言指標變數函式
- 二級指標,二維陣列函式引數傳遞指標陣列函式
- c語言函式指標的定義C語言函式指標
- C語言指標詳解(一)C語言指標
- C語言指標詳解(二)C語言指標
- 將函式作為引數傳遞解決非同步問題函式非同步
- 函式的引數傳遞函式
- Python語法—函式及引數傳遞Python函式
- C語言函式指標與回撥用函式C語言函式指標
- Go語言Slice作為函式引數詳解Go函式
- C語言指標常見問題C語言指標
- JavaScript函式傳遞引數JavaScript函式
- c 語言指標操作經典問題指標
- python中函式的引數傳遞Python函式
- C 語言回撥函式詳解函式
- C語言函式指標與回撥函式使用方法C語言函式指標
- C語言可變引數詳解C語言
- C#語言函式遞迴C#函式遞迴
- 函式引數傳遞及返回函式
- 函式作為引數傳遞函式
- Python的函式引數傳遞:傳值?引用?Python函式
- 函式呼叫引數變數傳值的問題函式變數
- php函式引用傳遞引數的方法PHP函式
- C語言指標(二) 指標變數 ----by xhxhC語言指標變數
- js中 函式引數的 傳值/傳引用 問題JS函式
- C語言語法基礎--S2函式和指標C語言函式指標
- 函式引數詳解函式
- c++指標傳遞與引用傳遞C++指標
- GO語言————6.3 傳遞變長引數Go
- C語言指標C語言指標
- C語言關於指標,gets()和gets_s()函式的理解C語言指標函式
- C語言使用函式引數傳遞中的省略號:va_list, va_start, va_arg, va_endC語言函式
- 【不在混淆的C】指標函式、函式指標、回撥函式指標函式
- String型別函式傳遞問題型別函式