??在c++中引入了stream堤瘤,一開始接觸這個的時候感覺無法正確的理解stream的用法,在寫項目的時候要用到讀寫文件,慢慢理解了stream的一些基本的用法。
-
overview
首先我們來看一下fstream的基本信息:
??可以看到
fstream
是從iostream
中繼承來的霎箍,那么iostream
的方法fstream
闯冷,我們再來看看c++中提供的I/O類:
圖中表明了各個類的關系榨咐,我們主要來看看
fstream
中的內(nèi)容璧亚,fstream
有4個類:
ifstream
fstream
ofstream
filebuf
其中前三個類中ifstream
和ofstream
的意思從字面上很好理解:
ifstream
將文件作為輸入的源ofstream
將文件作為輸出destinationfstream
在定義文件流對象是可以自己設置為in
或者out
定義了文件流對象后,每個文件流對象維護自己的filebuf
,filebuf
是與文件關聯(lián)的緩沖區(qū)柱搜,filebuf
對象在其內(nèi)部是通過操作一個中間的輸入/輸出緩沖區(qū)迟郎,該文件內(nèi)容的改變會與其同步(在對文件操作完后顯示的調(diào)用sync或者文件關閉之后)
-
不同I/O類型的關系
- 從概念上來說,不同的設備和不同的
char
(char
聪蘸、wchar
)的size
對I/O操作沒有影響宪肖。例如:
我們可以用
cin
從控制臺、文件健爬、string
中獲取輸入控乾;
????同理也可以用cout
向不同控制臺、文件娜遵、string
中輸出
c++抹平了這些是通過繼承實現(xiàn)的蜕衡,ifstream
和istringstream
是從istream
中繼承來的,那么繼承保證我們在使用ifstream
和istringstream
時好像在使用istream
(cin
)一樣魔熏。
I/O類不允許拷貝和賦值(將拷貝構造和賦值構造聲明為私有方法并不予以實現(xiàn))
-
fstream
我們來看一個簡單的示例程序:
#include <iostream>
#include <fstream>
int main(int argc, char **argv)
{
std::ofstream my_file;
my_file.open("example.txt");
my_file << "Contents from ofstream.\n"
my_file.close()
return 0;
}
以上的代碼會往example.txt
文件(若當前路徑下存在則直接寫入,不存在則會創(chuàng)建對應的文件)中寫入我們要寫的內(nèi)容鸽扁,和cout
向屏幕輸出一樣蒜绽,只是這里屏幕變成了文件而已。
從以上的代碼入手桶现,我們來一步一步看看文件流的用法躲雅。
-
打開文件
開文件就是我們需要將文件與文件對象關聯(lián),一個打開的文件在程序中的呈現(xiàn)方式是以文件流對象存在的骡和,對流對象的任何操作都會應用到物理文件上相赁,打開一個文件的動作是:
open(filename, mode)
其中
filename
是文件名,類型是字符串慰于,mode
是一個可選的參數(shù)钮科,可以從以下組合中選擇:
以上的每個可選參數(shù)可以通過位操作的|
來組合,例如我們想將文件以二進制的形式打開婆赠,并向其尾巴部添加內(nèi)容:
????std::ofstream my_file("example.txt", ios::out | ios::app | ios::binary);
每一個fstream
都有自己的默認模式:
通過以上的表格绵脯,ifstream
和ofstream
即使在定義的時候,其mode
參數(shù)默認為in
或者out
,對于fstream
蛆挫,默認的mode
被應用只在fstream
的mode
參數(shù)沒有傳入任何值的時候才會組合ios::in
和ios::out
,但是只要mode
中有一個參數(shù)被選擇赃承,那么默認的參數(shù)就會被重寫,不考慮先前的組合的mode
'fstream'分別定義了三個構造函數(shù)會自動的調(diào)用open()
函數(shù)悴侵,我們可以這樣來定義一個fstream
對象:
std::fstream my_file("example.txt", ios::out | ios::app | ios::binary);
但是打開文件可能會出現(xiàn)打不開的異常情況瞧剖,標準庫提供了一個函數(shù)is_open()
去檢測文件是否打開成功:
if(my_file.is_open()){
/*open is successful, proceed with output*/
}
-
關閉文件
就像我們開水龍頭必然要關水龍頭一樣,我們打開文件必須要關閉文件可免,文件一直打開會消耗系統(tǒng)資源抓于。在我們完成了對我文件的輸入或輸出操作后關閉文件后,對應的文件資源變得可用。
??my_file.close();
以上的close()
調(diào)用將會刷新與之相關的緩沖區(qū)并將文件關閉巴元。在關閉之后其原先的文件流對象可以綁定其他文件了毡咏,并且該文件可以被其他進程打開。值得注意的是逮刨,在實際的編寫代碼的過程中我們經(jīng)常會忘記調(diào)用close()
呕缭,但是在文件流對象被析構的時候與之關聯(lián)的文件也會被關閉,但是我認為自己主動去關閉文件是個好習慣修己,最好不要把這個任務交給編譯器恢总。
-
文本文件
在打開文件的的模式中不加入ios::binary
的打開方式是以文本的方式去打開文件,在這里存在一個問題是睬愤,對于格式化輸出(\n
片仿、\t
等轉(zhuǎn)移字符)的文本,會有一個轉(zhuǎn)換的過程尤辱,這里的行為就像cout
向屏幕輸出一樣砂豌。
#include <iostream>
#include <fstream>
int main(int argc, char **argv)
{
std::ofstream my_file("example.txt");
if(my_file.is_open()){
my_file << "This is a line.\n";
my_file << "This is another line.\n";
}else{
std::cout <<"Unable to open file";
}
return 0;
}
文件的內(nèi)容如下:
example.txt:
This is a line.
This is another line.
從文件中輸入的行為類似與我們從cin
中獲取輸入的行為類似,這里不舉例光督。
-
檢測打開文件的文件流對象的狀態(tài)
標準庫提供了一系列成員函數(shù)去檢測文件流對象的狀態(tài)阳距。
bad()
對文件的讀寫操作失敗時返回true,例如,當我們試圖去向一個未打開的文件執(zhí)行寫入操作或者向文件寫入時文件空間不足的時候會返回true
fail()
行為和
bad()
類似结借,但是在在讀寫文件內(nèi)容筐摘,內(nèi)容格式的錯誤的時候也會返回true,例如我們?nèi)プx一個整數(shù)的時候船老,但是一個字母被讀入的時候咖熟,這時候'fail()'會返回true
eof()
讀文件到文件尾的時候返回true
good()
在以上介紹的所有狀態(tài)中只要有一個返回true
good()
就返回false,注意good()
和bad()
不是相反的操作,相對于bad()
,good()
檢測的狀態(tài)更多柳畔。
成員函數(shù)clear()
用于重置以上的狀態(tài)馍管。
-
獲取和設置文件流對象的位置
所有的流對象都會在其內(nèi)部至少維護一個指示讀寫位置的動作。
ifstream
和istream
一樣維護一個內(nèi)部的get
薪韩,來獲取當前文件的讀入的位置咽斧,ofstream
反之亦然堪置,而fstream
維護了一個get
和put
的動作。
對于當前文件流讀寫的位置可以通過以下的成員函數(shù)獲取或者修改张惹。
tellg() && tellp()
這兩個函數(shù)返回一個類型為
streampos
的值舀锨,這個類型的值表示當前的位置,其中get position
(tellg()返回)或表示put position
(tellp()返回)
seekg() && seekp()
以上函數(shù)允許改變當前的
get
或put position
宛逗,其函數(shù)調(diào)用被重載:
seekg(position)
seekp(position)
以上兩個函數(shù)改變了stream pointer
的position
的絕對位置(從文件的開始位置算起)坎匿,其參數(shù)的類型是streampos
另外兩個重載函數(shù)的原型為;
seekg(offset, direction)
seekp(offset, direction)
以上是改變stream position
的相對位置雷激,以direction
為參考對象作偏移替蔬,其中direction
的類型是seekdir
,是一個枚舉類型屎暇,其值有以下的值可選:
| ios::beg
| offset counted from the beginning of the stream
|
| ios::cur
| offset counted from the curent position
???????|
| ios::end
| offset counted from the end of the stream
?????|
/**********************************
* @Brief: Obtaining the file size
* @CreatedTime:29/5/16
**********************************/
#include <iostream>
#include <fstream>
int main(int argc, char **argv)
{
std::streampos begin, end;
std::ifstream my_file("example.bin", ios::binary);
begin = my_file.tellg() // get the beginning position
my_file.seekg(0, ios::end); // set the stream pointer to end position
end = my_file.tellg(); // get the end position
my_file.close(); // close the opening file
std::cout << "Size is: " << (end - begin) << "bytes.\n"; // calculate the file size
return 0;
}
在以上代碼中我們注意到有streampos
類型的值承桥,streampos
類型是專門為緩沖區(qū)和stream pointer
的position
使用的,具有相同類型的值可以做減法根悼,也可以轉(zhuǎn)換為整型凶异。
-
二進制文件
二進制的文件不同于文本文件,文本文件讀寫其內(nèi)容時有其格式挤巡,例如我們可以利用getline
獲取每一行的內(nèi)容剩彬,但是首先對于二進制文件來說getline()
來讀取二進制文件效率不高,還有就是二進制的數(shù)據(jù)可能不是按照行的格式存儲矿卑。
??file stream
為二進制的數(shù)據(jù)設計了兩個成員函數(shù)讀寫二進制文件喉恋,read()
和write()
是讀寫二進制文件的操作,其中write()
是ostream
的成員函數(shù)母廷,fstream
從ostream
中繼承得到這個函數(shù);read()
函數(shù)是istream
的成員函數(shù)轻黑,ifstream
從其繼承這個函數(shù); fstream
的對象同時擁有這兩個函數(shù),其中read
和write
函數(shù)的原型如下:
write(memory_block, size)
read(memory_block, size)
read()
和write()
中的memory_block
是一個字節(jié)數(shù)組(char*)琴昆,表示要讀入或者寫入的字節(jié)塊氓鄙,其中size
是一個整型表示讀取或者要寫入的字節(jié)數(shù)。
#include <iostream>
#include <fstream>
int main(int argc, char **argv)
{
std::streampos size;
char *memory_block;
std::ifstream file("example.txt", ios::in | ios::bnary | ios::ate);
if(file.is_open()){
size = file.tellg();
memory_block = new char[size];
file.seekg(0, ios::beg);
file.read(memory_block, size);
file.close();
std::cout << "the entire file is in memory";
delete[] memory_block;
}else{
std::cout << "unable to open the file";
}
return 0;
}
通過以上的程序我們將文件的內(nèi)容放入內(nèi)存中(char數(shù)組)椎咧,由于無法事先確定文件的大小玖详,所以我們需要動態(tài)分配這個數(shù)組的大小把介,一開始我們打開文件的時候我們將position
指針放在了文件內(nèi)容的末尾勤讽,當調(diào)用tellg
的時候我們可以可以得到文件內(nèi)容的大小。在分配了整個數(shù)組后拗踢,我們將positon
指針放回到文件開始脚牍,將文件讀入。
-
緩沖和同步
當我們使用流對象時巢墅,在file stream
和物理文件之間存在一個類型為streambuf
的緩沖對象诸狭,例如券膀,我們有一個ofstream
的對象,我們調(diào)用put
的時候每次會向這個中間的buffer
插入一個字節(jié)驯遇,而不是直接插入到與之關聯(lián)的文件中芹彬。
當緩沖區(qū)刷新的時候,緩沖區(qū)中的內(nèi)容會寫入到與之關聯(lián)的文件中叉庐,當以下事件發(fā)生時舒帮,緩沖區(qū)會刷新:
**文件關閉: **在文件關閉之前,緩沖區(qū)中還沒有刷新的數(shù)據(jù)會同步陡叠,待處理的數(shù)據(jù)寫入文件或先從文件中讀取玩郊。
**緩沖區(qū)溢出: **當緩沖區(qū)溢出的時候會自動同步。
**手動同步: **顯式的執(zhí)行flush
或者endl
會刷新緩沖區(qū)枉阵。
**調(diào)用成員函數(shù)同步: ** 對應的文件流對象調(diào)用sync()
的方法就可以同步緩存區(qū)译红,當同步失敗的時候返回-1,同步成功返回0.
相關資料:
Input/output with files
C++ Primer 5th
Keep focus and have fun