c++中ostream類的超詳細說明

2021-04-19 12:03:01

根據前文,ostream類是c++標準輸出流的一個基礎類別,本篇詳細介紹ostream類的主要成員函數用法。

1.ostream的建構函式

從ostream標頭檔案中擷取一部分關於建構函式的宣告和定義,如下:

public:
//explicit用來防止由建構函式定義的隱式轉換
explicit
      basic_ostream(__streambuf_type* __sb)
      { this->init(__sb); }

protected:
      basic_ostream()
      { this->init(0); }

#if __cplusplus >= 201103L
      // Non-standard constructor that does not call init()
      basic_ostream(basic_iostream<_CharT, _Traits>&) { }

      basic_ostream(const basic_ostream&) = delete;

      basic_ostream(basic_ostream&& __rhs)
      : __ios_type()
      { __ios_type::move(__rhs); }

      // 27.7.3.3 Assign/swap

      basic_ostream& operator=(const basic_ostream&) = delete;

      basic_ostream&
      operator=(basic_ostream&& __rhs)
      {
    swap(__rhs);
    return *this;
      }

可以看到ostream類的預設建構函式是保護型別,而帶引數的建構函式則是公有的,根據public和protected的功能,我們要定義一個ostream物件,必須要在引數中傳入streambuf型別的指標才可以,否則會報編譯錯誤。

一個可用的例子如下:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    filebuf buf;
    if ( buf.open("/proc/self/fd/1", ios::out) == nullptr )
    {
        cerr << "stdout open failed" << endl;
        return -1;
    }
    ostream out(&buf);
    return 0;
}

與istream一樣,因為streambuf型別的建構函式是保護型別,不能直接使用,所以需要使用它的繼承者stringbuf或者filebuf,這裡使用了filebuf,並且我們輸出錯誤資訊沒有使用cout,這裡使用了ostream定義的另外一個範例cerr,會輸出錯誤資訊到標準錯誤輸出。

ostream類與istream類一樣,它的的拷貝建構函式和賦值函數也都是保護型別的,所以ostream是不允許拷貝或者賦值的,所以它也不能直接作為返回型別和引數傳遞,很多時候需要使用參照來進行傳遞。

2.左移位<<操作符

部分<<操作符函數原型如下:

    //過載一系列<<操作符,可以用於讀取變數資料並放入到流緩衝區中
      __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<<(unsigned short __n)
      {
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 117. basic_ostream uses nonexistent num_put member functions.
    return _M_insert(static_cast<unsigned long>(__n));
      }

      __ostream_type&
      operator<<(int __n);

      __ostream_type&
      operator<<(unsigned int __n)
      {
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 117. basic_ostream uses nonexistent num_put member functions.
    return _M_insert(static_cast<unsigned long>(__n));
      }

\<<操作符可用於將資料寫入到輸出流中,使用例子如下:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    filebuf buf;
    if ( buf.open("/proc/self/fd/1", ios::out) == nullptr )
    {
        cerr << "stdout open failed" << endl;
        return -1;
    }
    ostream out(&buf);
    int i = 1234;
    long long ll = 1234567;
    out << i << endl << ll << endl;
    return 0;
}

這時我們猜測,命令列標準輸出中應該會輸出兩行數位,編譯後執行結果如下:

1234
1234567

從這裡out變數用法來看,實際上就是實現了標準庫中cout的功能,當然,我是猜測可能是這樣實現的。

/proc/self/fd/1是linux系統中標準輸出檔案,所以開啟這個檔案操作的話,反映在程式執行的過程中,就是直接輸出到標準輸出。
3.put函數

ostream標頭檔案中put函數原型如下:

      //往緩衝區中插入一個字元
      __ostream_type&
      put(char_type __c);

put函數使用例子如下:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    filebuf buf;
    if ( buf.open("/proc/self/fd/1", ios::out) == nullptr )
    {
        cerr << "stdout open failed" << endl;
        return -1;
    }
    ostream out(&buf);
    char c = 'X';
    out.put('c').put('=').put(c).put('\n');
    return 0;
}

這裡因為put函數返回的是ostream&型別,所以可以連著使用put函數,程式碼編譯後執行結果如下:

[root@mylinux ~]# ./a.out 
c=X
[root@mylinux ~]#
4.write函數

ostream的write函數原型如下:

      //將__s指標所指向的字串複製出來並插入到緩衝區中,最多插入__n個字元
      __ostream_type&
      write(const char_type* __s, streamsize __n);

用法如下:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    filebuf buf;
    if ( buf.open("/proc/self/fd/1", ios::out) == nullptr )
    {
        cerr << "stdout open failed" << endl;
        return -1;
    }
    ostream out(&buf);
    if ( !out.good())
    {
        cerr << "stream buf state is bad" << endl;
        return -1;
    }
    out.write("aaa\n", 4);    
    return 0;
}

good函數是ostream繼承於父類別ios的一個成員函數,它用來檢查流的狀態是否正常,正常則返回true。

程式碼編譯執行後結果如下:

[root@mylinux ~]# ./a.out 
aaa
[root@mylinux ~]#
5.flush函數

函數原型如下:

//將資料從緩衝區同步到儲存媒介中
__ostream_type&
      flush();

使用方法如下:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ofstream out("aaa.txt");
    if ( !out.good())
    {
        cerr << "stream buf state is bad" << endl;
        return -1;
    }
    for (int n=0; n<10; ++n)
      {
        out << n;
        //out.flush();
      }    
    while(1);
    out.close();
    return 0;
}

這裡使用了ofstream型別,它是ostream的一個子類,所以對於flush用法是一樣的,這裡我們先把flush函數呼叫註釋掉,此時去執行程式碼,然後檢視aaa.txt檔案,會發現資料並沒有寫入到檔案中去,然後我們把註釋取消,重新編譯執行後,檢視aaa.txt內容,會看到0123456789已經被寫入到檔案中去。

按照我的理解,ofstream在往檔案中寫入資料時,資料實際上是先寫到緩衝區中,並沒有寫到檔案中去,所以需要呼叫一個flush,來確保資料會從緩衝區寫到輸出裝置,也就是檔案中去。

這裡有一個小插曲,我一開始使用了out << n << endl去往檔案寫資料,發現flush是不起作用的,一直很疑惑,後來看原始碼才發現endl這個操縱運算元,它會呼叫先往緩衝區寫入一個換行符然後再呼叫flush函數,所以flush才會不起作用。
6.tellp函數

tellp函數原型如下:

//返回當前寫緩衝區位置
     pos_type
      tellp();

使用例子如下:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ofstream out("aaa.txt");
    if ( !out.good())
    {
        cerr << "stream buf state is bad" << endl;
        return -1;
    }
    for (int n=0; n<10; ++n)
      {
        out << n << endl;
        out.flush();
      }    
    clog << "current pos is " << out.tellp() <<endl;
    out.close();
    return 0;
}

從程式碼分析,目前應該是寫到了20的位置,編譯執行,結果如下:

[root@mylinux~]# ./a.out 
current pos is 20
[root@mylinux~]# 
7.seekp函數

seekp函數原型如下:

      /**
     從當前位置開始,跳轉pos個寫位置
      */
      __ostream_type&
      seekp(pos_type pos);

      /**
      根據ios_base::seekdir定義的位置,跳轉off個寫位置
      */
       __ostream_type&
      seekp(off_type off, ios_base::seekdir);

範例如下:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ofstream out("aaa.txt");
    if ( !out.good())
    {
        cerr << "stream buf state is bad" << endl;
        return -1;
    }
    out << 1234567;
    clog << "first pos is " << out.tellp() <<endl;
    out.seekp(3);
    clog << "second pos is " << out.tellp() <<endl;
    out.seekp(1, ios::beg);
    clog << "third pos is " << out.tellp() <<endl;
    out.close();
    return 0;
}

輸出結果如下:

[root@mylinux ~]# ./a.out 
first pos is 7
second pos is 3
third pos is 1
[root@mylinux ~]#

到這裡,ostream類的public成員函數就介紹完畢啦,若有不對之處,歡迎指正。