1. 程式人生 > >【C++】使用sstream標頭檔案進行格式轉換

【C++】使用sstream標頭檔案進行格式轉換

sstresam是C++中的一個頭檔案,提供了比ANSI C的<stdio.h>更高階的一些功能,即單純性、型別安全和可擴充套件性。本文討論如何使用裡面的物件安全的進行型別轉換。

一、基本用法

1. 簡介

C++引入了ostringstream、istringstream、stringstream這三個類,要使用他們建立物件就必須包含sstream.h標頭檔案。

  • istringstream類用於執行C++風格的串流的輸入操作。
  • ostringstream類用於執行C風格的串流的輸出操作。
  • stringstream類同時可以支援C風格的串流的輸入輸出操作。

istringstream類是從istreamstringstreambase派生而來,ostringstream是從ostreamstringstreambase派生而來, stringstream則是從iostream類和stringstreambase派生而來。

他們的繼承關係如下圖所示:
  這裡寫圖片描述

2. istringstream

istringstream是由一個string物件構造而來,istringstream類從一個string物件讀取字元。
istringstream的建構函式原形如下:
istringstream::istringstream(string str);

#include <iostream>  
#include <sstream>  

using namespace std;  

int main()  
{  
    istringstream istr;  
    istr.str("1 56.7");  
    //上述兩個過程可以簡單寫成 istringstream istr("1 56.7");  
    cout << istr.str() << endl;  
    int a;  
    float b;  
    istr >> a;   
    istr >> b;     //可以簡單的寫成 istr >> a >> b
cout << a << endl; cout << b << endl; return 0; }

能夠正確的賦值a = 1 ,b=56.7
上例中,構造字串流的時候,空格會成為字串引數的內部分界,例子中對a,b物件的輸入”賦值”操作證明了這一點,字串的空格成為了整型資料與浮點型資料的分解點,利用分界獲取的方法我們事實上完成了字串到整型物件與浮點型物件的拆分轉換過程。
  str()成員函式的使用可以讓istringstream物件返回一個string字串(例如本例中的輸出操作cout<<istr.str();)

3. ostringstream

ostringstream同樣是由一個string物件構造而來,ostringstream類向一個string插入字元。
ostringstream的建構函式原形如下:
ostringstream::ostringstream(string str);

#include <iostream> 
#include <sstream> 
#include <string> 
using namespace std; 
int main()   
{ 
    ostringstream ostr; 
    //ostr.str("abcdefghjk");//如果構造的時候設定了字串引數,那麼增長操作的時候不會從結尾開始增加,而是修改原有資料,超出的部分增長 
    ostr.put('d'); 
    ostr.put('e'); 
    ostr<<"fg"; 

    string gstr = ostr.str(); 
    cout<<gstr; 
    system("pause"); 
}

輸出: defg
如果開啟註釋部分,那麼輸出結果defgefghjk
   在上例程式碼中,我們通過put()或者左移操作符可以不斷向ostr插入單個字元或者是字串,通過str()函式返回增長過後的完整字串資料,但值 得注意的一點是,當構造的時候物件內已經存在字串資料的時候,那麼增長操作的時候不會從結尾開始增加,而是修改原有資料,超出的部分增長。
[ basic_stringbuf::str : Sets or gets the text in a string buffer without changing the write position. ]

4. stringstream

對於stringstream了來說,不用我多說,大家也已經知道它是用於C++風格的字串的輸入輸出的。
stringstream的建構函式原形如下:
  stringstream::stringstream(string str);

#include <iostream> 
#include <sstream> 
using namespace std; 
int main()   
{ 
    stringstream sstr; 
    sstr << "1 56.7";   // 或者 sstr.str("1 56.7",); 
                        // 可以簡單寫成 istringstream istr("1 56.7"); 
    cout << sstr.str()<<endl; 
    int a; 
    float b; 
    sstr >> a >> b; 
    cout<<"a = "<< a <<",b = "<< b << endl; 

    system("pause"); 
}

輸出:

1 56.7
a = 1b = 56.7

二、型別資料的轉換

1. 開場示例

不同的資料型別,可以通過建立stringstream物件進行轉換。具體看程式碼

#include <iostream> 
#include <sstream> 
#include <string> 
using namespace std; 

int main()   
{ 
    stringstream sstr; 
    //--------int轉string----------- 
    int a = 100; 
    string str; 
    sstr << a; 
    sstr >> str; 
    cout << str << endl; 

    //--------string轉int--------
    sstr.clear();//如果你想通過使用同一stringstream物件實現多種型別的轉換,請注意在每一次轉換之後都必須呼叫clear()成員函式。 
    string str_int = "10203";
    int b;
    sstr << str_int; 
    sstr >> b; 
    cout << b << endl; //轉float也是一樣的

    //--------string轉char[]-------- 
    sstr.clear();
    string name = "colinguan"; 
    char cname[200]; 
    sstr << name; 
    sstr >> cname; 
    cout << cname << endl; 

    //--------string轉多型別-------- 
    sstr.clear();
    string str_tmp = "frank 2013 56.7890"; 
    string str_name;
    int str_year;
    float str_height;

    sstr << str_tmp; 
    sstr >> str_name >> str_year >> str_height;
    cout << "str_name:" << str_name
        << ",str_year:" << str_year
        << ",str_height:" << str_height << endl;
    system("pause"); 
}

輸出結果

100
10203
colinguan
str_name:frank,str_year:2013,str_height:56.789

2. 優勢

如果你已習慣了<stdio.h>風格的轉換,也許你首先會問:為什麼要花額外的精力來學習基於<sstream>的型別轉換呢?也許對下面一個簡單的例子的回顧能夠說服你。假設你想用sprintf()函式將一個變數從int型別轉換到字串型別。為了正確地完成這個任務,你必須確保證目標緩衝區有足夠大空間以容納轉換完的字串。此外,還必須使用正確的格式化符。如果使用了不正確的格式化符,會導致非預知的後果。下面是一個例子:

int n=10000;
chars[10];
sprintf(s,”%d”,n);// s中的內容為“10000

到目前為止看起來還不錯。但是,對上面程式碼的一個微小的改變就會使程式崩潰:

int n=10000;
char s[10];
sprintf(s,”%f”,n);// 看!錯誤的格式化符

在這種情況下,程式設計師錯誤地使用了%f格式化符來替代了%d。因此,s在呼叫完sprintf()後包含了一個不確定的字串。要是能自動推匯出正確的型別,那不是更好嗎?

由於ns的型別在編譯期就確定了,所以編譯器擁有足夠的資訊來判斷需要哪些轉換。<sstream>庫中宣告的標準類就利用了這一點,自動選擇所必需的轉換。而且,轉換結果儲存在stringstream物件的內部緩衝中。你不必擔心緩衝區溢位,因為這些物件會根據需要自動分配儲存空間。

3. 你的編譯器支援<sstream>嗎?

<sstream>庫是最近才被列入C++標準的。(不要把<sstream>與標準釋出前被刪掉的<strstream>弄混了。)因此,老一點的編譯器,如GCC2.95,並不支援它。如果你恰好正在使用這樣的編譯器而又想使用<sstream>的話,就要先對它進行升級更新。

<sstream>庫定義了三種類:istringstream、ostringstream和stringstream,分別用來進行流的輸入、輸出和輸入輸出操作。另外,每個類都有一個對應的寬字符集版本。簡單起見,我主要以stringstream為中心,因為每個轉換都要涉及到輸入和輸出操作。

注意,<sstream>使用string物件來代替字元陣列。這樣可以避免緩衝區溢位的危險。而且,傳入引數和目標物件的型別被自動推匯出來,即使使用了不正確的格式化符也沒有危險。

4. 重複利用stringstream物件

如果你打算在多次轉換中使用同一個stringstream物件,記住再每次轉換前要使用clear()方法;
在多次轉換中重複使用同一個stringstream(而不是每次都建立一個新的物件)物件最大的好處在於效率。stringstream物件的構造和解構函式通常是非常耗費CPU時間的。

5. 在型別轉換中使用模板

你可以輕鬆地定義函式模板來將一個任意的型別轉換到特定的目標型別。例如,需要將各種數字值,如int、long、double等等轉換成字串,要使用以一個string型別和一個任意值t為引數的to_string()函式。to_string()函式將t轉換為字串並寫入result中。使用str()成員函式來獲取流內部緩衝的一份拷貝:

template<class T>
void to_string(const T& t, string & result)
{
    ostringstream oss;//建立一個流
    oss << t;//把值傳遞如流中
    result = oss.str();//獲取轉換後的字元轉並將其寫入result
}

這樣,你就可以輕鬆地將多種數值轉換成字串了:

  • to_string(10.5,s1); //double到string
  • to_string(123,s2); //int到string
  • to_string(true,s3); //bool到string

可以更進一步定義一個通用的轉換模板,用於任意型別之間的轉換。函式模板convert()含有兩個模板引數out_typein_value,功能是將in_value值轉換成out_type型別:

template<class out_type, class in_value>
out_type convert(const in_value & t)
{
    stringstream stream;
    stream << t;      //向流中傳值
    out_type result;  //這裡儲存轉換結果
    stream >> result; //向result中寫入值
    return result;
}

這樣使用convert()

double d;
string salary;
string s = "12.56";

d = convert<double>(s);          //d等於12.56
salary = convert<string>(9000.0);//salary等於9000

結論 : 在過去留下來的程式程式碼和純粹的C程式中,傳統的<stdio.h>形式的轉換伴隨了我們很長的一段時間。但是,如文中所述,基於stringstream的轉換擁有型別安全和不會溢位這樣搶眼的特性,使我們有充足得理由拋棄<stdio.h>而使用<sstream><sstream>庫還提供了另外一個特性—可擴充套件性。你可以通過過載來支援自定義型別間的轉換。

三、stringstream.str()用法的陷阱

C++標準庫中的<string><sstream>為我們操作字串提供了很多的方便,例如:物件封裝、安全和自動的型別轉換、直接拼接、不必擔心越界等等。但是stringstream.str()有一個有趣的現象。我們先來看一個例子:

#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;
} 

在看這段程式碼的輸出結果之前,先問大家一個問題,這裡cstr1、cstr2、cstr3和cstr4打印出來結果是一樣的麼?(相信讀者心裡會想 : 結果肯定不一樣的嘛,否則不用在這裡“故弄玄虛”了。哈哈)

接下來,我們來看一下這段程式碼的輸出結果:

  • 在linux下執行
  ------ The results ----------
  cstr1:  012345678901234567890123456789012345678901234567890123456789
  cstr2:  012345678901234567890123456789012345678901234567890123456789
  cstr3:  abcdefghijklmnopqrstuvwxyz
  cstr4:  abcdefghijklmnopqrstuvwxyz
  t_cstr: abcdefghijklmnopqrstuvwxyz
  -----------------------------
  • windows 下使用Visual stdio 2010執行
------ The results ----------
cstr1:  012345678901234567890123456789012345678901234567890123456789
cstr2:  鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
[?We
cstr3:  鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
[?We
cstr4:  鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
[?We
t_cstr: 鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿鉿
[?We
-----------------------------

這裡咱們就以Linux下的執行結果作為標準來進行討論。

我們驚奇地發現cstr3和cstr4竟然不是ss所表示的數字字串,而是t_ss所表示的字母字串,這也太詭異了吧,但我們相信“真相只有一個”。下面我們通過再加幾行程式碼來看看,為什麼會出現這個“詭異”的現象。

#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;
} 

在上述程式碼中,我們把那幾個字串對應的地址打印出來,其輸出結果為:

    ------ 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

從上面的輸出,我們發現cstr3cstr4字串符的地址跟t_cstr是一樣,因此,cstr3、cstr4和t_cstr的列印結果是一樣的。按照我們通常的理解,當第17-19行呼叫ss.str()時,將會產生三個string物件,其對應的字串也將會是不同的地址。

而列印的結果告訴我們,真實情況不是這樣的。其實,streamstring在呼叫str()時,會返回臨時的string物件。而因為是臨時的物件,所以它在整個表示式結束後將會被析構。由於緊接著呼叫的c_str()函式將得到的是這些臨時string物件對應的C string,而它們在這個表示式結束後是不被引用的,進而這塊記憶體將被回收而可能被別的內容所覆蓋,因此我們將無法得到我們想要的結果。雖然有些情況下,這塊記憶體並沒有被別的內容所覆蓋,於是我們仍然能夠讀到我們期望的字串,(這點在這個例子中,可以通過將第20行刪除來體現)。但我們要強調的是,這種行為的正確性將是不被保證的。

通過上述分析,我們將程式碼修改如下:

#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;
}

現在我們將獲得我們所期望的輸出結果了:

    ------ 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

現在我們知道stringstream.str()方法將返回一個臨時的string物件,而它的生命週期將在本表示式結束後完結。當我們需要對這個string物件進行進一步操作(例如獲得對應的C string)時,我們需要注意這個可能會導致非預期結果的“陷阱”。:)

強調一下:由於臨時物件佔用記憶體空間被重新使用的不確定性,這個陷阱不一定會明顯暴露出來。但不暴露出來不代表行為的正確性,為了避免“詭異”問題的發生,請儘量採用能保證正確的寫法。