C 特性探尋 引數傳遞和返回值

2021-04-01 16:56:48 字數 2219 閱讀 3347

對於原始型別(或稱基本型別),如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 父類名或者父類介面 引數是抽象類時 可以使用抽象類多型 建立抽象類的子類 使用匿名內部類的方式 父類名或者父類介...