標籤:alt strong data 自己的 i++ 建構函式 term 輸入 string
引用是C++中新出現的。有別於C語言的文法元素之中的一個。
關於引用的說明,網路上也有不少。可是總感覺雲遮霧繞,讓人印象不深刻。
今天我就來深入解釋一下引用。並就一些常見的觀點進行說明,最後附帶代碼示範範例予以說明(注意。開發環境是vs2013)。
前面先擺出我的觀點:
1 引用的出現純粹是為了最佳化指標的使用,而提出的文法層面的處理。
2 引用實現原理上全然等價於指標。
3 引用對於傳遞對象參數有很大的最佳化和優點。
4 引用有其局限性,與指標相比,有時候可能與物件導向的設計有衝突。
以下給出我的範例。通過這個範例,我再來慢慢解釋上面的觀點:
void intreference(int& i){printf("[%s]i=%d\n", __FUNCTION__, i);i++;}void objectreference(std::string& str){printf("[%s]str=%s\n", __FUNCTION__, str.c_str());str += 'i';}class mystr :public std::string{public:mystr() :std::string(){}~mystr(){}};void testvirtual(mystr&str){printf("[%s]str=%s\n", __FUNCTION__, str.c_str());}class mytest{public:mytest(){}~mytest(){}virtual void test(){printf("father\n");}};class mysubtest:public mytest{public:mysubtest(){}~mysubtest(){}virtual void test(){printf("hello!\n");}};void testpurevirtual(mytest& test){test.test();}void main(){char* p;int i = 0;refint refintfunc = (refint)intreference;printf("i=%d\n", i);intreference(i);printf("i=%d\n", i);refintfunc(&i);printf("i=%d\n", i);refobj refobjfunc = (refobj)objectreference;std::string obj = "s";printf("str=%s\n", obj.c_str());objectreference(obj);printf("str=%s\n", obj.c_str());refobjfunc(&obj);printf("str=%s\n", obj.c_str());//int& j = i;printf("i %08x,j %08x\n", &i, &i);std::string* pstr = new mystr();//testvirtual(*pstr);//error C2664: “void testvirtual(mystr &)”: 無法將參數 1 從“std::string”轉換為“mystr &”mysubtest t;testpurevirtual(t);getchar();}
範例裡面我給出了兩個引用測試函數和一個變數引用
範例說明了什麼:
1 引用實現原理上全然等價於指標
請注意。函數intreference與函數objectreference是一個引用參數的函數
而函數指標refintfunc與函數指標refobjfunc是一個指標參數的函數指標
對於後者的調用。編譯器會毫不遲疑的將i的地址傳遞給函數
假設引用參數實現原理與指標不全然等價,那麼必定會導致函數調用出現故障
但結果卻非常有趣,我發現兩種方式,效果全然同樣,沒有不論什麼差異。
以下是執行時的反組譯碼:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >
從反組譯碼能夠清晰的看到,對於直接進行引用參數函數調用。和使用指標參數調用。兩者的彙編代碼全然沒有什麼差別
引用在實現的時候,傳遞的就是一個指標給函數!!
不不過對於單一資料型別如此,對於複雜資料類型這也是相同的:
能夠看到,兩者都是將obj的地址作為參數,放入到了eax,然後再推送到棧中去了
也就是說,在實現層面上面,兩者是等同的
那麼對於局部,非參數傳遞的引用呢?
以下是局部引用j和其引用對象i賦值時的反組譯碼:
注意第一條紅線,j是有自己的棧空間地址的!並不是如同網路上所說的別名。不佔用空間,等價等等。不是這種!
它仍然要佔空間,佔一個指標大小的空間。假設i和j是char和char引用,那麼j佔用的空間甚至比i還大!
在賦值的時候。系統將i的地址給eax。然後再通過eax寄存器將地址傳入j,注意dword ptr,這表示指標j!
這和我前面提到的觀點:引用實現原理上全然等價於指標 是全然一致的。
>
<
2 既然它在實現層面上全然等價於指標。那為什麼還會有引用?
這就要回到我前面提出的第一個觀點:引用的出現純粹是為了最佳化指標的使用,而提出的文法層面的處理
假設這裡使用指標。就會很麻煩!
首先,假設函數的參數是指標。開發人員就必需要要驗證指標!這個差點兒是無法避免的情況!
否則指標一旦為空白,整個程式必定崩潰。
可是引用就避免了這個麻煩——通過文法層面上的幹預——使得使用者無法顯式的傳遞null 指標到函數中去
假設有null 指標或者野指標,崩潰僅僅會發生在函數外部,而非內部。
其次。輸入.比輸入->更加讓開發人員開心一些,不論是長度還是安全性上,指標式的成員函數調用,總讓人心驚膽顫
因此,引用全然是一種文法層面的處理。就是C++中的私人成員變數一樣,僅僅是從文法上阻止使用者去顯式訪問——實際上能夠利用指標,強制從記憶體中讀寫該變數。
當然引用不只不過這樣,之所以物件導向要增加引用,另外一個作用還在於:
假設參數純粹是一個對象,那麼意味著程式須要頻繁的在棧上面構造和析構對象。
而引用成功的攻克了這個問題。能夠讓開發人員決定要不要在棧上面構造對象並自己主動析構它。
這樣導致效率極大的提升了——非常多複雜的對象。其建構函式和複製建構函式可能異常複雜和耗時。
同一時候,另外一些對象可能並不希望調用者使用它們的建構函式。比方單例對象!
而引用非常好的攻克了這個矛盾。
3引用有沒有限制?答案是有!
限制在哪裡?我們知道。物件導向設計中有介面這個概念,而C++與之關聯的是虛函數。
我們常常會持有一個父類的指標,而在當中填入各種子類的對象,然後通過虛函數去調用相應的子類介面實現。
可是這裡使用引用卻有限制。僅僅能在聲明為父類引用的時候。使用子類,而無法在聲明為子類引用的時候使用父類。
指標卻能夠不受此限制,進行自由的轉化(當然這是有風險的!
)
以下給出了一個示範範例:
對於mystr和函數testvirtual,假設傳入一個父類對象(實際上還是一個子類,僅僅是是一個父類指標),在文法上這是被禁止!
對於mytest和mysubtest以及函數testpurevirtual,這樣又是能夠的。
這種限制要求開發人員在設計的時候就必須很仔細,事先想好介面的統一性。否則後面代碼就有的改了
當然,這樣也有優點,能夠避免一些問題。比方null 指標或者對象不匹配異常(將一個非mytest或者其子類的對象指標強制轉化過來。此時調用必定崩潰。)
<pre code_snippet_id="1639674" snippet_file_name="blog_20160408_34_61346" name="code" class="cpp">class mystr :public std::string{public:mystr() :std::string(){}~mystr(){}};void testvirtual(mystr&str){printf("[%s]str=%s\n", __FUNCTION__, str.c_str());}class mytest{public:mytest(){}~mytest(){}virtual void test(){printf("father\n");}};class mysubtest:public mytest{public:mysubtest(){}~mysubtest(){}virtual void test(){printf("hello!\n");}};void testpurevirtual(mytest& test){test.test();}void main(){int i = 0;refint refintfunc = (refint)intreference;printf("i=%d\n", i);intreference(i);printf("i=%d\n", i);refintfunc(&i);printf("i=%d\n", i);refobj refobjfunc = (refobj)objectreference;std::string obj = "s";printf("str=%s\n", obj.c_str());objectreference(obj);printf("str=%s\n", obj.c_str());refobjfunc(&obj);printf("str=%s\n", obj.c_str());int& j = i;printf("i %08x,j %08x\n", &i, &j);std::string* pstr = new mystr();//testvirtual(*pstr);//error C2664: “void testvirtual(mystr &)”: 無法將參數 1 從“std::string”轉換為“mystr &”mysubtest t;testpurevirtual(t);getchar();}最後給出執行結果的:
能夠看到。結果充分說明了引用事實上就是指標
這裡補充說明一下i和j的問題:
當我聲明了j的時候,能夠看到函數棧的大小
而沒有聲明j的時候。函數棧明顯變小了
小了12位元組,非常奇怪,好像和指標的大小不一致啊
沒有關係,我再聲明一個指標,我們再看看
看到沒有?棧又恢複到了14c了。而我僅僅是聲明了一個char*p,而且沒有做不論什麼調用。
這說明j是佔領空間的,大小正好是一個指標!!
C++引用具體解釋