C語言函數傳遞指針參數的問題

這貨寫的真詳細,並且通俗易懂。看到的有福氣了0.0呵呵

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

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

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

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

code case 1

#include <stdio.h>
void swap(int a,int b)
{
    int temp=a;
    a=b;
    b=temp;
}
int main()
{
    int a=4,b=5;
    swap(a,b);
    printf("a = %d ,b = %d\n",a,b);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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

我們不妨修改這段代碼,在main()和swap()裏分別打印a和b的地址,看看到底發生了什麼;我們修改代碼如下: 
code case 2

#include <stdio.h>
void swap(int a,int b)
{
    printf("address in swap():%p %p\n",&a,&b);
    int temp=a;
    a=b;
    b=temp;
}
int main()
{
    int a=4,b=5;
    printf("address in main():%p %p\n",&a,&b);
    swap(a,b);
    printf("a = %d ,b = %d\n",a,b);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

它的運行結果爲:

address in main():0061FF2C 0061FF28
address in swap():0061FF10 0061FF14
a = 4 ,b = 5
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

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

我們舉一個簡單的例子,你有一個包子,你拿着這個包子給包子師傅看,然後包子師傅照着你這個包子的形狀重新做了一個跟你的包子一模一樣的包子,然後包子師傅馬上吃掉了它,你覺得你的包子被吃掉了嗎?當然沒有!你的包子還在自己手裏。

那麼在C語言裏如何才能交換兩個變量的值呢? 
方法是通過指針傳參,看下面的代碼 
code case 3

#include <stdio.h>
void swap(int *a,int *b)
{
    printf("address in swap():%p %p\n",a,b);
    int temp=*a;
    *a=*b;
    *b=temp;
}
int main()
{
    int a=4,b=5;
    printf("address in main():%p %p\n",&a,&b);
    swap(&a,&b);
    printf("a = %d ,b = %d\n",a,b);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

運行結果爲:

address in main():0061FF2C 0061FF28
address in swap():0061FF2C 0061FF28
a = 5 ,b = 4
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

這樣,就把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 <stdio.h>
void swap(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);
    int temp=*a;
    *a=*b;
    *b=temp;
}
int main()
{
    int a=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);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

運行結果:

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
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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

用比喻的方法來講,main函數裏的a,b,就好比是你手中的包子,swap裏的a,b就好比是包子師傅拿來用來盛放你手中包子的盤子,而*a和*b是你將自己手中的包子放到包子師傅盤子裏的包子;包子師傅吃掉了盤子中的包子,你覺得你還有包子可吃嗎?#huakji

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

#include <stdio.h>
#include <malloc.h>
typedef struct LNode
{
    int data;
    struct LNode *next;
}LNode;
void InitLinkList(LNode *L)
{
    L=(LNode *)malloc(sizeof(LNode));
    L->data=0;
    L->next=NULL;
}
int main()
{
    LNode *L=NULL;
    InitLinkList(L);
    printf("%p\n",L);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

問:該代碼能否正確初始化一個鏈表頭結點? 
我想,如果你能正確理解前面的幾個例子,那麼,你的答案一定回答的是NO,該InitLinkList並不能真正初始化一個鏈表頭結點,它的工作其實是,在函數裏初始化了頭結點,但是,隨着函數的結束,釋放掉了所有的空間,並沒有對main裏的L起到了任何作用;

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

#include <stdio.h>
#include <malloc.h>
typedef struct LNode
{
    int data;
    struct LNode *next;
}LNode;
LNode * InitLinkList(LNode *L)
{
    L=(LNode *)malloc(sizeof(LNode));
    L->data=0;
    L->next=NULL;
    return L;
}
int main()
{
    LNode *L=NULL;
    L=InitLinkList(L);
    printf("%p\n",L);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

運行結果

006D1588
  • 1
  • 1

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

我們也可以這樣改

#include <stdio.h>
#include <malloc.h>
typedef struct LNode
{
    int data;
    struct LNode *next;
}LNode;
void InitLinkList(LNode **L)
{
    (*L)=(LNode *)malloc(sizeof(LNode));
    (*L)->data=0;
    (*L)->next=NULL;
}
int main()
{
    LNode *L=NULL;
    InitLinkList(&L);
    printf("%p\n",L);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

運行結果

006B1588
  • 1
  • 1

那麼,這又是怎麼回事呢? 
仔細思考一下,我們傳遞的參數是main裏指針L的地址,在InitLinklist裏,*L是什麼呢?L又是什麼呢?如果你認真查看了上面的代碼,加上我之前的敘述,你應該會知道,L實際上就是main裏&L的拷貝,L的值肯定是和&L是不一樣的,但這沒關係,因爲*L和L是相同的,所以,你瞭解我這句話的含義了麼?

發佈了77 篇原創文章 · 獲贊 110 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章