在編寫應(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();