一說到指針和函數(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;}是不正確的。