在C++中,參數傳遞的方式是「實虛結合」。ios
按值傳遞的過程爲:首先計算出實參表達式的值,接着給對應的形參變量分配一個存儲空間,該空間的大小等於該形參類型的,而後把以求出的實參表達式的值一一存入到形參變量分配的存儲空間中,成爲形參變量的初值,供被調用函數執行時使用。這種傳遞是把實參表達式的值傳送給對應的形參變量,故稱這種傳遞方式爲「按值傳遞」。c++
使用這種方式,調用函數自己不對實參進行操做,也就是說,即便形參的值在函數中發生了變化,實參的值也徹底不會受到影響,仍爲調用前的值。程序員
輸出:c#
若是在函數定義時將形參說明成指針,對這樣的函數進行調用時就須要指定地址值形式的實參。這時的參數傳遞方式就是地址傳遞方式。安全
地址傳遞與按值傳遞的不一樣在於,它把實參的存儲地址傳送給對應的形參,從而使得形參指針和實參指針指向同一個地址。所以,被調用函數中對形參指針所指向的地址中內容的任何改變都會影響到實參。多線程
輸出:函數
按值傳遞方式容易理解,但形參值的改變不能對實參產生影響。優化
地址傳遞方式雖然可使得形參的改變對相應的實參有效,但若是在函數中反覆利用指針進行間接訪問,會使程序容易產生錯誤且難以閱讀。ui
若是以引用爲參數,則既可使得對形參的任何操做都能改變相應的數據,又使得函數調用顯得方便、天然。引用傳遞方式是在函數定義時在形參前面加上引用運算符「&」。spa
輸出:
1、 函數參數傳遞機制的基本理論
函數參數傳遞機制問題在本質上是調用函數(過程)和被調用函數(過程)在調用發生時進行通訊的方法問題。基本的參數傳遞機制有兩種:值傳遞和引用傳遞。如下討論稱調用其餘函數的函數爲主調函數,被調用的函數爲被調函數。
值傳遞(passl-by-value)過程當中,被調函數的形式參數做爲被調函數的局部變量處理,即在堆棧中開闢了內存空間以存放由主調函數放進來的實參的值,從而成爲了實參的一個副本。值傳遞的特色是被調函數對形式參數的任何操做都是做爲局部變量進行,不會影響主調函數的實參變量的值。
引用傳遞(pass-by-reference)過程當中,被調函數的形式參數雖然也做爲局部變量在堆棧中開闢了內存空間,可是這時存放的是由主調函數放進來的實參變量的地址。被調函數對形參的任何操做都被處理成間接尋址,即經過堆棧中存放的地址訪問主調函數中的實參變量。正由於如此,被調函數對形參作的任何操做都影響了主調函數中的實參變量。
2、 C語言中的函數參數傳遞機制
在C語言中,值傳遞是惟一可用的參數傳遞機制。可是據筆者所知,因爲受指針變量做爲函數參數的影響,有許多朋友還認爲這種狀況是引用傳遞。這是錯誤的。請看下面的代碼:
函數swap以兩個指針變量做爲參數,當main()調用swap時,是以值傳遞的方式將指針變量p一、p2的值(也就是變量a、b的地址)放在了swap在堆棧中爲形式參數x、y開闢的內存單元中。
這裏咱們能夠獲得如下幾點:
1. 進程的堆棧存儲區是主調函數和被調函數進行通訊的主要區域。
2. C語言中參數是從右向左進棧的。
3. 被調函數使用的堆棧區域結構爲:
局部變量(如temp)
返回地址
函數參數
低地址
高地址
4. 由主調函數在調用後清理堆棧。
5. 函數的返回值通常是放在寄存器中的。
這裏尚需補充說明幾點:一是參數進棧的方式。對於內部類型,因爲編譯器知道各種型變量使用的內存大小故直接使用push指令;對於自定義的類型(如structure),採用從源地址向目的(堆棧區)地址進行字節傳送的方式入棧。二是函數返回值爲何通常放在寄存器中,這主要是爲了支持中斷;若是放在堆棧中有可能由於中斷而被覆蓋。三是函數的返回值若是很大,則從堆棧向存放返回值的地址單元(由主調函數在調用前將此地址壓棧提供給被調函數)進行字節傳送,以達到返回的目的。對於第二和第三點,《Thinking in C++》一書在第10章有比較好的闡述。四是一個顯而易見的結論,若是在被調函數中返回局部變量的地址是毫無心義的;由於局部變量存於堆棧中,調用結束後堆棧將被清理,這些地址就變得無效了。
3、 C++語言中的函數參數傳遞機制
衆所周知,在c++中調用函數時有三種參數傳遞方式:
(1)傳值調用;
(2)傳址調用(傳指針);
(3)引用傳遞;
實際上,還有一種參數傳遞方式,就是全局變量傳遞方式。這裏的「全局」變量並不見得就是真正的全局的,全部代碼均可以直接訪問的,只要這個變量的做用域足夠這兩個函數訪問就能夠了,好比一個類中的兩個成員函數可使用一個成員變量實現參數傳遞,或者使用static關鍵字定義,或者使用namespace進行限制等,而這裏的成員變量在這種意義上就能夠稱做是「全局」變量(暫時尚未其它比「全局」更好的詞來描述)。固然,可使用一個類外的真正的全局變量來實現參數傳遞,但有時並無必要,從工程上講,做用域越小越好。這種方式有什麼優勢呢?
效率高!
的確,這種效率是全部參數傳遞方式中效率最高的,比前面三種方式都要高,不管在什麼狀況下。但這種方式有一個致命的弱點,那就是對多線程的支持很差,若是兩個進程同時調用同一個函數,而經過全局變量進行傳遞參數,該函數就不可以老是獲得想要的結果。
下面再分別討論上面三種函數傳遞方式。
1. 從功能上。按值傳遞在傳遞的時候,實參被複制了一份,而後在函數體內使用,函數體內修改參數變量時修改的是實參的一份拷貝,而實參自己是沒有改變的,因此若是想在調用的函數中修改實參的值,使用值傳遞是不能達到目的的,這時只能使用引用或指針傳遞。例如,要實現兩個數值交換。
void swap(int a int b)
void main(){
int a=1 b=2
swap(a b)
}
這樣,在main()函數中的a b值實際上並無交換,若是想要交換隻能使用指針傳遞或引用傳遞,如:
void swap(int *a int *b)
或
void swap(int &a int &b)
2.從傳遞效率上。這裏所說傳遞效率,是說調用被調函數的代碼將實參傳遞到被調函數體內的過程,正如上面代碼中,這個過程就是函數main()中的a b傳遞到函數swap()中的過程。這個效率不能一律而論。對於內建的int char short long float等4字節或如下的數據類型而言,實際上傳遞時也只須要傳遞1-4個字節,而使用指針傳遞時在32位cpu中傳遞的是32位的指針,4個字節,都是一條指令,這種狀況下值傳遞和指針傳遞的效率是同樣的,而傳遞double long long等8字節的數據時,在32位cpu中,其傳值效率比傳遞指針要慢,由於8個字節須要2次取完。而在64位的cpu上,傳值和傳址的效率是同樣的。再說引用傳遞,這個要看編譯器具體實現,引用傳遞最顯然的實現方式是使用指針,這種狀況下與指針的效率是同樣的,而有些狀況下編譯器是能夠優化的,採用直接尋址的方式,這種狀況下,效率比傳值調用和傳址調用都要快,與上面說的採用全局變量方式傳遞的效率至關。
再說自定義的數據類型,class struct定義的數據類型。這些數據類型在進行傳值調用時生成臨時對象會執行構造函數,並且當臨時對象銷燬時會執行析構函數,若是構造函數和析構函數執行的任務比較多,或者傳遞的對象尺寸比較大,那麼傳值調用的消耗就比較大。這種狀況下,採用傳址調用和採用傳引用調用的效率大多數下至關,正如上面所說,某些狀況下引用傳遞可能被優化,整體效率稍高於傳址調用。
3. 從執行效率上講。這裏所說的執行效率,是指在被調用的函數體內執行時的效率。由於傳值調用時,當值被傳到函數體內,臨時對象生成之後,全部的執行任務都是經過直接尋址的方式執行的,而指針和大多數狀況下的引用則是以間接尋址的方式執行的,因此實際的執行效率會比傳值調用要低。若是函數體內對參數傳過來的變量進行操做比較頻繁,執行總次數又多的狀況下,傳址調用和大多數狀況下的引用參數傳遞會形成比較明顯的執行效率損失。
綜合二、3兩種狀況,具體的執行效率要結合實際狀況,經過比較傳遞過程的資源消耗和執行函數體消耗之和來選擇哪一種狀況比較合適。而就引用傳遞和指針傳遞的效率上比,引用傳遞的效率始終不低於指針傳遞,因此從這種意義上講,在c++中進行參數傳遞時優先使用引用傳遞而不是指針。
4. 從類型安全上講。值傳遞與引用傳遞在參數傳遞過程當中都執行強類型檢查,而指針傳遞的類型檢查較弱,特別地,若是參數被聲明爲 void ,那麼它基本上沒有類型檢查,只要是指針,編譯器就認爲是合法的,因此這給bug的產生製造了機會,使程序的健壯性稍差,若是沒有必要,就使用值傳遞和引用傳遞,最好不用指針傳遞,更好地利用編譯器的類型檢查,使得咱們有更少的出錯機會,以增長代碼的健壯性。
這裏有個特殊狀況,就是對於多態的狀況,若是形參是父類,而實參是子類,在進行值傳遞的時候,臨時對象構造時只會構造父類的部分,是一個純粹的父類對象,而不會構造子類的任何特有的部分,由於辦有虛的析構函數,而沒有虛的構造函數,這一點是要注意的。若是想在被調函數中經過調用虛函數得到一些子類特有的行爲,這是不能實現的。
5. 從參數檢查上講。一個健壯的函數,總會對傳遞來的參數進行參數檢查,保證輸入數據的合法性,以防止對數據的破壞而且更好地控制程序定期望的方向運行,在這種狀況下使用值傳遞比使用指針傳遞要安全得多,由於你不可能傳一個不存在的值給值參數或引用參數,而使用指針就可能,極可能傳來的是一個非法的地址(沒有初始化,指向已經delete掉的對象的指針等)。因此使用值傳遞和引用傳遞會使你的代碼更健壯,具體是使用引用仍是使用,最簡單的一個原則就是看傳遞的是否是內建的數據類型,對內建的數據類型優先使用值傳遞,而對於自定義的數據類型,特別是傳遞較大的對象,那麼請使用引用傳遞。
6. 從靈活性上。無疑,指針是最靈活的,由於指針除了能夠像值傳遞和引用傳遞那樣傳遞一個特定類型的對象外,還能夠傳遞空指針,不傳遞任何對象。指針的這種優勢使它大有用武之地,好比標準庫裏的time( )函數,你能夠傳遞一個指針給它,把時間值填到指定的地址,你也能夠傳遞一個空指針而只要返回值。
以上討論了四種參數傳遞方式的優缺點,下面再討論一下在參數傳遞過程當中一些共同的有用的技術。
1. const關鍵字。當你的參數是做爲輸入參數時,你總不但願你的輸入參數被修改,不然有可能產生邏輯錯誤,這時能夠在聲明函數時在參數前加上const關鍵字,防止在實現時意外修改函數輸入,對於使用你的代碼的程序員也能夠告訴他們這個參數是輸入,而不加const關鍵字的參數也多是輸出。例如strlen,你能夠這樣聲明
int strlen(char str)
功能上確定沒有什麼問題,可是你想告訴使用該函數的人,參數str是一個輸入參數,它指向的數據是不能被修改的,這也是他們指望的,總不會有人但願在請人給他數錢的時候,裏面有張100的變成10塊的了,或者真鈔變成假鈔了,他們但願有一個保證,說該函數不會破壞你的任何數據,聲明按以下方式即可讓他們放心:
int strlen(const char str)
可不能夠給str自己也加一個限制呢,若是把地址改了數得的結果不就錯了嗎?總得給人點兒自由吧,只要它幫你數錢就好了,何須介意他怎麼數呢?只要不破壞你的錢就ok了,若是給str一個限制,就會出現問題了,按照上面的聲明,能夠這樣實現:
但是,若是你硬要把聲明改爲
int strlen(const char const str)
上面的函數確定就運行不了了,只能改用其它的實現方式,但這個不是太有必要。只要咱們保護好咱們的錢就好了,若是它數不對,下次我次不讓它數,再換我的就是了。
對於成員函數,若是咱們要顯示給客戶代碼說某個成員函數不會修改該對象的值,只會讀取某些內容,也能夠在該函數聲明中加一個const.
class person
{......
public:
unsigned char age( void ) const // 看到const就放心了,這個函數確定不會修改m_age
private:
unsigned char m_age // 我認爲這個類型已經足夠長了,若是以爲不改能夠改成unsigned long
}
2. 默認值。我的認爲給參數添加一個默認值是一個很方便的特性,很是好用,這樣你就能夠定義一個具備好幾個參數的函數,而後給那些不經常使用的參數一些默認值,客戶代碼若是認爲那些默認值正是他們想要的,調用函數時只須要填一些必要的實參就好了,很是方便,這樣就省去了重載好幾個函數的麻煩。但是我不明白c#爲何把這個特性給去掉了,多是爲了安全,這樣就要求每次調用函數時都要顯示地給函數賦實參。因此要注意,這但是個雙刃劍,若是想用使刀的招跟對手武鬥,極可能傷到本身。
3.參數順序。當同個函數名有不一樣參數時,若是有相同的參數儘可能要把參數放在同一位置上,以方便客戶端代碼。
c++ 中常用的是常量引用,如將swap2改成:
Swap2(const int& x; const int& y)
這時將不能在函數中修改引用地址所指向的內容,具體來講,x和y將不能出如今"="的左邊。