傳值?還是傳引用?
(Wang hailong)
關(guān)于編程的參數(shù)傳遞問(wèn)題,總是存在著這樣的爭(zhēng)論。傳值?還是傳引用?(還是傳指針?還是傳地址?)這些提法,經(jīng)常出現(xiàn)在C++, java, C#的編程技術(shù)文檔里面。這個(gè)問(wèn)題也經(jīng)常引起開(kāi)發(fā)人員的爭(zhēng)論,徒耗人力物力。實(shí)際上,這根本不成為問(wèn)題,只是由于人為加入的概念,混淆了人們的視聽(tīng)。
從程序運(yùn)行的角度來(lái)看,參數(shù)傳遞,只有傳值,從不傳遞其它的東西。只不過(guò),值的內(nèi)容有可能是數(shù)據(jù),也有可能是一個(gè)內(nèi)存地址。
開(kāi)發(fā)人員應(yīng)該了解程序的編譯結(jié)果是怎樣在計(jì)算機(jī)中運(yùn)行的。程序運(yùn)行的時(shí)候,使用的空間可以分為兩個(gè)部分,棧和堆。棧是指運(yùn)行棧,局部變量,參數(shù),都分配在棧上。程序運(yùn)行的時(shí)候,新生成的對(duì)象,都分配在堆里,堆里分配的對(duì)象,棧里的數(shù)據(jù)參數(shù),或局部變量。
下面舉一個(gè)C++的例子。
public class Object{
int i;
public Object(int i){
this.i = i;
}
public int getValue(){
return i;
}
public void setValue(int i){
this.i = i;
}
};
class A {
Void func1(int a, Object b){
Object * c = new Object( a );
b = c;
}
public void main(){
Object * param = new Object( 1 );
func1( 2, param );
// what is value of parram now ?
// it is still 1.
}
};
我們來(lái)看一下,當(dāng)調(diào)用到func1函數(shù)時(shí),運(yùn)行到Object * c = new Object( a ); 棧和堆的狀態(tài)。不同編譯器生成的代碼運(yùn)行的結(jié)果可能會(huì)稍有不同。但參數(shù)和局部變量的大致排放順序都是相同的。
這時(shí)候,我們來(lái)看,param變量被壓入運(yùn)行棧的時(shí)候,只是進(jìn)行了簡(jiǎn)單的復(fù)制。把param里面的內(nèi)容拷貝到b里面。這時(shí)候,b就指向了Object(1)。這里的參數(shù)傳遞,是把param的值傳遞給b。
下面我們來(lái)看,程序執(zhí)行到b = c;時(shí)候的堆棧狀態(tài)。
我們可以看到,b現(xiàn)在指向了Object(2)。但是對(duì)param的值毫無(wú)影響。param的值還是Object(1)。
所以,我們說(shuō),對(duì)參數(shù)的賦值不會(huì)影響到外層函數(shù)的數(shù)據(jù),但是,調(diào)用參數(shù)的操作方法,卻等于直接操作外層函數(shù)的數(shù)據(jù)。比如,如果我們?cè)?/SPAN>func1()函數(shù)中,不調(diào)用b=c;而調(diào)用b.setValue(3),那么Object(1)的數(shù)據(jù)就會(huì)變?yōu)?/SPAN>3,param的數(shù)據(jù)也會(huì)改變?yōu)?/SPAN>3。
在java和C#中的情況,也都是一樣。
C++還有一種變量定義方法,表面上看起來(lái),不符合上面的說(shuō)明,這里進(jìn)行說(shuō)明。
Object * a = new Object(1);
Object & * b = a;
這里的b就等于是a的另外一個(gè)別名,b就是a。對(duì)b賦值就等于對(duì)a賦值。甚至作為參數(shù)傳遞時(shí),也是如此。對(duì)這種類(lèi)型的參數(shù)的賦值,就等于對(duì)外層函數(shù)數(shù)據(jù)的賦值。
public class B{
void func1(Object & * b){
b = new Object(4);
}
public void main(){
Object * a = new Object(1);
func1(a);
// a is changed to Object(4) now.
}
}
當(dāng)運(yùn)行完func1(a);時(shí),a的值變化為Object(4)。這是因?yàn)榫幾g器實(shí)際把參數(shù)Object & * b編譯為Object ** b_addr,b_addr的值是b的地址,也就是a的地址。
當(dāng)調(diào)用func1()的時(shí)候,實(shí)際上是把b_addr作為參數(shù)壓到棧里,b_addr的值是a的地址。
當(dāng)執(zhí)行b = new Object(4); 時(shí),實(shí)際執(zhí)行了 b_addr->b = new Object(4); 也就是執(zhí)行了 b_addr->a = new Object(4); a的值當(dāng)然變化了。
還有一點(diǎn)需要說(shuō)明,當(dāng)使用COM,CORBA等中間件規(guī)范進(jìn)行開(kāi)發(fā)時(shí),我們需要定義IDL語(yǔ)言。參數(shù)的類(lèi)型分為,[in],[out],[in, out],其中的RPC遠(yuǎn)程調(diào)用的參數(shù)打包規(guī)范,就更復(fù)雜了,但原理卻是一樣的。