小心stringstream.str()字符串用法的陷阱

在編寫應(yīng)用程序時(shí)肠仪,我們經(jīng)常要使用到字符串异旧。C++標(biāo)準(zhǔn)庫中的<string>和<sstream>為我們操作字符串提供了很多的方便,例如:對象封裝拌屏、安全和自動(dòng)的類型轉(zhuǎn)換倚喂、直接拼接务唐、不必?fù)?dān)心越界等等带兜。但今天我們并不想長篇累牘得去介紹這幾個(gè)標(biāo)準(zhǔn)庫提供的功能刑巧,而是分享一下stringstream.str()的一個(gè)有趣的現(xiàn)象无畔。我們先來看一個(gè)例子:

   #include <string>
   #include <sstream>
   #include <iostream>
   
   using namespace std;
   
   int main()
   {
       stringstream ss("012345678901234567890123456789012345678901234567890123456789");
      stringstream t_ss("abcdefghijklmnopqrstuvwxyz");
      string str1(ss.str());
  
      const char* cstr1 = str1.c_str();
      const char* cstr2 = ss.str().c_str();
      const char* cstr3 = ss.str().c_str();
      const char* cstr4 = ss.str().c_str();
      const char* t_cstr = t_ss.str().c_str(); 
  
      cout << "------ The results ----------" << endl
           << "cstr1:\t" << cstr1 << endl 
           << "cstr2:\t" << cstr2 << endl
           << "cstr3:\t" << cstr3 << endl
           << "cstr4:\t" << cstr4 << endl
           << "t_cstr:\t" << t_cstr << endl
           << "-----------------------------"  << endl;
  
      return 0;
  }

在看這段代碼的輸出結(jié)果之前恭理,先問大家一個(gè)問題颜价,這里cstr1周伦、cstr2专挪、cstr3和cstr4 打印出來結(jié)果是一樣的么寨腔?(相信讀者心里會(huì)想:結(jié)果肯定不一樣的嘛迫卢,否則不用在這里“故弄玄虛”了。哈哈

接下來,我們來看一下這段代碼的輸出結(jié)果:

    ------ The results ----------
    cstr1:  012345678901234567890123456789012345678901234567890123456789
    cstr2:  012345678901234567890123456789012345678901234567890123456789
    cstr3:  abcdefghijklmnopqrstuvwxyz
    cstr4:  abcdefghijklmnopqrstuvwxyz
    t_cstr: abcdefghijklmnopqrstuvwxyz
    -----------------------------

這里,我們驚奇地發(fā)現(xiàn)cstr3和cstr4竟然不是ss所表示的數(shù)字字符串篡九,而是t_ss所表示的字母字符串醋奠,這也太詭異了吧沛善,但我們相信“真相只有一個(gè)”塞祈。下面我們通過再加幾行代碼來看看议薪,為什么會(huì)出現(xiàn)這個(gè)“詭異”的現(xiàn)象。

   #include <string>
   #include <sstream>
   #include <iostream>
   
   using namespace std;
   
   #define PRINT_CSTR(no) printf("cstr" #no " addr:\t%p\n",cstr##no)
   #define PRINT_T_CSTR(no) printf("t_cstr" #no " addr:\t%p\n",t_cstr##no)
   
  int main()
  {
      stringstream ss("012345678901234567890123456789012345678901234567890123456789");
      stringstream t_ss("abcdefghijklmnopqrstuvwxyz");
      string str1(ss.str());
  
      const char* cstr1 = str1.c_str();
      const char* cstr2 = ss.str().c_str();
      const char* cstr3 = ss.str().c_str();
      const char* cstr4 = ss.str().c_str();
      const char* t_cstr = t_ss.str().c_str(); 
  
      cout << "------ The results ----------" << endl
           << "cstr1:\t" << cstr1 << endl 
           << "cstr2:\t" << cstr2 << endl
           << "cstr3:\t" << cstr3 << endl
           << "cstr4:\t" << cstr4 << endl
           << "t_cstr:\t" << t_cstr << endl
           << "-----------------------------"  << endl;
      printf("\n------ Char pointers ----------\n");
      PRINT_CSTR(1);
      PRINT_CSTR(2);
      PRINT_CSTR(3);
      PRINT_CSTR(4);
      PRINT_T_CSTR();
  
      return 0;
  }

在上述代碼中产捞,我們把那幾個(gè)字符串對應(yīng)的地址打印出來,其輸出結(jié)果為:

    ------ The results ----------
    cstr1:  012345678901234567890123456789012345678901234567890123456789
    cstr2:  012345678901234567890123456789012345678901234567890123456789
    cstr3:  abcdefghijklmnopqrstuvwxyz
    cstr4:  abcdefghijklmnopqrstuvwxyz
    t_cstr: abcdefghijklmnopqrstuvwxyz
    -----------------------------

    ------ Char pointers ----------
    cstr1 addr:     0x100200e4
    cstr2 addr:     0x10020134
    cstr3 addr:     0x10020014
    cstr4 addr:     0x10020014
    t_cstr addr:    0x10020014

從上面的輸出哼御,我們發(fā)現(xiàn)cstr3和cstr4字串符的地址跟t_cstr是一樣坯临,因此,cstr3艇搀、cstr4和t_cstr的打印結(jié)果是一樣的尿扯。按照我們通常的理解,當(dāng)?shù)?7-19行調(diào)用ss.str()時(shí)焰雕,將會(huì)產(chǎn)生三個(gè)string對象衷笋,其對應(yīng)的字符串也將會(huì)是不同的地址。

而打印的結(jié)果告訴我們矩屁,真實(shí)情況不是這樣的辟宗。其實(shí),streamstring在調(diào)用str()時(shí),會(huì)返回臨時(shí)的string對象秕铛。而因?yàn)槭桥R時(shí)的對象,所以它在整個(gè)表達(dá)式結(jié)束后將會(huì)被析構(gòu)。由于緊接著調(diào)用的c_str()函數(shù)將得到的是這些臨時(shí)string對象對應(yīng)的C string,而它們在這個(gè)表達(dá)式結(jié)束后是不被引用的,進(jìn)而這塊內(nèi)存將被回收而可能被別的內(nèi)容所覆蓋,因此我們將無法得到我們想要的結(jié)果这敬。雖然有些情況下冷蚂,這塊內(nèi)存并沒有被別的內(nèi)容所覆蓋隆夯,于是我們?nèi)匀荒軌蜃x到我們期望的字符串,(這點(diǎn)在這個(gè)例子中,可以通過將第20行刪除來體現(xiàn))托嚣。但我們要強(qiáng)調(diào)的是,這種行為的正確性將是不被保證的提揍。

通過上述分析刨仑,我們將代碼修改如下:

   #include <string>
   #include <sstream>
   #include <iostream>
   
   using namespace std;
   
   #define PRINT_CSTR(no) printf("cstr" #no " addr:\t%p\n",cstr##no)
   #define PRINT_T_CSTR(no) printf("t_cstr" #no " addr:\t%p\n",t_cstr##no)
   
  int main()
  {
      stringstream ss("012345678901234567890123456789012345678901234567890123456789");
      stringstream t_ss("abcdefghijklmnopqrstuvwxyz");
      string str1(ss.str());
  
      const char* cstr1 = str1.c_str();
      const string& str2 = ss.str();
      const char* cstr2 = str2.c_str();
      const string& str3 = ss.str();
      const char* cstr3 = str3.c_str();
      const string& str4 = ss.str();
      const char* cstr4 = str4.c_str();
      const char* t_cstr = t_ss.str().c_str(); 
  
      cout << "------ The results ----------" << endl
           << "cstr1:\t" << cstr1 << endl 
           << "cstr2:\t" << cstr2 << endl
           << "cstr3:\t" << cstr3 << endl
           << "cstr4:\t" << cstr4 << endl
           << "t_cstr:\t" << t_cstr << endl
           << "-----------------------------"  << endl;
      printf("\n------ Char pointers ----------\n");
      PRINT_CSTR(1);
      PRINT_CSTR(2);
      PRINT_CSTR(3);
      PRINT_CSTR(4);
      PRINT_T_CSTR();
  
      return 0;
  }

現(xiàn)在我們將獲得我們所期望的輸出結(jié)果了:

    ------ The results ----------
    cstr1:  012345678901234567890123456789012345678901234567890123456789
    cstr2:  012345678901234567890123456789012345678901234567890123456789
    cstr3:  012345678901234567890123456789012345678901234567890123456789
    cstr4:  012345678901234567890123456789012345678901234567890123456789
    t_cstr: abcdefghijklmnopqrstuvwxyz
    -----------------------------

    ------ Char pointers ----------
    cstr1 addr:     0x100200e4
    cstr2 addr:     0x10020134
    cstr3 addr:     0x10020184
    cstr4 addr:     0x100201d4
    t_cstr addr:    0x10020014

現(xiàn)在我們知道stringstream.str()方法將返回一個(gè)臨時(shí)的string對象,而它的生命周期將在本表達(dá)式結(jié)束后完結(jié)。當(dāng)我們需要對這個(gè)string對象進(jìn)行進(jìn)一步操作(例如獲得對應(yīng)的C string)時(shí),我們需要注意這個(gè)可能會(huì)導(dǎo)致非預(yù)期結(jié)果的“陷阱”。:)

最后,我們想強(qiáng)調(diào)一下:由于臨時(shí)對象占用內(nèi)存空間被重新使用的不確定性,這個(gè)陷阱不一定會(huì)明顯暴露出來。但不暴露出來不代表行為的正確性,為了避免“詭異”問題的發(fā)生,請盡量采用能保證正確的寫法。

正確的寫法應(yīng)該是string str = stringstream.str();

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朴上,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖元潘,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咖摹,死亡現(xiàn)場離奇詭異胁后,居然都是意外死亡敲才,警方通過查閱死者的電腦和手機(jī)已添,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膀哲,“玉大人,你說我怎么就攤上這事兴喂。” “怎么了汗菜?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糜工。 經(jīng)常有香客問我,道長链瓦,這世上最難降的妖魔是什么突梦? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮宫患,結(jié)果婚禮上刊懈,老公的妹妹穿的比我還像新娘。我一直安慰自己娃闲,他們只是感情好虚汛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畜吊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪户矢。 梳的紋絲不亂的頭發(fā)上玲献,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音梯浪,去河邊找鬼捌年。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挂洛,可吹牛的內(nèi)容都是我干的礼预。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼虏劲,長吁一口氣:“原來是場噩夢啊……” “哼托酸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柒巫,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤励堡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后堡掏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體应结,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹅龄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揩慕。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扮休,靈堂內(nèi)的尸體忽然破棺而出迎卤,到底是詐尸還是另有隱情,我是刑警寧澤肛炮,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布止吐,位于F島的核電站,受9級(jí)特大地震影響侨糟,放射性物質(zhì)發(fā)生泄漏碍扔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一秕重、第九天 我趴在偏房一處隱蔽的房頂上張望不同。 院中可真熱鬧,春花似錦溶耘、人聲如沸二拐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽百新。三九已至,卻和暖如春庐扫,著一層夾襖步出監(jiān)牢的瞬間饭望,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工形庭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铅辞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓萨醒,卻偏偏與公主長得像斟珊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子富纸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法囤踩,類相關(guān)的語法,內(nèi)部類的語法晓褪,繼承相關(guān)的語法高职,異常的語法,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程辞州,因...
    小菜c閱讀 6,401評論 0 17
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理怔锌,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 貓咪睡著了 春天的花開了又謝 再看一眼埃元,醒來之后 這世界早已滄海桑田
    蛇神閱讀 234評論 0 0
  • Book 摘自 知乎問題 自學(xué)吉他涝涤,哪本教程(書)會(huì)比較好?大倪Ni 同學(xué)的回答, 稍作整理如下: 樂理教材基礎(chǔ)...
    龐貝船長閱讀 479評論 0 0