對於原始型別(或稱基本型別),如int, char, float, 指標 等,引數傳遞和返回值不
會碰到什麼難以理解的問題。能引起關注的焦點是,當我們把物件作為引數傳遞,或者
返回乙個物件時,這裡面發生了什麼?
我個人覺得,「返回乙個物件」的說法或概念尤其難以理解。例如函式f()返回乙個物件
(類a的例項):
a a;
a = f(); //
或者:f().f1(); // 呼叫a的成員函式f1()
物件會大的根本不可能放入暫存器中(象c/c++編譯器對於基本型別所作的慣常處理那樣
),那麼這個物件是臨時從棧中申請的嗎?
但是,函式執行時從棧中申請的物件是無法傳遞出去的。因為函式執行完畢、出棧之後
,棧就會摺疊、恢復。這就意味著該物件的生存期結束了,外部是不可以再訪問或使用
這個物件的(堅持這樣做是充滿危險和非法的):它消失了。如果我們強制讓這個物件
可以為外部所使用,那麼棧將無法得到恢復,包括先前壓入棧中的函式引數,仍將滯留
在棧中。此種場面(和它帶來的問題)實在太糟糕了。
那麼這個物件從堆中申請如何?由於它是臨時使用的,編譯器還必須記得在恰當的時候
將之安全地刪除。也許這原則上可以做到,但是也存在不妥之處。首先堆的訪問操作效
率明顯不如棧,其次(也許更嚴重的),編譯器擅自使用堆,而沒有讓程式設計師知道(堆
的使用幾乎是程式設計師的領地,來歷不明的爭用是難以容忍的)。
所以一種可能的較好的做法是,在函式呼叫前在棧上預先申請一塊空間,準備用來儲存
返回的物件。但是,為了讓函式能夠在返回把結果拷貝到這裡,必須把這塊位址傳給函
數,這是乙個隱含的操作(由編譯器生成push指令完成)。
例如對於上面的f().f1()的呼叫,這條語句前半部分的行為可以這樣描述:
allocate space from stack for an object of a
push address of this space ; let me call it a_ptr
push all parameters of function f()
call f()
recover stack (pop up all parameters and a_ptr )
這樣一來,經過f()的呼叫後,我們得到的a_ptr指向乙個a的例項,這正是所謂「返回的
」物件。接著,呼叫了成員函式f1():
a_ptr->f1()
// destroy the temp object (*a_ptr)
我們記得,這是乙個臨時物件,所以呼叫f1()之後它將會被析構。
對於「宣告並賦值」的寫法,例如:
a a = f();
前面所做的分析仍然完全適應。在這種情況下,我們發現那個臨時物件有名字了,它就
是a。但是對於象a = f() 這樣的「宣告,然後賦值」寫法,情況正變得有趣。因為a已經存在
,我們發現可以不用在棧上申請用來容納返回物件的臨時空間。我們可以直接把a的位址
傳遞給函式f(),而不是那個臨時物件的位址。這樣做效率更高。結果是,函式在返回時
直接往a所佔據的空間拷貝。
這的確非常精彩。至此函式返回物件的問題已得到解決。還必須強調一點:函式在返回
時無論向臨時物件,還是目的物件拷貝,類的拷貝建構函式(copy constructor)都將
被執行。如果類沒有定義顯式的拷貝建構函式,那麼將執行預設的位拷貝(bitcopy)。
現在來看物件作為引數傳遞發生什麼,例如:
a x;
f(x); // suppose prototype of function f() is:void f(a a);
這裡函式f需要乙個a的物件引數(整個物件按值傳遞,既不是傳遞物件引用,也不是傳
遞物件指標)。不難想象,這需要在棧上先申請一塊空間存放「傳入」的物件。從函式f
()內部來看,這就是作為引數的物件a。然後,這塊空間的位址被壓入棧中,函式在內部
使用指向物件的位址來操作物件。但是,在實際執行call f()指令前,要先完成從x到a
的拷貝過程(因而拷貝建構函式會被呼叫)。
最後在函式f()返回前,a的析構函式會被呼叫。函式返回後,棧將恢復到呼叫前的水平
(容納物件a的那塊空間被「釋放」了)。
note:關鍵思想是從"thinking in c++"學到的。此前我並未意識到:從函式中返回乙個
物件,事先直接將目的物件位址壓棧(作為乙個隱含的引數)。
C函式引數傳遞與返回值傳遞
1 引數傳遞 stdcall和 cdecl都是函式呼叫約定關鍵字,先給出這兩者的區別,然後舉例項分析 stdcall 引數由右向左壓入堆疊 堆疊由函式本身清理。cdecl 引數也是由右向左壓入堆疊 但堆疊由呼叫者清理。另外,這兩者在同一名字修飾約定下,編譯過後變數和函式的名字也不一樣,具體見另一博文...
引數傳遞以及返回值
在呼叫乙個方法時,我們經常傳入我們需要的引數,對於基本型別的傳入,在執行方法時直接用即可,這裡僅介紹幾種引用型別的引數傳遞 類名作為形式引數 如果乙個方法的形參要乙個類 型別,就傳入乙個該類的物件 根據 可知,建立物件時完成初始化,此時物件裡的的num時2,在呼叫方法時,傳入30,替代了原來的2,所...
引數和返回值
基本資料型別 這裡所說的形式引數對基本資料型別不做研究 引用資料型別 引數是具體類時 建立引用資料型別 使用匿名類 new 類名 使用匿名內部類的方式 父類名或者父類介面 物件名 new 父類名或者父類介面 引數是抽象類時 可以使用抽象類多型 建立抽象類的子類 使用匿名內部類的方式 父類名或者父類介...