1. 程式人生 > >C++標準I/O庫:iostream, fstream, sstringstream

C++標準I/O庫:iostream, fstream, sstringstream

sso www c const ams 生效 系列 linux 引用 binary

在寫代碼的過程中。我們最常做的事就是io操作,不管是對控制臺,還是文件。但一段時間不寫代碼就忘了,這裏理一下C++標準I/O庫的詳細類和操作。

C++的標準I/O庫包含我們常常使用的iostream,fstream。以及不太常常使用的stringstream。前兩者是對控制臺和文件的I/O操作,stringstream則能夠使用I/O操作對內存中的數據進行格式化操作。

C++的標準I/O操作相對與C來說,更加的簡明,安全,但運行效率會有所下降。


標準I/O庫類繼承體系

對於編程語言來說,在概念上,從控制終端、磁盤文件以及內存中讀取數據都應該不影響I/O操作。C++為了解決支持不同設備的字符流。通過面向對象的思想(廢話了,你要不用這個思想,你還是什麽C++)。通過繼承來實現一組類,分別處理控制終端、磁盤文件,以及內存數據的I/O操作,下圖是引用cplusplus官網關於輸入輸出流類繼承體系的關系圖,自己畫了一下,例如以下:

技術分享

由上圖能夠知道,I/O操作的基類是ios_base,各個類的用途例如以下:

  • <iostream>
  • istream 從流中讀取數據
  • ostream 向流中寫數據
  • iostream 對流進行讀寫操作。派生於istream和ostream
  • <fstream>
  • ifstream 從文件裏讀取數據。派生於istream
  • ofstream 向文件裏寫數據,派生於ostream
  • fstream 讀寫文件, 派生於iostream
  • <sstream>
  • istringstream 讀取string對象。派生於istream
  • ostringstream 寫string對象。派生於ostream
  • stringstream 讀寫string對象,派生於iostream

C++標準對於I/O體系,定義了主要的流格式標誌(hex, dec,等),文件打開模式(in, out, bin等),流的狀態標誌(failbit等)。以及相關的函數等,例如以下在linux 下/usr/include/c++/4.6/bits/ios_base.h中關於這些標誌的枚舉定義:

  • 流格式標誌

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_Openmode 
    { 
      _S_app            = 1L << 0,
      _S_ate            = 1L << 1,
      _S_bin            = 1L << 2,
      _S_in             = 1L << 3,
      _S_out            = 1L << 4,
      _S_trunc          = 1L << 5,
      _S_ios_openmode_end = 1L << 16 
    };

  • 流的狀態標誌

 enum _Ios_Iostate
    { 
      _S_goodbit                = 0,
      _S_badbit                 = 1L << 0,
      _S_eofbit                 = 1L << 1,
      _S_failbit                = 1L << 2,
      _S_ios_iostate_end = 1L << 16 
    };

I/O流的狀態

為了更好地管理I/O操作。標準定義了一系列條件狀態標誌和函數,用來管理流對象。比方說是否可用,以及出現的錯誤。

對於流的狀態標誌是在ios_base類中進行定義的。所以流的狀態標誌管理適用於終端、文件、string流對象。流的狀態定義為:ios_base::iostate,詳細有哪些值在ios_base中有定義,例如以下:

    typedef _Ios_Iostate iostate;
    static const iostate badbit =	_S_badbit;    //流被破壞 (流本身的問題)
    static const iostate eofbit =	_S_eofbit;    //流已到結尾
    static const iostate failbit =	_S_failbit;   //I/O失敗(操作本身上的邏輯問題)
    static const iostate goodbit =	_S_goodbit;   //好流...正常
為了管理上述的狀態標誌,標準在ios類中提供了下面函數來進行處理:

iostate  rdstate() const                     //返回流的條件狀態
void clear(iostate __state = goodbit);       //設置流的條件狀態,默認設置為正常
void setstate(iostate __state);              //設置流的狀態,和clear的差別:不會清除其它標誌
bool good() const           //流的是否正常
bool eof() const            //流是否到結尾
bool fail() const           //流是否I/O失敗
bool bad() const            //流是否被破壞

能夠通過以下代碼簡單進行狀態標誌的測試:

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

int main()
{
    int a;

    cin>>a;
    cout<<"goodbit: "<<cin.good()<<" eofbit: "<<cin.eof()<<" failbit: "<<cin.fail()<<" badbit: "<<cin.bad()<<endl;
    
    cin>>a;
    cout<<"goodbit: "<<cin.good()<<" eofbit: "<<cin.eof()<<" failbit: "<<cin.fail()<<" badbit: "<<cin.bad()<<endl;
}
輸入1 a後顯演示樣例如以下:

1
goodbit: 1 eofbit: 0 failbit: 0 badbit: 0
a
goodbit: 0 eofbit: 0 failbit: 1 badbit: 0

由上可知非法輸入會導致I/O失敗,failbit流標誌生效。


文件I/O操作

C++提供了ifstream和ofstream類負責文件I/O的讀和寫操作。類fstream負責文件的讀寫。繼承關系文章開頭已說明。

前面說了,在ios_base基類中定義了文件打開模式,

typedef _Ios_Openmode openmode;

/// Seek to end before each write.
static const openmode app =         _S_app;

/// Open and seek to end immediately after opening.
static const openmode ate =         _S_ate;

/// Perform input and output in binary mode (as opposed to text mode).
static const openmode binary =      _S_bin;

/// Open for input.  Default for @c ifstream and fstream.
static const openmode in =          _S_in;

/// Open for output.  Default for @c ofstream and fstream.
static const openmode out =         _S_out;

/// Open for input.  Default for @c ofstream.
static const openmode trunc =       _S_trunc;

詳細含義例如以下:

  • ios_base::in 僅僅讀打開
  • ios_base::out 僅僅寫打開
  • ios_base::app 每次寫之前移到文件尾
  • ios_base::ate 打開文件後立馬定位到文件尾
  • ios_base::trunc 打開文件並清空文件流
  • ios_base::binary 以二進制模式進行讀寫操作

以ifstream方式打開文件,默認打開方式為in。以ofstream方式打開,默認打開方式為out | trunc。fstream是默認以in | out方式打開。假設要以ofstream打開文件。且同一時候保存文件的數據。僅僅能通過顯示的指定app打開模式。

例如以下是fstream頭文件裏的open函數原型:

ifstream
void open(const char* __s, ios_base::openmode __mode = ios_base::in)

ofstream
void open(const char* __s, ios_base::openmode __mode = ios_base::out | ios_base::trunc)

fstream
void open(const char* __s, ios_base::openmode __mode = ios_base::in | ios_base::out)

假設fstream打開方式是out,也會清空文件裏數據。

當文件以out方式打開(不包括in模式)時,假設文件不存在,會創建一個新文件。


  • 輸入輸出操作符

istream,ostream類中都定義了用於輸入輸出的算法提取器(Arithmetic Extractor):‘>>‘和‘<<’操作符,能夠提取內置數據類型的數據,比如,輸入操作符>>用於從流緩沖區streambuf中提取數據。並輸出到特定類型的變量中,比如istream頭文件裏詳細定義例如以下:

//類內定義的,用於提取
__istream_type& operator>>(bool& __n);
__istream_type& operator>>(short& __n);
__istream_type& operator>>(unsigned short& __n);
__istream_type& operator>>(int& __n);
__istream_type& operator>>(unsigned int& __n); 
__istream_type& operator>>(long& __n);     
__istream_type& operator>>(unsigned long& __n);
__istream_type& operator>>(long long& __n);
__istream_type& operator>>(unsigned long long& __n);
__istream_type& operator>>(float& __f)
__istream_type& operator>>(double& __f)
__istream_type& operator>>(long double& __f)
__istream_type& operator>>(void*& __p)


//類外定義的,用於提取字符
template<class _Traits> 
inline basic_istream<char, _Traits>&
operator>>(basic_istream<char, _Traits>& __in, unsigned char& __c);

template<class _Traits>
inline basic_istream<char, _Traits>&
operator>>(basic_istream<char, _Traits>& __in, signed char& __c);

template<class _Traits>
inline basic_istream<char, _Traits>&
operator>>(basic_istream<char, _Traits>& __in, unsigned char* __s);

template<class _Traits>
inline basic_istream<char, _Traits>&
operator>>(basic_istream<char, _Traits>& __in, signed char* __s);

//string類中對輸入輸出operator的重載,使其能支持string類型的輸入輸出
template<typename _CharT, typename _Traits, typename _Alloc>
basic_istream<_CharT, _Traits>&
operator>>(basic_istream<_CharT, _Traits>& __is,
       basic_string<_CharT, _Traits, _Alloc>& __str);


template<typename _CharT, typename _Traits, typename _Alloc>
inline basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os,
       const basic_string<_CharT, _Traits, _Alloc>& __str)

因為ifstream和ofstream派生於istream和ostream,所以fstream相同支持對文件流filebuf的輸入輸出操作。比例如以下例通過輸入輸出操作符讀取文件裏的數據:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main ()
{
    string fileName = "test.txt";
    ofstream outOpt(fileName.c_str(),  ios_base::out | ios_base::trunc);
    if(!outOpt)
    {
	cout<<fileName<<" open failed..."<<endl;
	return -1;
    }

    outOpt<<"1 2 3 test text";
    outOpt.close();

    int a[3];
    string b[2];

    ifstream inOpt(fileName.c_str());
    inOpt>>a[0]>>a[1]>>a[2];    //以空白符為分隔,從文件緩沖區中讀取三個整數
    inOpt>>b[0]>>b[1];    //以空白符為分隔,從文件緩沖區讀取兩個string數據(string類型對operator <<進行類重載,所以能夠支持該操作)

    cout<<a[0]<<‘ ‘<<a[1]<<‘ ‘<<a[2]<<endl;   
    cout<<b[0]<<‘ ‘<<b[1]<<endl;    
    inOpt.close();

    return 0;
}

  • getline操作

在文件I/O的過程中,我們常常要處理的是按行對數據進行分析處理。istream類內定義了getline成員函數支持以特定的size或delimiter(默覺得換行符)來提取定長或以特定分隔符分開的數據。相同,string頭文件裏也提供了getline全局函數,支持從istream類中。以特定的size或delimiter(默覺得換行符)來提取數據。定義例如以下:

//istream類內定義,提供從輸入流中讀取一行數據到char*中
__istream_type&
getline(char_type* __s, streamsize __n, char_type __delim);	//delim默覺得換行

//string頭文件定義(實際在basic_string.h中), 提供從輸入流中讀取一行數據到string中
template<typename _CharT, typename _Traits, typename _Alloc>
basic_istream<_CharT, _Traits>&
getline(basic_istream<_CharT, _Traits>& __is,
    basic_string<_CharT, _Traits, _Alloc>& __str, _CharT __delim);	//delim默覺得換行

使用例如以下:

#include <iostream>
#include <fstream>

#include <string>

using namespace std;

int main()
{
    ifstream inFile("test.txt");
    if(!inFile)
    {
        cout<<"open test.txt failed..."<<endl;
        return -1;
    }
    
    char str1[256] = "";
    inFile.getline(str1, 255, ‘\n‘);

    string str2;
    getline(inFile, str2);
    
    cout<<str1<<endl;
    cout<<str2<<endl;
}

  • 獲取文件的所有內容的seekg和tellg

為了對文件高效的操作時常要用到文件流的定位功能。在ios_base類中。定義了例如以下的成員,

typedef _Ios_Seekdir seekdir;
static const seekdir beg =          _S_beg;    //文件流開頭
static const seekdir cur =          _S_cur;    //當前所在文件流的位置
static const seekdir end =          _S_end;   //文件流尾部

在istream中定義了tellg和seekg。ostream中定義了tellp和seekp,分別用來獲取當前操作所在文件流的位置和進行文件流的操作的定位。例如以下是通過seek操作來讀取整個文件的內容到內存中:

#include <iostream>
#include <fstream>

int main ()
{
    std::ifstream is ("test.txt", std::ios_base::binary);
    if (is)
    {
        //獲取文件的大小
        is.seekg (0,  std::ios_base::end);   //定位到文件結尾
        int length = is.tellg();    //獲取當前所在文件流的位置,因為已經定位文件結尾,所以是獲取文件流大小
        is.seekg (0, is.beg);       //又一次將pos定位到文件流開始


        char * buffer = new char [length];
        is.read (buffer,length);
        is.close();

        // 輸出到屏幕
        std::cout.write (buffer,length);

        delete[]  buffer;
    }

    return 0;
}

內存數據的 I/O操作

C++ I/O標準庫支持內存數據的I/O的操作,在sstream頭文件裏定義了 istringstream,ostringstream。stringstream,僅僅須要將流與內存string對象綁定,就能夠進行I/O操作。上面三個I/O類分別用來讀取內存string對象到指定對象中,將指定對象中的數據寫寫入string對象。stringstream可用來讀寫string對象

在上面I/O文件操作一節已經說了在istream和ostream中定義的標準輸入輸出操作,由派生關系可知,對於istringstream,ostringstream相同能夠進行標準輸入輸出操作,對stringstream的stringbuf進行操作(與iostream的streambuf,fstream的filebuf類似,這裏暫不介紹)。對於stringstream類經常使用的操作例如以下:

stringstream ss(mystr)//創建ss對象,stream流中存放string類型的mystr的數據;
ss.str() //返回ss流中的數據,返回類型為string類型
ss.str(mystr) //設置ss流中數據為mystr的內容

詳細使用例如以下:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    istringstream istream;
    istream.str("1 2 3 hello");
    
    int a, b, c;
    string str1;
    istream>>a>>b>>c>>str1; //從流中讀取數據到對象中(以空白符為切割符)
    cout<<a<<‘ ‘<<b<<‘ ‘<<c<<‘ ‘<<str1<<endl;
    
    ostringstream ostream;
    ostream<<a<<‘ ‘<<b<<‘ ‘<<c<<‘ ‘<<str1; //向流中寫數據
    
    string out = ostream.str();
    cout<<out<<endl;
}


C++標準I/O庫:iostream, fstream, sstringstream