C++流概述


C++流概述
在程序設計中,數據輸入/輸出(I/O)操作是必不可少的,C++語言的數據輸入/輸出操作是通過I/O流庫來實現的。C++中把數據之間的傳輸操作稱為流,流既可以表示數據從內存傳送到某個載體或設備中,即輸出流,也可以表示數據從某個載體或設備傳送到內存緩沖區變量中,即輸入流。


C++流涉及以下概念:

標准I/O流:內存與標准輸入輸出設備之間信息的傳遞;
文件I/O流:內存與外部文件之間信息的傳遞;
字符串I/O流:內存變量與表示字符串流的字符數組之間信息的傳遞。
STL中定義的流類:

流類分類 流類名稱 流 類 作 用
流基類 ios 所有流類的父類,保存流的狀態並處理錯誤
輸入流類 istream 輸入流基類,將流緩沖區中的數據作格式化和非格式化之間的轉換並輸入
  ifstream 文件輸入流類
  istream_withassign cin輸入流類,即操作符>>輸入流
  istrstream 串輸入流類, 基於C類型字符串char*編寫
  istringstream 串輸入流類, 基於std::string編寫
輸出流類 ostream 輸出流基類,將流緩沖區中的數據作格式化和非格式化之間的轉換。並輸出
  ofstream 文件輸出流類
  ostream_withassign Cout、cerr、clog的輸出流類,即操作符<<輸出流
  ostrstream 串輸入流類, 基於C類型字符串char*編寫
  ostringstream 串輸入流類, 基於std::string編寫
輸入/輸出流類 iostream 多目的輸入/輸出流類的基類
  fstream 文件流輸入/輸出類
  strstream 串流輸入/輸出類, 基於C類型字符串char*編寫
  stringstream 串流輸入/輸出類, 基於std::string編寫
注,對於串流,提供了兩套類,一個基於C類型字符串char *編寫(定義於頭文件strstream),一個基於std::string編寫(定義於sstream), 后者是C++標准委員會推薦使用的。另外看一些資料,都說還定義了文件流和串流的基類fstreambase和strstreambase,而且描述ofstream繼承於ostream和fstreambase,但沒有找到,於是省略了。可搜索關鍵字(fstreambase或strstreambase)查看詳情。

預定義標准流對象:

istream &cin; //鍵盤 
ostream &cout; //屏幕 
ostream &cerr; //屏幕 
ostream &clog; //打印機 
wistream &wcin; 
wostream &wcout; 
wostream &wcerr; 
wostream &wclog;

C++中讀取string對象
1.標准輸入讀取:cin >> string

a.忽略開頭所有的空白字符(空格、換行、制表符等);

b.讀取字符直至再次遇到空白字符,讀取終止;

2.讀取整行文本:getline(istream, string)

a.不忽略開頭的空白字符;

b.讀取字符直至遇到換行符,如果第一個字符是換行符,則返回空string;

c.返回時丟棄換行符,換行符不存儲在string中。

STL C++中string、ifstream、stringstream的使用
1、從標准輸入接受字符串,然后進行相關處理

#include<iostream> 
#include<sstream> 
#include<string> 
using namespace std; 
int main() 

string s; //定義一個stirng對象,從標准輸入接受一個字符串 
cout<<"請輸入一行字符串:"<<endl; 
getline(cin,s); 
stringstream ss(s); //定義一個string流(使用s實例化) 
cout<<"處理后的字符串為:"<<endl; //將string流里的東西輸出 
for(string s1;ss>>s1;cout<<s1<<endl); 
return 0; 
}

運行結果如下:

請輸入一行字符串: 
you are a good boy 
處理后的字符串為: 
you 
are 

good 
boy

根據前文所說“忽略開頭空白字符,讀取字符直至再次遇到空白字符為止”,這樣的結果不難理解。

2、從文件讀入字符串,然后進行相關處理

#include<iostream> 
#include<sstream> 
#include<string> 
#include<fstream> 
using namespace std; 
int main() 

ifstream fin("test.in"); //定義一個文件輸入流,從文件中讀取數據 
if(!fin) 

cout<<"can not open the input file"; 
return -1; 

string s; //定義一個string,獲取文件輸入流中的一行 
while(getline(fin,s)) 

stringstream ss(s); //定義一個string流(使用一個string對象填充) 
int a,b; 
ss>>a;ss>>b; //將string流中的兩個值分別讀入a、b中 
cout<<"該行數據和為:"<<a+b<<endl; 

return 0; 
}

test.in文件內容如下:

1 10 
2 100 
3 8 
4 9

運行結果為:

該行數據和為:11 
該行數據和為:102 
該行數據和為:11 
該行數據和為:13

其中,getline函數原型如下:

template<class E, class T, class A> 
basic_istream<E, T>& getline( 
basic_istream <E, T>& is, 
basic_string<E, T, A>& str); 
template<class E, class T, class A> 
basic_istream<E, T>& getline( 
basic_istream <E, T>& is, 
basic_string<E, T, A>& str, 
E delim);


第二個重載函數很有意思,結尾是char, 或wchar型的一個分隔符,如果設為’\n’,則為以換行符為分隔,如果設為’,',則為以逗號為分隔。由此,雖然C++中的字符串沒有分割函數,如果是從文件中讀取出被特定分隔符分隔的文本,那么就可以用此方法,如:

std::ifstream file; 
file.open("tt.txt"); 
std::string s,t; 
while(std::getline(file,s)) //按行讀取 

std::stringstream strs(s); //把行裝到另一個流中 
while(std::getline(strs,t,’,')) //把流按分隔符實現分割 
std::cout<<t<<std::endl; 

file.close();

上面的程序相當於將整個文本先按行分割,再按分隔符分割,也可以變換一下,只按分隔符分割,然后過濾掉按行符,換行符與某元素連在了一起,並處於開頭。

C++的流操作復制文件
寫.wrl, .obj文件格式轉換時用到,記錄一下相關方法

使用C++標准程序庫的輸入輸出流(I/O Stream)復制文件,存在許多的方法,

方法一:逐個字符復制

#include < fstream > 
std::ifstream input("in",iOS::binary); 
std::ofstream output("out",ios::binary); 
char ch; 
while (input.get(ch)) output << ch;

注意:如果使用input>>ch讀取字符,則必須先調用input.unsetf(ios::skipws)取消輸入流默認的跳過空白符(空格、換行、制表符等)的輸入格式,因為換行符是空白符的一種。另外,對於ofstream對象,默認的操作方式是ios_base::out | ios_base::trunc,即輸出和文件清空!

方法二:逐行復制

#include < fstream > 
#include < string > 
std::ifstream input("in",ios::binary); 
std::ofstream output("out",ios::binary); 
std::string line; 
while (getline(input,line)) output << line << "\n";

注意:這里的代碼有一個小小的缺陷,如果文件不是純文本格式的文件,或者文本文件的最后沒有換行符,那么會導致復制后的文件末尾添加了一個多余的換行符。

方法三:迭代器復制

#include < fstream > 
#include < iterator > 
#include < algorithm > 
std::ifstream input("in",ios::binary); 
std::ofstream output("out",ios::binary); 
input.unsetf(ios::skipws); 
copy(istream_iterator(input),istream_iterator(),ostream_iterator(output,""));

同樣這里也有一個小技巧,輸入流的格式默認為跳過空白字符,因此調用unsetf取消這個格式,才可保證正確的復制。見后方中字符串緩沖與文件緩沖類的定義,文件緩沖類沒有定義自己的迭代器,所以就用了

方法四:緩沖區復制

#include < fstream > 
std::ifstream input("in",ios::binary); 
std::ofstream output("out",ios::binary); 
output << input.rdbuf();

這里直接使用了輸入流的緩沖區,因此沒有引入額外的臨時對象。

很顯然,上述四種方法中,最后一種方法最簡潔,由於直接操作輸入流的緩沖區,從運行效率上來說,也比其他方法有着略微的優勢(當然,由於操作系統可能提供了 額外的基於設備的文件緩沖機制,也許你無法證實這一點)。因此,除非要對輸入內容進行處理,直接復制文件推薦最后一種方法,既不容易出錯,又能獲得良好的性能。

另外,對文件進行更改、刪除、插入等操作,可以直接用以上方法也可以先把文件讀入vector<string>,處理后再輸出,不過當文件很大的時候,vector占用內存空間較大,而且輸出時會破壞原文件格式,盡量不使用。

以上是幾種同種流(文件流)之間的數據復制的方式,字符串流與文件流之間也可以以此方式進行復制。另外,再看一下get函數:

int_type get(); 
basic_istream& get(E& c); 
basic_istream& get(E *s, streamsize n); 
basic_istream& get(E *s, streamsize n, E delim); 
basic_istream& get(basic_streambuf<E, T> *sb); 
basic_istream& get(basic_streambuf<E, T> *sb, E delim);

delim表示結束符,前文中討論的流分割函數還記得吧?又多了種方法,估計get與全局函數getline(不是那個成員函數,成員函數要求給出streamsize,而全局的就不用)實現得差不多。默認的也是’\n’,按行分隔。

C++流緩沖區的應用——輸出文件內容的方法舉例
簡單討論C++流對象的底層緩沖區,並舉例介紹如何使用該緩沖區進行文件內容的輸出

C++標准庫封裝了一個緩沖區類streambuf,以供輸入輸出流對象使用。每個標准C++輸出輸出流對象都包含一個指向streambuf的指針,用戶可以通過調用rdbuf()成員函數獲得該指針,從而直接訪問底層streambuf對象。因此,可以直接對底層緩沖區進行數據讀寫,從而跳過上層的格式化輸入輸出操作。

template <class Elem, class Traits> class basic_ios 
: public ios_base { 
basic_streambuf<_Elem, _Traits>*_Mystrbuf; 
//C++標准庫封裝了一個緩沖區類streambuf。 
_Mysb * rdbuf() const 
{ // return stream buffer pointer 
return (_Mystrbuf); 

//使調用者與參數(流緩沖指針)關聯, 
//返回自己當前關聯的流緩沖區指針, 可用來重定向等 
_Mysb * rdbuf(_Mysb *_Strbuf) 
{ // set stream buffer pointer 
_Mysb *_Oldstrbuf = _Mystrbuf; 
_Mystrbuf = _Strbuf; 
return (_Oldstrbuf); 

};

對象通過調用rdbuf()獲得了底層streambuf對象的指針,也就可以通過該指針調用streambuf支持你各種操作進行輸入輸出。在這里主要介紹如何利用該指針實現文件內容的輸出。

對於文件流類和字符串流類,分別派生了相應的流緩沖區類型:

template<class _Elem, 
class _Traits> class basic_streambuf; typedef basic_streambuf<char, char_traits<char> > streambuf; typedef basic_streambuf<wchar_t, char_traits<wchar_t> > wstreambuf; 
template <class Elem, class Tr = char_traits<Elem>, class Alloc = allocator<Elem> > class basic_stringbuf : 
public basic_streambuf<Elem, Tr> 
typedef basic_stringbuf<char, char_traits<char>, allocator<char> > stringbuf; typedef basic_stringbuf<wchar_t, char_traits<wchar_t>, allocator<wchar_t> > wstringbuf; 
template <class Elem, class Tr = char_traits<Elem> > class basic_filebuf : public basic_streambuf<Elem, Tr> 
typedef basic_filebuf<char, char_traits<char> > filebuf; 
typedef basic_filebuf<wchar_t, char_traits<wchar_t> > wfilebuf;

輸出流提供了一個重載版本operator<<,它以streambuf指針為參數,實現把streambuf對象中的所有字符輸出到輸出流出中;輸入流也提供了一個對應的operator>>重載版本,把輸入流對象中的所有字符輸入到streambuf對象中。輸入流的get成員重載版本中還有以streambuf指針為參數的版本,也可以用來把輸入流的東西寫入到輸出流緩沖區中。

下面用三種方法實現把一個文件的內容輸出標准輸出(當然還可以通過普通的標准格式化輸入輸出完成):

方法一:通過operator<<

#include <iostream> 
#include <fstream> 
using namespace std; 
int main() 

ifstream fin("source.dat"); 
cout<<fin.rdbuf(); 
return 0; 
}

方法二:利用get成員函數

ifstream fin("source.dat"); 
while (!fin.get(*cout.rdbuf()).eof()) { // extract a line input 
if (fin.fail()) // blank line 
fin.clear(); 
cout<<char(fin.get()); // extract '\n' 
}

代碼解釋:由於上面代碼中的get版本在遇到’\n’字符時,結束提取,所以需要用循環實現整個文件內容的輸出。另外,當此版本get函數遇到空行時,因為沒有提取到任何字符(注意:get不提取回車符),注意會設置失敗標志ios::failbit,所以此時應當調用clear()函數清除錯誤標志。同樣,因為該版本get不會提取回車符,所以需要用另一版本的get()提取回車符。不同版本的Get函數參見前文。此處還使用了*cout.rdbuf(),cout是ostream類的對象,當然就有緩沖區,可以用rdbuf返回緩沖區對象的指針。最后,關於fin.clear, 需要特別注意的是:要清空流類對象的內存,不能用clear方法,那只是設置了錯誤標志位;

方法三:利用重載的get函數

ifstream fin("main.cpp"); 
fin.get(*cout.rdbuf(), EOF);

代碼解釋:這個版本的get成員函數可以自定義提取終止符。這里通過設置為文件結束符(EOF)來達到一下提取整個文件的目的。

當然,你可以把上面的cout換成任意的輸出流,比如文件輸出流,從而可以實現文件的拷貝功能。

另外,上面代碼中並沒有使用輸入流的>>操作符,因為>>和<<是相對的,只是把操作數交換一下位置罷了。因此,你可以把上面代碼中用out<<in.rdbuf()的地方換成in>>out.rdbuf(),代碼的功能完全一樣,效果也一樣。

關於流緩沖區
1. 緩沖類型。

標准庫提供緩沖是為了減少對read和write的調用。提供的緩沖有三種類型(整理自APUE):

全緩沖。 在這種情況下,實際的I/O操作只有在緩沖區被填滿了之后才會進行。對駐留在磁盤上的文件的操作一般是有標准I/O庫提供全緩沖。緩沖區一般是在第一次對流進行I/O操作時,由標准I/O函數調用malloc函數分配得到的。flush描述了標准I/O緩沖的寫操作。緩沖區可以由標准I/O函數自動flush(例如緩沖區滿的時候);或者我們對流調用fflush函數。
行緩沖。 在這種情況下,只有在輸入/輸出中遇到換行符的時候,才會執行實際的I/O操作。這允許我們一次寫一個字符,但是只有在寫完一行之后才做I/O操作。一般的,涉及到終端的流–例如標注輸入(stdin)和標准輸出(stdout)–是行緩沖的。

無緩沖。 標准I/O庫不緩存字符。需要注意的是,標准庫不緩存並不意味着操作系統或者設備驅動不緩存。

ISO C要求:

當且僅當不涉及交互設備時,標准輸入和標准輸出是全緩存的。

標准錯誤絕對不是全緩存的。

但是,這並沒有告訴我們當標准輸入/輸出在涉及交互設備時,它們是無緩存的還是行緩存的;也沒有告訴我們標准錯誤應該是行緩存的還是無緩存的。不過,大多數實現默認的緩存類型是這樣的:

標准錯誤總是無緩存的。

對於所有的其他流來說,如果它們涉及到交互設備,那么就是行緩存的;否則是全緩存的。

2. 改變默認緩存類型,即自定義緩沖區

a. 對於C中文件操作:

可以通過下面的函數改變緩存類型(摘自APUE):

void setbuf(FILE *restrict fp, char *restrict buf); 
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

這些函數必須在流打開之后、但是未對流做任何操作之前被調用(因為每個函數都需要一個有效的文件指針作為第一個參數)。

利用setbuf,可以打開或者關閉緩存。為了打開緩存,buf參數必須一個大小為BUFSIZ的緩存,BUFSIZ是定義在stdio.h中的常量。<ISO/IEC 9899>要求:BUFSIZ至少為256。如果要關閉緩存,可以將buf設成NULL。

利用setvbuf,我們可以設定緩存類型。這是通過mode參數指定的。

b. 對於C++中流:

virtual basic_streambuf *setbuf(E *s, streamsize n);

第一個參數指向自定義緩沖區空間,第二個為緩沖區大小。

格式化輸出
查看《C++流操作》

摘要:控制符是在頭文件iomanip.h中定義的對象。 使用前必須把iomanip.h包含進來

摘選自:

(1)《C++流總結》

(2)《關於流和緩沖區的理解》
---------------------
作者:erzr_zhang
來源:CSDN
原文:https://blog.csdn.net/erzr_zhang/article/details/52770789
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM