程式中的資料總是在流動著,既然是流動就會有方向。資料從程式的外部流到程式內部,稱為輸入;資料從程式內部流到外部稱為輸出。
C++
提供有相應的API
實現程式和外部資料之間的互動,統稱這類API
為 IO
流API
。
流
是一個形象概念,資料從一端傳遞到另一端時,類似於水一樣在流動,只是流動的不是水,而是資料
。
概括而言,流物件可連線 2
端,並在兩者之間搭建起一個通道 ,讓資料通過此通道流過來、流過去。
初學C++
時,會接觸 cout
和cin
兩個流物件。
cout
稱為標準輸出流物件,其一端連執行緒式,一端連線標準輸出裝置(標準輸出裝置一般指顯示器
),cout
的作用是把程式中的資料顯示在顯示器上。
除了cout
,還有cerr
,其作用和 cout
相似。兩者區別:
cout
帶有資料快取功能,cerr
不帶快取功能。
快取
類似於蓄水池
,輸出時,先快取資料,然後再從快取中輸出到顯示器上。
cout
輸出程式通用資料(測試,邏輯結果……),cerr
輸出錯誤資訊。
另還有一個
clog
物件,和cerr
類似,與cerr
不同之處,帶有快取功能。
cin
稱為標準輸入流物件,一端連執行緒式,一端連線標準輸入裝置(標準輸入裝置一般指鍵盤),cin
用來把標準輸入裝置上的資料輸入到程式中。
使用 cout
和cin
時需要包含 iostream
標頭檔案。
#include <iostream>
開啟 iostream
原始碼,可以看到 iostream
檔案中包含了另外 2
個標頭檔案:
#include <ostream>
#include <istream>
且在 iostream
標頭檔案中可以查詢到如下程式碼:
extern istream cin; /// Linked to standard input
extern ostream cout; /// Linked to standard output
extern ostream cerr; /// Linked to standard error (unbuffered)
extern ostream clog; /// Linked to standard error (buffered)
cout
、cerr
、clog
是 ostream
類的範例化物件,cin
是 istream
類的範例化物件。
ostream
類過載了<<
運運算元,istream
類過載了>>
運運算元,可以使用這 2
個運運算元方便、快速地完成輸入、輸出各種型別資料。開啟原始碼,可以檢視到 <<
運運算元返回撥用者本身。意味著使用 cout<<資料
時,返回 cout
本身,可以以鏈式方式
進行資料輸出。
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
string name="果殼";
int age=12;
//鏈式輸出格式
cout<<"姓名:"<<name<<"年齡:"<<age;
return 0;
}
istream
類過載了 >>
運運算元,返回撥用者(即 istream
物件)本身,也可以使用鏈式方式進行輸入。
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
char sex;
int age;
//鏈式輸入
cin>>sex>>age;
return 0;
}
cout
、cin
流物件的其它函數暫不介紹,繼續本文的重點檔案流。
檔案流 API
完成程式中的資料和檔案中的資料的輸入與輸出,使用時,需要包含 fstream
標頭檔案。
#include <fstream>
ifstream
從 istream
類派生,用來實現把檔案中的資料l輸入(讀)到程式中。
輸入操作對程式而言,也稱為
讀
操作。
檔案輸入流物件的使用流程:
使用 ifstream
流物件的 open
函數建立起程式
和外部儲存裝置
中的檔案資源之間的流通道。
檔案型別分文字檔案和二進位制檔案。
使用之前,瞭解一下 open
函數的原型說明。開啟ifstream
標頭檔案,可檢視到 ifstream
類中有如下的資訊說明:
template<typename _CharT, typename _Traits>
class basic_ifstream : public basic_istream<_CharT, _Traits>
{
/**
* @brief Opens an external file.
* @param __s The name of the file.
* @param __mode The open mode flags.
*
* Calls @c std::basic_filebuf::open(s,__mode|in). If that function
* fails, @c failbit is set in the stream's error state.
*
* Tip: When using std::string to hold the filename, you must use
* .c_str() before passing it to this constructor.
*/
void open(const char* __s, ios_base::openmode __mode = ios_base::in)
{
if (!_M_filebuf.open(__s, __mode | ios_base::in))
this->setstate(ios_base::failbit);
else
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 409. Closing an fstream should clear error state
this->clear();
}
#if __cplusplus >= 201103L
/**
* @brief Opens an external file.
* @param __s The name of the file.
* @param __mode The open mode flags.
*
* Calls @c std::basic_filebuf::open(__s,__mode|in). If that function
* fails, @c failbit is set in the stream's error state.
*/
void open(const std::string& __s, ios_base::openmode __mode = ios_base::in)
{
if (!_M_filebuf.open(__s, __mode | ios_base::in))
this->setstate(ios_base::failbit);
else
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 409. Closing an fstream should clear error state
this->clear();
}
#endif
}
ifstream
過載了 open
函數,2
個函數引數數量一致,但第一個引數的型別不相同。呼叫時需要傳遞 2
個引數:
open
函數通過 const char* __s
型別(字串指標)接受,第二個open
函數通過const std::string& __s
型別(字串物件)接受。ios_base::in(輸入)
模式。開啟模式如下所示: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
};
typedef _Ios_Openmode openmode;
/// 以寫的方式開啟檔案,寫入的資料追加到檔案末尾
static const openmode app = _S_app;
/// 開啟一個已有的檔案,檔案指標指向檔案末尾
static const openmode ate = _S_ate;
/// 以二進位制方式開啟一個檔案,如不指定,預設為文字檔案方式
static const openmode binary = _S_bin;
/// 以輸入(讀)方式開啟檔案
static const openmode in = _S_in;
/// 以輸出(寫)方式開啟檔案,如果沒有此檔案,則建立,如有此檔案,此清除原檔案中資料
static const openmode out = _S_out;
/// 開啟檔案的時候丟棄現有檔案裡邊的內容
static const openmode trunc = _S_trunc;
開啟檔案實現:
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
ifstream inFile;
//檔案路徑儲存在字元陣列中
char fileName[50]="d:\\guoke.txt";
inFile.open(fileName,ios_base::in);
//檔案路徑儲存在字串物件中
string fileName_="d:\\guoke.txt" ;
inFile.open(fileName_,ios_base::in);
return 0;
}
除了直接呼叫 open
函數外,還可以使用 ifstream
的建構函式,如下程式碼,本質還是呼叫 open
函數。
char fileName[50]="d:\\guoke.txt";
//建構函式
ifstream inFile(fileName,ios_base::in);
或者:
string fileName_="d:\\guoke.txt" ;
ifstream inFile(fileName_,ios_base::in);
可以使用ifstream
的 is_open
函數檢查檔案是否開啟成功。
開啟檔案後,意味著輸入流通道
建立起來,預設情況下,檔案指標指向檔案的首位置,等待讀取操作。
讀或寫都是通過移動檔案指標實現的。
讀取資料的方式:
>>
運運算元。ifstream
是istream
的派生類,繼承了父類別中的所有公共函數,如同 cin
一樣可以使用 >>
運運算元實現對檔案的讀取操作。
cin
使用>>
把標準輸入裝置上的資料輸入至程式。
ifstream
使用>>
把檔案中的資料輸入至程式。兩者的資料來源不一樣,目的地一樣。
提前在 guoke.txt
檔案中寫入如下內容,也可以用空白隔開數位。
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//用來儲存檔案中的資料
int nums[10];
//檔案輸入流物件
ifstream inFile;
//檔案路徑
char fileName[50]="d:\\guoke.txt";
//開啟檔案
inFile.open(fileName,ios_base::in);
if(inFile.is_open()) {
//檢查檔案是否正確開啟
cout<<"檔案開啟成功"<<endl;
//讀取檔案中的內容
for(int i=0; i<5; i++){
//讀取
inFile>>nums[i];
//輸出到顯示器
cout<<nums[i]<<endl;
}
}
return 0;
}
如上程式碼,把檔案中的 5
個數位讀取到 nums
陣列中。
用
>>
運運算元讀取時,以換行符、空白等符號作為結束符。
get
、getline
函數。ifstream
類提供有 get
、getline
函數,可用來讀取檔案中資料。get
函數有多個過載,本文使用如下的 2
個。getline
函數和get
函數功能相似,其差異之處後文再述。
//以字元為單位讀取
istream &get( char &ch );
//以字串為單位讀取
istream &get( char *buffer, streamsize num );
先在 D
盤使用記事本建立 guoke.txt
檔案,並在檔案中輸入以下 2
行資訊:
this is a test
hello wellcome
編寫如下程式碼,使用 get
函數以字元型別
逐個讀取檔案中的內容。
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//用來儲存檔案中的資料
int nums[10];
ifstream inFile;
char fileName[50]="d:\\guoke.txt";
inFile.open(fileName,ios_base::in);
char myChar;
if(inFile.is_open()) {
cout<<"檔案開啟成功"<<endl;
//以字元為單位讀取資料
while(inFile.get(myChar)){
cout<<myChar;
}
}
return 0;
}
//輸出結果
this is a test
hello wellcome
讀取時,需要知道是否已經達到了檔案的未尾,或者說如何知道檔案中已經沒有資料。
如上使用 get
函數讀取時,如果沒有資料了,會返回false
。
使用 eof
函數。eof
的全稱是 end of file
, 當檔案指標移動到檔案無資料處時,eof
函數返回 true
。建議使用此函數。
while(!inFile.eof()){
inFile.get(myChar);
cout<<myChar;
}
使用 get
的過載函數以字串
型別讀取。
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//用來儲存檔案中的資料
int nums[10];
ifstream inFile;
char fileName[50]="d:\\guoke.txt";
inFile.open(fileName,ios_base::in);
char myChar[100];
if(inFile.is_open()) {
cout<<"檔案開啟成功"<<endl;
while(!inFile.eof() ) {
//以字串為單位讀取
inFile.get(myChar,100);
cout<<myChar<<endl;
//為什麼要呼叫無參的 get 函數?
inFile.get();
}
}
return 0;
}
輸出結果:
上述 get
函數以字串
為單位進行資料讀取,會把讀出來的資料儲存在第一個引數 myChar
陣列中,第二個引數限制每次最多讀 num-1
個字元。
如果把上述的
inFile.get(myChar,100);
改成
inFile.get(myChar,10);
則程式執行結果如下:
第一次讀了 9
個字元后結束 ,第二次遇到到換行符後結束,第三行讀了 9
個字元后結束,第四行遇到檔案結束後結束 。
為什麼在程式碼要呼叫無參 get
函數?
因為get
讀資料時會把換行符
保留在快取器中,在讀到第二行之前,需要呼叫無參的 get
函數提前清除(讀出)快取器。否則後續資料讀不出來。
getline
和 get
函數一樣,可以以字串
為單位讀資料,但不會快取換行符(結束符)。如下同樣可以讀取到檔案中的所有內容。
while(inFile.eof()){
inFile.getline(myChar,100)
cout<<myChar<<endl;
}
read
函數。除了get
和getline
函數還可以使用 read
函數。函數原型如下:
istream &read( char *buffer, streamsize num );
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//用來儲存檔案中的資料
int nums[10];
ifstream inFile;
char fileName[50]="d:\\myinfo.txt";
inFile.open(fileName,ios_base::in);
char myChar[100];
if(inFile.is_open()) {
cout<<"檔案開啟成功"<<endl;
inFile.read(myChar,100);
cout<<myChar;
}
return 0;
}
read
一次性讀取到num
個位元組或者遇到 eof(檔案結束符)
停止讀操作。這點和 get
和getline
不同,後者以換行符為結束符號。
讀操作結束後,需要關閉檔案物件。
inFile.close();
ofstream
稱為檔案輸出流,其派生於ostream
,用於把程式中的資料輸出(寫)到檔案中。和使用 ifstream
的流程一樣,分 3
步走:
使用 ofstream
流物件的 open
函數(和 ifstream
的 open
函數引數說明一樣)開啟檔案,因為是寫操作,開啟的模式預設是ios_stream::out
,當然,可以指定其它的如ios_stream::app
模式。
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//輸出流物件
ofstream outFile;
char fileName[50]="d:\\guoke.txt";
outFile.open(fileName,ios_base::out);
if (outFile.is_open()){
cout<<"開啟檔案成功"<<endl;
}
return 0;
}
<<
運運算元。#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//輸出流物件
ofstream outFile;
char fileName[50]="d:\\guoke.txt";
outFile.open(fileName,ios_base::out);
if (outFile.is_open()){
cout<<"開啟檔案成功"<<endl;
for(int i=0;i<10;i++){
//向檔案中寫入 10 個數位
outFile<<i;
}
}
return 0;
}
輸出結果:
put
、write
函數。put
函數以字元為單位向檔案中寫入資料,put
函數原型如下:
ostream &put( char ch );
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//輸出流物件
ofstream outFile;
char fileName[50]="d:\\guoke.txt";
outFile.open(fileName,ios_base::out);
if (outFile.is_open()){
cout<<"開啟檔案成功"<<endl;
for(int i=0;i<10;i++){
//寫入 10 個大寫字母
outFile.put(char(i+65) );
}
}
return 0;
}
write
可以把字串
寫入檔案中,如下為write
函數原型:
ostream &write( const char *buffer, streamsize num );
引數說明:
char
型別指標。#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
//輸出流物件
ofstream outFile;
char fileName[50]="d:\\guoke.txt";
outFile.open(fileName,ios_base::out);
char infos[50]="thisisatest";
if (outFile.is_open()){
cout<<"開啟檔案成功"<<endl;
outFile.write(infos,50);
}
return 0;
}
檔案中內容:
thisisatest
如果把
outFile.write(infos,50);
改成
outFile.write(infos,5);
則檔案中內容為
thisi
操作完成後,需要呼叫close
函數關閉檔案。
outFile.close();
隨機存取指可以根據需要移動二進位制檔案
中的檔案指標,隨機讀或寫二進位制檔案中的內容。
隨機存取要求開啟檔案時,指定檔案開啟模式為
ios_base::binary
。
隨機讀寫分 2
步:
隨機存取的關鍵是使用檔案指標
的定位函數進行位置定位:
gcount() 返回最後一次輸入所讀入的位元組數
tellg() 返回輸入檔案指標的當前位置
seekg(檔案中的位置) 將輸入檔案中指標移到指定的位置
seekg(位移量,參照位置) 以參照位置為基礎移動若干位元組
tellp() 返回輸出檔案指標當前的位置
seekp(檔案中的位置) 將輸出檔案中指標移到指定的位置
seekp(位移量,參照位置) 以參照位置為基礎移動若干位元組
如下程式碼,使用檔案輸出流向檔案中寫入資料,然後隨機定位檔案指標位置,再進行讀操作。
#include<fstream>
#include<iostream>
using namespace std;
int main() {
int i,x;
// 以寫的模式開啟檔案
ofstream outfile("d:\\guoke.txt",ios_base::out | ios_base::binary);
if(!outfile.is_open()) {
cout << "open error!";
exit(1);
}
for(i=1; i<100; i+=2)
//向檔案中寫入資料
outfile.write((char*)&i,sizeof(int));
outfile.close();
//輸入流
ifstream infile("d:\\guoke.txt",ios_base::in|ios_base::binary);
if(!infile.is_open()) {
cout <<"open error!\n";
exit(1);
}
//定位
infile.seekg(30*sizeof(int));
for(i=0; i<4 &&!infile.eof(); i++) {
//讀資料
infile.read((char*)&x,sizeof(int));
cout<<x<<'\t';
}
cout <<endl;
infile.close();
return 0;
}
原檔案中內容:
程式碼執行後的執行結果,並沒有輸入檔案中的所有內容。
本文講述了標準輸入、輸出流和檔案流物件。