同樣的函數引用里為什麼使用string引用時會清空數據?

在把string類型改成int基本類型或者直接使用前面的string a之後就沒有這種現象,為什麼?

代碼如下

using namespace std;

int main()

{

const string print(const string );

float b = 11.17;

string a = "cba";

const string d= print("abc");

cout &<&< d &<&< "==&>" &<&< d &<&< " ";

cin &>&> b;

return b;

}

const string print(const string l)

{

cout &<&< l&<&<"==&>" &<&< l &<&<" ";

return l;

}


你放截屏的意思是要我手敲一遍代碼去復現?


感覺沒人答到點子上啊。。。

你傳入的是"abc"

形參類型是const string

相當於在函數體內部使用"abc"構造了一個string的臨時對象,並把其綁定在了"L"上

之後函數運行完這個string局部臨時對象就析構了

所以你這個問題就是「返回了局部對象的引用

如果不在函數內部建立臨時對象,而是在外部建立就沒有任何問題,如下:


const string d= print("abc");

應該是因為這個傳進去的是個臨時變數,而參數和返回值都是引用傳遞。字元串裡面用完了(超出作用域)就被釋放了,所以返回時就返回了一個空的。返回值去掉引用,可能就能得到"abc"了。

補張圖:


改成print(a)就可以正常顯示,因為a已經分配好了,main函數返回才析構。而「abc」是在參數中的,調用的時候先生成一個string 臨時變數,然後用構造函數初始化,string有個參數是const char *的構造函數。調用完成之後,就析構這個臨時變數了,而d引用了一個已經析構的臨時變數。至於int類型,實際是一樣的,只不過int析構的時候不會把int值改成0,而string析構的時候把字元串設置成空了。

c++引用坑居多,不用引用是更好的選擇。


你引用棧上的東西,返回即析構,出什麼結果都有可能。

@陳碩 這種問題你都回,影響timeline啊。匿了。


題主你一定困惑於為什麼int型的參數就能被解引用.雖然堆棧被平棧了.但是,只是修改了esp指針.堆棧裡面的數值尚未被覆蓋.所以後面能正確引用到. string類型悲劇就悲劇在,它會調用析構函數,也就是最後一個紅框的內容. 所以那個值直接被抹掉了...o(︶︿︶)o 唉

能懂就懂,不懂也沒關係.這個已經超出了C++語言的範疇.知道有這麼回事情就行了.

-----------

嗯...為什麼沒有人點贊?...嗯...嗯...睡覺~


必須返回對象時,別妄想返回其reference~~~~~Effective cpp 條款21。不要輕易嘗試返回局部對象的引用,如有必要,請返回右值引用。


《深度探索C++對象模型》第275頁:如果一個臨時性對象被綁定於一個reference,對象將殘留,直到被初始化之reference的生命結束,或直到臨時對象的生命範疇(scope)結束——視哪一種情況先到達而定。(候捷先生的翻譯略有點生硬)

例如:const string s = "abc";

會產生這樣的程序代碼:

string temp;

temp.string::string("abc");

const string s = temp;

此時,臨時對象會殘留。

對於題中的臨時對象,生成後被綁定到函數的形參(同上面的例子)。

按上述規則,函數調用完成後,可以認為是被初始化之reference的生命結束,也可以認為是臨時對象的生命範疇結束(畢竟是棧上的變數,函數調用結束會退棧,退棧之前編譯器會隱式插入string的析構函數),那麼此時臨時對象也不再殘留而被析構,從而d引用到的是析構後的string對象(l和d的地址相同)。

但是,退棧並不會把之前的棧上數據清零,所以d還能訪問到棧上的string對象,看到其size成員變數。而string對象的析構函數會把size置成0,那麼通過變數d 訪問時,由於d.size為0,&<&<操作符不列印任何字元,從而觀察到「清空數據」的假象。

事實上,析構後的string對象的狀態是未定義的,上述情形只是在MSVC下的實現而已,用於解釋題中看到的現象。正如《Effective C++》條款21所言:必須返回對象時,別妄想返回其reference。


你引用其他函數的局部變數還想怎樣


返回局部變數,代碼本身就有問題,string類型是一個類,當它生命周期結束時會析構而引起內容消失,int類型看起來正確,但數值是不安全的,可以被別的程序隨意修改


這個居然能編譯過嗎?內部引用不能穿出才對呀

啊,不對我錯了,這個是臨時對象的引用


你沒理解reference和const reference。


我還是比較好奇,你是如何把一個右值綁定到一個引用上的?msvc有特殊技巧嗎

----

修正,首先承認錯誤,看題目要看完整再回答,看完整,看完整,看完整。

的確,右值不應該,程序猿也萬萬不能將其被綁定在一個左值引用上(不管他是const亦或者不是const,但是MSVC裡面竟然可以,吃驚),右值應當被一個左值先妥妥的存起來(不應該搞起來就直接用,除非是一次性的),然後就可以做任何你想做的事了。【然而這並不是題主的題目】

題主的print函數為const string print(const string l) ,當調用函數時"abc「,此時"abc"還是一個右值,然後很據函數匹配原則以及右值當左值用的原則,const string l 雖然不是最優,但是可以匹配, 好,那就愉快的當左值用了

然而函數用完可是會給你愉快的析構的啊啊啊,也就是說,這個」abc「已經死了,之所以還能列印出來自己的地址,那是"abc"的殘念太深啊啊啊。

總結

」abc「菌經歷了傳奇的一生

-&> ( = ) -&> 析構 -&>return -&> 在main中取地址

最後

"abc"菌已經出問題了,建議不要繼續使用,待會兒就要燙燙燙燙了


推薦閱讀:

TAG:編程 | 引用 | 內存管理 | C |