第五章 指針與函數(shù)

一說到指針和函數(shù)的關(guān)系,很多人就會想到指針作為函數(shù)的參數(shù)。但是,可能很少有人會注意指針作為函數(shù)的參數(shù)時的真正意義。也許我們自己的程序很少用指針作為函數(shù)的返回值,但是,它確實存在,而且意義非凡。這部分將介紹指針和函數(shù)之間很少被人注意的關(guān)系。

5.1 函數(shù)的參數(shù)

函數(shù)的參數(shù)的傳遞方式無外乎兩種,一是傳值一是傳址。一般的教課書上都認為數(shù)組和指針都是傳址調(diào)用,而只有普通的數(shù)據(jù)類型(如整型、結(jié)構(gòu)體等)才是傳值調(diào)用。但,我并不這么認為。

我認為只有顯示調(diào)用傳值調(diào)用符號&的變量才是傳址調(diào)用,其他的都是傳值調(diào)用。對于指針和數(shù)組來說傳的值是指針的值/數(shù)組首元素的地址,此時,再利用指針/數(shù)組去訪問元素則是相當于操作原先元素的地址了,效果和把原先元素的地址傳遞過來是一樣的,但在形參和實參結(jié)合的時候確實進行了一次賦值,而這個值是指針的值,并且指針的值又是原先元素的地址。

變量都有其地址,機器都是通過其地址引用變量的。傳值是指將實參的值賦給形參,而形參和實參的地址顯然是不同的,他們的作用域也不同,形參只作用于該函數(shù),并存儲于堆棧中,在堆棧中占有一個固定的地址。在形參和實參匹配時,會把堆棧中這個地址的內(nèi)容修改成實參的值,這就是傳值。

傳址則是指在形參和實參匹配時,將被調(diào)用函數(shù)中所有形參的代號全部修改成調(diào)用函數(shù)中實參的地址。這時,被調(diào)用函數(shù)中訪問該形參就是訪問該函數(shù)空間外的某個地址。

由于在函數(shù)的參數(shù)中,數(shù)組總是被解釋為指針,我們只要分析下指針作為函數(shù)參數(shù)的情形就行了。

int test(int *p){.....};
int main()
{
  int a[] = {1,2,3};
  int *p = a;
  test(p);
  ……
}

此時,在test函數(shù)的棧中,為形參p預留了一個4字節(jié)的空間,并且符號p就是這個空間的首址。在函數(shù)形參和實參匹配時,形參p的值就變成了實參p的值,此時發(fā)生了一次賦值,這個賦值的過程就是傳值。但碰巧的是,這個值正好是數(shù)組a第一個元素的地址,所以后面的*(p+i)之類的操作,就是修改a數(shù)組了。某種意義上,這種傳值方式對于a來說是傳址調(diào)用,但此處的形參p和實參p的地址絕對是不一樣的,此時修改形參p的值,絕對不會影響實參p的值,只是修改形參p所指向的空間才會影響實參p所指向的空間。

測試用例:

#include <iostream>
using namespace std;
#include <strings.h>
struct Node
{
    int a;
    int b;
};
 
int test(int a,int b[],int *c,int &d,Node ab,Node &abc)
{
    cout<<&a<<"  "<<a<<endl;
    cout<<(unsigned int)&b<<"  "<<(unsigned int)b<<endl;
    cout<<&c<<"  "<<*c<<endl;
    cout<<&d<<"  "<<d<<endl;
    cout<<(unsigned int)&ab<<endl;
    cout<<(unsigned int)&abc<<endl;
    return 1;
}
int main()
{
    int a =1;
    int b[] = {2,3,4};
    int f = 5;
    int *c =&f;
    int d =6;
    Node ab;
    Node abc;
    cout<<&a<<"  "<<a<<endl;
    cout<<(unsigned int)&b<<"  "<<(unsigned int)b<<endl;
    cout<<&c<<"  "<<*c<<endl;
    cout<<&d<<"  "<<d<<endl;
    cout<<(unsigned int)&ab<<endl;
    cout<<(unsigned int)&abc<<endl;
    test(a,b,c,d,ab,abc);
    return 0;
}

結(jié)果:

0xbff4a19c  1
3220480372  3220480372
0xbff4a194  5
0xbff4a190  6
3220480392
3220480384
0xbff4a150  1
3220480340  3220480372
0xbff4a158  5
0xbff4a190  6
3220480352
3220480384

從上述結(jié)果,我們可以看到,只有在形參前面加了傳址符號&后,形參和實參的地址才是一樣的,此時只是將被調(diào)用函數(shù)中形參符號全部改成調(diào)用函數(shù)中的地址。對于數(shù)組和指針的方式只是指針的傳值調(diào)用罷了,指針的地址是不一致的。

注:當結(jié)構(gòu)體作為函數(shù)的參數(shù),并且結(jié)構(gòu)體中有指針成員時要特別注意。在結(jié)構(gòu)體作為函數(shù)的參數(shù)時,形參—實參匹配的過程雖然是個傳值的過程,即把實參結(jié)構(gòu)體中的所有成員的值賦給形參結(jié)構(gòu)體中相應的成員。按理說形參和實參中所有的成員都有自己的地址是沒有聯(lián)系的,但是不要忘了對于指針成員來說雖然它們都有自己的存儲地址,但是他們所指向的空間是一致的。這點,同樣適用于結(jié)構(gòu)體賦值。即=默認重載賦值方式。

5.2 指針作為函數(shù)的返回值

函數(shù)的返回值不能是數(shù)組也不能是函數(shù),但函數(shù)的返回值可以是指針,而這個指針可以是指向數(shù)組的指針,也可以是指向函數(shù)的指針。由于指針可以指向任何由地址表示的東西,所以,某種意義上函數(shù)的返回值可以任何東西,我們需要做的僅僅是用個指針指向它而已。只是當函數(shù)的返回值是指針的情況下要注意指針指向的空間在函數(shù)返回時不能釋放。為此,我們不能用局部變量的地址作為指針的值,再將該指針返回。我們可以用以下的方法代替:

方法1:用常量區(qū)作為返回值。(只適用于字符串常量)

char* test(){return "const data";}
由于字符串常量存儲于常量區(qū),而常量區(qū)里的內(nèi)容不會隨著該函數(shù)的返回而失效。

方法2:用全局變量。

全局變量不利于數(shù)據(jù)的封裝,而且全局變量過多將會使程序更加混亂。但對于小程序,全局變量不失為一種簡單有效的方法。

方法3:用靜態(tài)變量。

靜態(tài)變量雖然在函數(shù)返回時并不釋放,但是它同全局變量一樣,再次調(diào)用此函數(shù)會影響前面的結(jié)果,使這類函數(shù)成為線程不安全型。

方法4:動態(tài)開辟空間。

這種方法是相對比較常用的,但是需要調(diào)用者在使用后主動釋放內(nèi)存,同時,并不是所有申請內(nèi)存都能成功,并且多次申請和釋放內(nèi)存后會引起大量的內(nèi)存碎片降低系統(tǒng)性能。

方法5:放棄用返回值傳出大量信息,改用函數(shù)的參數(shù)。

利用這種方法,我們也可以讓函數(shù)返回指向什么東西的指針,但此時這個返回值就是指向該函數(shù)的某個實參(不是形參)的指針,那么對于調(diào)用函數(shù)的參數(shù)來說,這個返回值就是可有可無的了。

函數(shù)的返回值的情況多種多樣,本文著重討論比較不常見的情況,但為了便于理解,對于常見的情況也簡要涉及?,F(xiàn)簡要說明下函數(shù)返回值的各種情況。

情況1:一般數(shù)據(jù)類型作為函數(shù)返回值。
  例子:int test();
情況2:結(jié)構(gòu)體作為函數(shù)的返回值。
  例子:Node test();
情況3:無返回值
  例子:void test();
情況4:一般數(shù)據(jù)類型指針作為函數(shù)返回值。
  例子:int* test();
情況5:結(jié)構(gòu)體指針作為函數(shù)返回值。
  例子:Node * test();
情況6:萬能指針作為函數(shù)返回值。
  例子:void* test();
情況7:指向函數(shù)的指針作為函數(shù)的返回值。
  例子:void (*test())(); 這個函數(shù)的返回值是個指向無返回值的無參函數(shù)的指針。
情況8:用指向?qū)崊⒌闹羔樧鳛榉祷刂怠?  例子:Node* test(Node &Var){return &Var;}
       Node* test(Node* pVar){return pVar;}

備注:

1.由于函數(shù)名即為函數(shù)的地址,而且函數(shù)屬于代碼段在整個程序的運行中一直有效,所以對于返回值是指向函數(shù)的指針,只要返回該函數(shù)的名字就行了。
2.int test(); int (*ptest)(); ptest = test; ptest = &test;則,有以下幾種方法訪問test函數(shù)。
    test();  ptest();  (*ptest)();
3.函數(shù)名在使用時總是會被編譯器轉(zhuǎn)換為函數(shù)的指針,所以在賦值時我們可以把函數(shù)名直接賦值給函數(shù)指針即用ptest = &test;代替ptest = test。同理,我們也可以用ptest();代替(*ptest)();
4.用形參數(shù)指針作為其返回值是不正確的,因為形參存儲于該函數(shù)的堆棧中,函數(shù)返回后就銷毀了。
  例如Node* test(Node pVar){return &pVar;}是不正確的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容