程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 輸入輸出流,看似復雜卻更好用----小話c++(2)

輸入輸出流,看似復雜卻更好用----小話c++(2)

編輯:C++入門知識

[Mac 10.7.1  Lion  Intel-based  x64  gcc4.2.1  xcode4.2]

 


Q: c++的默認輸入輸出和c的有什麼優缺點?

A: 任何一種語言都會有自身最推薦的一套模式,因為設計者可能已經很認真考慮了。沒有必要說哪種最好,哪種很差,最好和很差都有條件限制的。不同的使用者總能找到一種最適合自身的方式。

    c++的輸入輸出更面向對象化和模塊化,它的操作其實蘊含著對於輸入或者輸出對象的操作,是輸出一個整形,還是一個字符,或是一個自定義對象。而c中更能感受到函數和過程化的思維,輸出沒必要告訴使用者要使用一個對象,告訴我要輸出什麼,我就給你輸出什麼。

    因為c++的對象化,所以不用像printf一樣一定要傳入一個字符串,而是將輸出的變量單獨寫出;這就導致有時用c++的方式寫起來更方便,而有的時候在控制復雜的輸出的時刻顯得更復雜,printf的格式字符串中一個小小的格式變化即可輸出不同情況,而使用cout可能需要修改更多的代碼。

    c++的對象化以及重載運算符的方式,讓輸出一個對象的具體操作可以轉移到對象內部,讓對象自身來實現具體的輸出;而printf默認沒有此功能,程序員不得不在printf的時候加上具體對象輸出邏輯或者封裝一個輸出對象數據的函數,這可能讓程序員有時不能很好地記憶到底某個對象用什麼輸出函數,而c++將接口定義好了,就是<<, 看起來更易於使用。

    c語言中的printf函數的執行時采用動態解析格式字符串,根據是否是%d來輸出一個整形,是否是%c來輸出一個字符的方式,可能會導致運行時效率損失;而c++的輸出方式,編譯期間即可確定輸出的是什麼,這是printf效率在有時有可能低於cout的一個因素。

    c++和c語言的輸入輸出混用有時可能導致一些意想不到的問題,主要在於c++庫對於c庫的部分依賴性。所以,要特別小心,如果對於內部的機制很不清楚,最好不要隨便混用。

 


Q: 對於輸入輸出中的各個類,它們之間到底是什麼關系?

A: c++對於輸入輸出,有很多設計,它們共同形成了可以進行輸入輸出功能的簡潔且易擴展的類設計圖。下面將一一分析:

對於cout對象,它是ostream的一個對象,ostream的定義如下:

[cpp]  typedef basic_istream<char>       istream;    ///< @isiosfwd  
typedef basic_ostream<char>       ostream;    ///< @isiosfwd  
typedef basic_iostream<char>      iostream;   ///< @isiosfwd 
  typedef basic_istream<char>   istream; ///< @isiosfwd
  typedef basic_ostream<char>   ostream; ///< @isiosfwd
  typedef basic_iostream<char>   iostream; ///< @isiosfwd    同樣,包括istream和iostream的定義。對於istream, ostream和iostream, 它們實現了輸入和輸出的具體功能(當然,它們內部還將調用其它內部函數實現最終的輸入輸出)。
    basic_istream類的實現如下(部分):

[cpp] template<typename _CharT, typename _Traits> 
  class basic_istream : virtual public basic_ios<_CharT, _Traits> 
  template<typename _CharT, typename _Traits>
    class basic_istream : virtual public basic_ios<_CharT, _Traits>[cpp] __istream_type&  
      operator>>(bool& __n) 
      { return _M_extract(__n); } 
       
      __istream_type&  
      operator>>(short& __n); 
       
      __istream_type&  
      operator>>(unsigned short& __n) 
      { return _M_extract(__n); } 
 
      __istream_type&  
      operator>>(int& __n); 
     
      __istream_type&  
      operator>>(unsigned int& __n) 
      { return _M_extract(__n); } 
 
      __istream_type&  
      operator>>(long& __n) 
      { return _M_extract(__n); } 
__istream_type&
      operator>>(bool& __n)
      { return _M_extract(__n); }
     
      __istream_type&
      operator>>(short& __n);
     
      __istream_type&
      operator>>(unsigned short& __n)
      { return _M_extract(__n); }

      __istream_type&
      operator>>(int& __n);
   
      __istream_type&
      operator>>(unsigned int& __n)
      { return _M_extract(__n); }

      __istream_type&
      operator>>(long& __n)
      { return _M_extract(__n); }[cpp] __istream_type& 
  get(__streambuf_type& __sb) 
  { return this->get(__sb, this->widen('\n')); } 
 
  int_type  
  peek();       
 
  __istream_type&  
  putback(char_type __c); 
 
    __istream_type&
      get(__streambuf_type& __sb)
      { return this->get(__sb, this->widen('\n')); }
 
      int_type
      peek();     

      __istream_type&
      putback(char_type __c);    可以看出,它實現了輸入一個變量以及獲取輸入流最後數據、返回給輸入流數據等一些列操作。
    如下是basic_ostream的實現(部分):

[cpp] template<typename _CharT, typename _Traits> 
  class basic_ostream : virtual public basic_ios<_CharT, _Traits> 
  template<typename _CharT, typename _Traits>
    class basic_ostream : virtual public basic_ios<_CharT, _Traits>[cpp] __ostream_type&  
operator<<(long __n) 
{ return _M_insert(__n); } 
 
__ostream_type&  
operator<<(unsigned long __n) 
{ return _M_insert(__n); }   
 
__ostream_type&  
operator<<(bool __n) 
{ return _M_insert(__n); } 
 
__ostream_type&  
operator<<(short __n); 
      __ostream_type&
      operator<<(long __n)
      { return _M_insert(__n); }
     
      __ostream_type&
      operator<<(unsigned long __n)
      { return _M_insert(__n); } 

      __ostream_type&
      operator<<(bool __n)
      { return _M_insert(__n); }

      __ostream_type&
      operator<<(short __n);[cpp] __ostream_type&  
put(char_type __c); 
 
__ostream_type&  
flush(); 
      __ostream_type&
      put(char_type __c);

      __ostream_type&
      flush();
    可以看出,它實現了具體的輸出不同類型數據以及刷新等操作。同時,也可以看到,它們均繼承了basic_ios類,下面是它的部分實現:[cpp] template<typename _CharT, typename _Traits> 
  class basic_ios : public ios_base 
  template<typename _CharT, typename _Traits>
    class basic_ios : public ios_base[cpp] void 
clear(iostate __state = goodbit); 
 
bool 
good() const 
{ return this->rdstate() == 0; } 
 
bool 
eof() const 
{ return (this->rdstate() & eofbit) != 0; } 
 
bool 
fail() const 
{ return (this->rdstate() & (badbit | failbit)) != 0; } 
 
bool 
bad() const 
{ return (this->rdstate() & badbit) != 0; } 
      void
      clear(iostate __state = goodbit);

      bool
      good() const
      { return this->rdstate() == 0; }

      bool
      eof() const
      { return (this->rdstate() & eofbit) != 0; }

      bool
      fail() const
      { return (this->rdstate() & (badbit | failbit)) != 0; }

      bool
      bad() const
      { return (this->rdstate() & badbit) != 0; }

可以看出,basic_ios內部實現了basic_istream和basic_ostream類共同可能需要執行的地方:緩沖區狀態等。basic_ios繼承了ios_base類,它的部分實現如下:[cpp] class ios_base 
  class ios_base[cpp]    
inline streamsize 
   precision() const { return _M_precision; } 
 
   inline streamsize 
   precision(streamsize __prec) 
   { 
     streamsize __old = _M_precision; 
     _M_precision = __prec; 
     return __old; 
   } 
 
   inline streamsize 
   width() const { return _M_width; } 
 
 inline ios_base& 
 boolalpha(ios_base& __base) 
 { 
   __base.setf(ios_base::boolalpha); 
   return __base; 
 } 
  
 inline streamsize
    precision() const { return _M_precision; }

    inline streamsize
    precision(streamsize __prec)
    {
      streamsize __old = _M_precision;
      _M_precision = __prec;
      return __old;
    }

    inline streamsize
    width() const { return _M_width; }

  inline ios_base&
  boolalpha(ios_base& __base)
  {
    __base.setf(ios_base::boolalpha);
    return __base;
  }
它主要實現了精度控制、寬度等一些更基本的流控制。
 而對於iostream類,它繼承了istream和ostream.

[cpp] template<typename _CharT, typename _Traits> 
  class basic_iostream 
  : public basic_istream<_CharT, _Traits>,  
    public basic_ostream<_CharT, _Traits> 
  template<typename _CharT, typename _Traits>
    class basic_iostream
    : public basic_istream<_CharT, _Traits>,
      public basic_ostream<_CharT, _Traits>可以看出,它可以實現istream和ostream的所有功能。還有,ifstream, ofstream, fstream, istrstream, ostrstream, strstream以及strstreambase和上面各個類的繼承關系可以類似分析。
 總的說來,按照輸入輸出的最終功能,將它的實現抽象出共同完成的部分以及單獨完成的部分,即為上面的設計圖。

 


Q:istrstream, ostrstream以及strstream,它們究竟要實現什麼?

A: 把它們和c中的sprintf, sscanf聯系起來就理解了。它們只不過是要實現如何對內存中一塊所謂的字符串進行拼接或者取出的操作。不過c++已經將這幾個類標記為廢棄了,類似功能的類為stringstream, istringstream, ostringstream.如下舉個例子:

    因為mac下xcode對於stringstream的處理稍有bug, 下面直接使用gcc4.2.1編譯:

[cpp] #include <iostream>  
#include <sstream>  
 
#define COUT_ENDL(str)  std::cout << #str << " is " << (str) << std::endl;  
 
int main() 

    std::stringstream s; 
    int i = 128; 
    char buf[32]; 
    int value; 
    s << "hello" << " " << i; 
    s >> buf; 
    s >> value; 
    COUT_ENDL(buf) 
    COUT_ENDL(value) 
    return 0; 

#include <iostream>
#include <sstream>

#define COUT_ENDL(str)  std::cout << #str << " is " << (str) << std::endl;

int main()
{
    std::stringstream s;
    int i = 128;
    char buf[32];
    int value;
    s << "hello" << " " << i;
    s >> buf;
    s >> value;
    COUT_ENDL(buf)
    COUT_ENDL(value)
    return 0;
}保存為stringstream.cpp, 使用g++ stringstream.cpp -o stringstream編譯,運行:[plain] buf is hello 
value is 128 
buf is hello
value is 128
其實,很簡單,使用 << 將後面的數據存入字符串中,注意是用字符串格式,i是個整形依然被轉換成字符串格式; >> 將數據提取出來,到後面的變量中。

 

 

Q: 感覺c++的輸入輸出的形式很多很多,而且有的地方可能會混淆一些操作,為什麼會出現這種情況?

A: 出現這個問題的原因主要在於c++使用類的概念,而且輸入輸出牽扯到較多的類繼承體系,每個類都可能有一些可以被調用的公共函數或者可使用的公共變量,導致最終使用子類進行操作的時候,同樣可以使用基類的公共函數得到一些信息,這個可能導致可以操作輸入輸出流的方式極大增加。但是,不管有多少變形的方式,基本功能依然是那些:輸入,輸出,查詢流狀態,設置流參數和刷新。總而言之,不管你想封裝多少函數,c++不會阻止你,不過鏈接器可能阻止最終的可執行文件的生成(如果最終的可執行文件超過系統限制).

 

 

Q: 對於cout, 形如cout << endl 這種形式為什麼可以通過編譯?

A: 這種形式和cout << boolalpha也是類似的。形如如下代碼:

[cpp] #include <iostream>  
 
using namespace std; 
 
int main (int argc, const char * argv[]) 

    bool b = true; 
    cout << boolalpha << b << endl; 
    return 0; 

#include <iostream>

using namespace std;

int main (int argc, const char * argv[])
{
    bool b = true;
    cout << boolalpha << b << endl;
    return 0;
}根據重載運算符,如下代碼亦是可以的:
[cpp] #include <iostream>  
 
using namespace std; 
 
int main (int argc, const char * argv[]) 

    bool b = true; 
    cout.operator<<(boolalpha).operator<<(b).operator<<(endl); 
    return 0; 

#include <iostream>

using namespace std;

int main (int argc, const char * argv[])
{
    bool b = true;
    cout.operator<<(boolalpha).operator<<(b).operator<<(endl);
    return 0;
}那麼boolalpha和endl到底是什麼呢?
查看endl的聲明和定義,發現如下,在basic_ostream類中:

[cpp] template<typename _CharT, typename _Traits> 
  inline basic_ostream<_CharT, _Traits>&  
  endl(basic_ostream<_CharT, _Traits>& __os) 
  { return flush(__os.put(__os.widen('\n'))); } 
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    endl(basic_ostream<_CharT, _Traits>& __os)
    { return flush(__os.put(__os.widen('\n'))); }boolalpha的聲明和定義,如下在ios_base類中:
[cpp] inline ios_base& 
boolalpha(ios_base& __base) 

  __base.setf(ios_base::boolalpha); 
  return __base; 

  inline ios_base&
  boolalpha(ios_base& __base)
  {
    __base.setf(ios_base::boolalpha);
    return __base;
  }既然如此,如下的代碼就應該可以運行:
[cpp] #include <iostream>  
 
using namespace std; 
 
int main (int argc, const char * argv[]) 

    bool b = true; 
 
    boolalpha(cout); 
    cout << b; 
    endl(cout); 
 
    return 0; 

#include <iostream>

using namespace std;

int main (int argc, const char * argv[])
{
    bool b = true;

    boolalpha(cout);
    cout << b;
    endl(cout);

    return 0;
}運行,和上面代碼效果一致。從上可以看出,boolalpha和endl是函數,它們的原型分別為:
[cpp] inline ios_base& 
boolalpha(ios_base& __base); 
  inline ios_base&
  boolalpha(ios_base& __base);[cpp] template<typename _CharT, typename _Traits> 
  inline basic_ostream<_CharT, _Traits>&  
  endl(basic_ostream<_CharT, _Traits>& __os); 
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    endl(basic_ostream<_CharT, _Traits>& __os);
從basic_ostream類中可以找到如下函數:[cpp]       __ostream_type& 
      operator<<(__ostream_type& (*__pf)(__ostream_type&)) 
      { 
    // _GLIBCXX_RESOLVE_LIB_DEFECTS  
    // DR 60. What is a formatted input function?  
    // The inserters for manipulators are *not* formatted output functions.  
    return __pf(*this); 
      } 
 
__ostream_type& 
      operator<<(ios_base& (*__pf) (ios_base&)) 
      { 
    // _GLIBCXX_RESOLVE_LIB_DEFECTS  
    // DR 60. What is a formatted input function?  
    // The inserters for manipulators are *not* formatted output functions.  
    __pf(*this); 
    return *this; 
      } 
 
typedef basic_ostream<_CharT, _Traits> __ostream_type; 
      __ostream_type&
      operator<<(__ostream_type& (*__pf)(__ostream_type&))
      {
 // _GLIBCXX_RESOLVE_LIB_DEFECTS
 // DR 60. What is a formatted input function?
 // The inserters for manipulators are *not* formatted output functions.
 return __pf(*this);
      }

__ostream_type&
      operator<<(ios_base& (*__pf) (ios_base&))
      {
 // _GLIBCXX_RESOLVE_LIB_DEFECTS
 // DR 60. What is a formatted input function?
 // The inserters for manipulators are *not* formatted output functions.
 __pf(*this);
 return *this;
      }

typedef basic_ostream<_CharT, _Traits> __ostream_type;


   
這樣就簡單了,cout.operator<<(boolalpha)實際上是調用了cout的operator<<(ios_base &(*__pf)(ios_base &))函數,而cout.operator<<(endl)實際上是調用了cout的operator<<(__ostream_type &(*__pf)(__ostream_type &))函數。boolalpha和endl只不過是被當做函數指針傳入當參數而已。內部將分別調用boolalpha(cout);和endl(cout);來實現最終功能。我覺得這麼設計,其一好處無非是讓使用者更分便, 到處使用<<即可,不用想著到底是重載哪個函數,內部已經為上層做好了轉換。同時,對於操作子,最終不是cout在操作它,而是操作子在操作cout.


Q: cout << "\n";到底會不會刷新緩沖區?

A: 對於行緩沖來說,它當然會刷新緩沖區。千萬不要認為只有cout << endl;才會強制刷新緩沖區。如下代碼,

[cpp] #include <iostream>  
 
using namespace std; 
 
int main (int argc, const char * argv[]) 

    cout << "hello"; 
    cout << "ok\n";  
    cout << endl; 
    cout << "thanks"; 
     
    return 0; 

#include <iostream>

using namespace std;

int main (int argc, const char * argv[])
{
    cout << "hello";
    cout << "ok\n";
    cout << endl;
    cout << "thanks";
   
    return 0;
}在中間兩條cout語句和return  0; 處打上斷點,調試運行:

運行到cout << "ok\n"時,上面的"hello"並沒有被輸出,這說明在緩沖區中;當運行到cout << endl;時,hellook\n均輸出了,這說明\n確實導致了刷新緩沖區。當然也有可能是緩沖區太小(雖然不太可能是實際的狀態),把cout << "ok\n";改為cout << "ok";後再次調試到此,hellook並沒有輸出。 運行到return  0; 時發現thanks沒有被輸出,運行完,thanks才輸出。

當然,可以肯定的是,使用\n可能不會刷新緩沖區,但是endl一定會刷新緩沖區。

[cpp] template<typename _CharT, typename _Traits> 
  inline basic_ostream<_CharT, _Traits>&  
  endl(basic_ostream<_CharT, _Traits>& __os) 
  { return flush(__os.put(__os.widen('\n'))); } 
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    endl(basic_ostream<_CharT, _Traits>& __os)
    { return flush(__os.put(__os.widen('\n'))); }

Q: 經常使用cout的setf函數、flags函數以及使用操作子比如hex, dec等,這樣可能造成一個後果:此時輸出流的狀態已經不是很確定了,怎麼很好地區分它們的作用?

A: 看源碼是了解一個問題最直接的方式。

如下setf函數源碼:

[cpp] inline fmtflags 
setf(fmtflags __fmtfl) 

  fmtflags __old = _M_flags; 
  _M_flags |= __fmtfl; 
  return __old; 

    inline fmtflags
    setf(fmtflags __fmtfl)
    {
      fmtflags __old = _M_flags;
      _M_flags |= __fmtfl;
      return __old;
    }如下flags源碼:
[cpp] inline fmtflags 
flags(fmtflags __fmtfl) 

  fmtflags __old = _M_flags; 
  _M_flags = __fmtfl; 
  return __old; 

    inline fmtflags
    flags(fmtflags __fmtfl)
    {
      fmtflags __old = _M_flags;
      _M_flags = __fmtfl;
      return __old;
    }其中_M_flags是ios_base的一個成員,保存流的一些配置信息,比如是否采用16進制輸出,是否左對齊等等。可以看出,setf僅僅是將一種配置信息加入原有的配置信息中,而flags是完全替換了原有的配置信息。同時,也提供了unsetf和setf相搭配使用。如下是流的配置信息:[cpp] enum _Ios_Fmtflags  
  {  
    _S_boolalpha    = 1L << 0, 
    _S_dec      = 1L << 1, 
    _S_fixed        = 1L << 2, 
    _S_hex      = 1L << 3, 
    _S_internal     = 1L << 4, 
    _S_left         = 1L << 5, 
    _S_oct      = 1L << 6, 
    _S_right        = 1L << 7, 
    _S_scientific   = 1L << 8, 
    _S_showbase     = 1L << 9, 
    _S_showpoint    = 1L << 10, 
    _S_showpos  = 1L << 11, 
    _S_skipws   = 1L << 12, 
    _S_unitbuf  = 1L << 13, 
    _S_uppercase    = 1L << 14, 
    _S_adjustfield  = _S_left | _S_right | _S_internal, 
    _S_basefield    = _S_dec | _S_oct | _S_hex, 
    _S_floatfield   = _S_scientific | _S_fixed, 
    _S_ios_fmtflags_end = 1L << 16  
  }; 
  enum _Ios_Fmtflags
    {
      _S_boolalpha  = 1L << 0,
      _S_dec   = 1L << 1,
      _S_fixed   = 1L << 2,
      _S_hex   = 1L << 3,
      _S_internal  = 1L << 4,
      _S_left   = 1L << 5,
      _S_oct   = 1L << 6,
      _S_right   = 1L << 7,
      _S_scientific  = 1L << 8,
      _S_showbase  = 1L << 9,
      _S_showpoint  = 1L << 10,
      _S_showpos  = 1L << 11,
      _S_skipws  = 1L << 12,
      _S_unitbuf  = 1L << 13,
      _S_uppercase  = 1L << 14,
      _S_adjustfield  = _S_left | _S_right | _S_internal,
      _S_basefield  = _S_dec | _S_oct | _S_hex,
      _S_floatfield  = _S_scientific | _S_fixed,
      _S_ios_fmtflags_end = 1L << 16
    };對於hex操作,舉個例子:[cpp] #include <iostream>  
 
using namespace std; 
 
int main (int argc, const char * argv[]) 

    cout.setf(ios::hex); 
    cout << 15 << endl; 
 
    return 0; 

#include <iostream>

using namespace std;

int main (int argc, const char * argv[])
{
    cout.setf(ios::hex);
    cout << 15 << endl;

    return 0;
}程序輸出:
[cpp] 15 
15
setf並沒有起到讓輸出流按照十六進制輸出的作用,而如果使用cout << hex << 15 << endl; 就能夠達到效果,為什麼呢?
先看看cout << hex實際調用的hex函數的定義:

[cpp] inline ios_base& 
hex(ios_base& __base) 

  __base.setf(ios_base::hex, ios_base::basefield); 
  return __base; 

  inline ios_base&
  hex(ios_base& __base)
  {
    __base.setf(ios_base::hex, ios_base::basefield);
    return __base;
  }可以看出,它和cout.setf(ios::hex);是不一致的。

看看,hex函數內部調用的setf函數的定義:

[cpp]     inline fmtflags 
    setf(fmtflags __fmtfl, fmtflags __mask) 
    { 
      fmtflags __old = _M_flags; 
      _M_flags &= ~__mask; 
      _M_flags |= (__fmtfl & __mask); 
      return __old; 
    } 
 
_S_basefield    = _S_dec | _S_oct | _S_hex, 
    inline fmtflags
    setf(fmtflags __fmtfl, fmtflags __mask)
    {
      fmtflags __old = _M_flags;
      _M_flags &= ~__mask;
      _M_flags |= (__fmtfl & __mask);
      return __old;
    }

_S_basefield  = _S_dec | _S_oct | _S_hex,
static const fmtflags basefield = _S_basefield;可以看出, _M_flags &= ~__mask; 是將流狀態中的_S_dec,  _S_oct, _S_hex標志全部清除,然後加上特定的標志。而cout.setf(ios::hex);的方式僅僅是將十六進制標志加入流狀態中,但是流狀態中的十進制輸出狀態依然保留,導致依然按照十進制輸出了。

了解了代碼內部原理,外部不管發生了多麼神奇的現象,都顯得很單純。


Q: 對於輸入和輸出流,clear和sync函數的區別是什麼?
A: clear只表示流狀態標志的清理。


[cpp] /**
 *  @brief  [Re]sets the error state.
 *  @param  state  The new state flag(s) to set.
 *
 *  See std::ios_base::iostate for the possible bit values.  Most
 *  users will not need to pass an argument.
*/ 
void 
clear(iostate __state = goodbit); 
      /**
       *  @brief  [Re]sets the error state.
       *  @param  state  The new state flag(s) to set.
       *
       *  See std::ios_base::iostate for the possible bit values.  Most
       *  users will not need to pass an argument.
      */
      void
      clear(iostate __state = goodbit);
而對於sync函數或者flush函數才真正將緩沖區數據進行刷新。


xichen
2012-5-31 11:47:56

[cpp]  

 

 

摘自 陳曦的分享

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved