隨機文件I / O
文件指針
每個文件流類都包含一個文件指針,用於跟蹤文件中的當前讀/寫位置。當從文件中讀取或寫入某些內容時,讀取/寫入將發生在文件指針的當前位置。默認情況下,打開文件進行讀取或寫入時,文件指針將設置爲文件的開頭。但是,如果在附加模式下打開文件,則文件指針將移動到文件末尾,因此寫入不會覆蓋文件的任何當前內容。
使用seekg()和seekp()進行隨機文件訪問
到目前爲止,我們所做的所有文件訪問都是順序的,也就是說,我們已按順序讀取或寫入文件內容。但是,也可以進行隨機文件訪問,也就是說,跳過文件中的各個點來讀取其內容。當您的文件已滿,並且您希望檢索特定記錄時,這可能很有用。您可以直接跳到要檢索的記錄,而不是在找到所需記錄之前讀取所有記錄。
通過使用seekg()函數(用於輸入)和seekp()函數(用於輸出)操作文件指針來完成隨機文件訪問。如果你想知道,g代表“get”而p代表“put”。對於某些類型的流,seekg()(更改讀取位置)和seekp()(更改寫入位置)獨立運行,但是,對於文件流,讀取和寫入位置始終相同,因此seekg和seekp可以是可互換使用。
seekg()和seekp()函數有兩個參數。第一個參數是一個偏移量,用於確定移動文件指針的字節數。第二個參數是Ios標誌,指定偏移參數應偏移的內容。
Ios seek flag | 含義 |
---|---|
beg | 偏移量相對於文件的開頭(默認) |
cur | 偏移量相對於文件指針的當前位置 |
end | 偏移量相對於文件末尾 |
正偏移意味着將文件指針移向文件末尾,而負偏移意味着將文件指針移向文件的開頭。
這裏有些例子:
inf.seekg(14, ios::cur); //向前移動14個字節
inf.seekg(-18, ios::cur); //向後移動18個字節
inf.seekg(22, ios::beg); // 在文件中移動到第22個字節
inf.seekg(24); // 在文件中想移動到第24個字節
inf.seekg(-28, ios::end); // 移到文件結尾前的第28個字節
移動到文件的開頭或結尾很容易:
inf.seekg(0, ios::beg); // 移到文件的開頭
inf.seekg(0, ios::end); // 移到文件末尾
讓我們使用seekg()和我們在上一課中創建的輸入文件做一個例子。該輸入文件如下所示:
This is line 1
This is line 2
This is line 3
This is line 4
這是一個例子:
int main()
{
using namespace std;
ifstream inf("Sample.dat");
//如果我們無法打開輸入文件流進行讀取
if (!inf)
{
// 打印錯誤並退出
cerr << "Uh oh, Sample.dat could not be opened for reading!" << endl;
exit(1);
}
string strData;
inf.seekg(5); // 移動到第5個字符
// 獲取剩餘的內容並打印出來
getline(inf, strData);
cout << strData << endl;
inf.seekg(8, ios::cur); // 將8個字節移動到文件中
// 獲取剩餘的內容並打印出來
getline(inf, strData);
cout << strData << endl;
inf.seekg(-15, ios::end); // 在文件末尾向前移動15個字節
// 獲取剩餘的內容並打印出來
getline(inf, strData);
cout << strData << endl;
return 0;
}
這會產生結果:
is line 1
line 2
his is line 4
注意:一些編譯器在與文本文件結合使用時(由於緩衝)具有seekg()和tellg()的錯誤實現。如果您的編譯器是其中之一(並且您將知道因爲您的輸出將與上面的不同),您可以嘗試以二進制模式打開文件:
ifstream inf("Sample.dat", ifstream::binary);
另外兩個有用的函數是tellg()和tellp(),它們返回文件指針的絕對位置。這可用於確定文件的大小:
ifstream inf("Sample.dat");
inf.seekg(0, ios::end); // 移到文件末尾
cout << inf.tellg();
這打印:
64
這是sample.dat以字節爲單位的長度(假設在最後一行之後返回一個回車符)。
使用fstream同時讀取和寫入文件
fstream類能夠同時讀取和寫入文件!這幾乎是這裏最大的警告,不可能在任意讀寫之間切換。一旦發生讀或寫,在兩者之間切換的唯一方法是執行修改文件位置的操作(例如,搜索)。如果你實際上不想移動文件指針(因爲它已經在你想要的位置),你可以隨時尋找當前位置:
//假設iofile是fstream類型的對象
iofile.seekg(iofile.tellg(), ios::beg); // 尋求當前的文件位置
如果你不這樣做,可能會發生任何奇怪和奇怪的事情。
(注意:雖然看起來似乎iofile.seekg(0, ios::cur)也可行,但似乎有些編譯器可能會對此進行優化。
另一個棘手的問題:與ifstream不同,我們可以說while (inf)確定是否有更多要閱讀,這對fstream不起作用。
讓我們使用fstream做一個文件I / O示例。我們將編寫一個程序來打開文件,讀取其內容,並將它找到的任何元音更改爲“#”符號。
int main()
{
using namespace std;
//注意我們必須同時指定in和out,因爲我們正在使用fstream
fstream iofile("Sample.dat", ios::in | ios::out);
// 如果我們無法打開iofile,請輸出錯誤
if (!iofile)
{
// 打印錯誤並退出
cerr << "Uh oh, Sample.dat could not be opened!" << endl;
exit(1);
}
char chChar; //我們將按字符分類
// 雖然仍有數據需要處理
while (iofile.get(chChar))
{
switch (chChar)
{
//如果我們找到一個元音
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
// 備份一個character
iofile.seekg(-1, ios::cur);
// 因爲我們進行了搜索,所以我們現在可以安全地進行寫操作了
//讓我們在元音上寫一個#
iofile << '#';
//現在我們想回到讀取模式,以便下次調用
// get()將正確執行. 我們將seekg()用於當前位置,
// 因爲我們不想移動文件指針。
iofile.seekg(iofile.tellg(), ios::beg);
break;
}
}
return 0;
}
其他有用的文件功能
要刪除文件,只需使用remove()函數即可。
此外,如果流當前打開,則is_open()函數將返回true,否則返回false。
關於編寫指向磁盤的指針的警告
雖然將變量傳輸到文件非常簡單,但在處理指針時事情會變得更加複雜。請記住,指針只是保存它指向的變量的地址。雖然可以讀取和寫入磁盤的地址,但這樣做非常危險。這是因爲變量的地址可能因執行而異。因此,雖然當您將該地址寫入磁盤時,變量可能已經存在地址0x0012FF7C,但當您重新讀取該地址時,它可能不再存在於那裏!
例如,假設您有一個名爲nValue的整數,它位於地址0x0012FF7C處。您爲nValue指定了值5.您還聲明瞭一個名爲* pnValue的指針指向nValue。pnValue保存nValue的地址0x0012FF7C。您希望以後保存這些,因此將值5和地址0x0012FF7C寫入磁盤。
幾周後,再次運行程序並從磁盤讀取這些值。您將值5讀入另一個名爲nValue的變量,該變量位於0x0012FF78。您將地址0x0012FF7C讀入名爲* pnValue的新指針。因爲當nValue位於0x0012FF78時pnValue現在指向0x0012FF7C,pnValue不再指向nValue,並且嘗試訪問pnValue會導致您遇到麻煩。
規則:不要將地址寫入文件。當您從磁盤讀回其值時,最初位於這些地址的變量可能位於不同的地址,並且地址將無效。