C++ 基礎系列——文件操作


一、C++文件類及用法

C++ 標准庫提供了 3 個類用於實現文件操作,它們統稱為文件流類,這 3 個類分別為:

  • ifstream:專用於從文件讀取數據
  • ofstream:專用於向文件寫入數據
  • fstream:可讀可寫

這三個文件流類都位於 fstream 頭文件中

  • fstream 類擁有 istream、ostream 類的全部成員方法。
  • fstream 頭文件中並沒有定義可直接使用的 fstream、ifstream 和 ofstream 類對象

fstream 類常用成員方法

成員方法名 適用類對象 功能
open() fstream 打開指定文件,使其與文件流對象關聯
is_open() ifstream 檢查指定文件是否已打開。
close() ofstream 關閉文件,切斷和文件流對象的關聯。
swap() ofstream 交換 2 個文件流對象。
operator>> (i)fstream 重載 >> 運算符,用於從指定文件中讀取數據。
gcount() (i)fstream 返回上次從文件流提取出的字符個數。該函數常和 get()、getline()、ignore()、 peek()、read()、readsome()、putback() 和 unget() 聯用。
get() (i)fstream 從文件流中讀取一個字符,同時該字符會從輸入流中消失。
getline(str,n,ch) (i)fstream 從文件流中接收 n-1 個字符給 str 變量,當遇到指定 ch 字符時會停止讀取,默 認情況下 ch 為 '\0'。
ignore(n,ch) (i)fstream 從文件流中逐個提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 個 字符,或者當前讀取的字符為 ch。
peek() (i)fstream 返回文件流中的第一個字符,但並不是提取該字符。
putback(c) (i)fstream 將字符 c 置入文件流(緩沖區)。
operator<< (o)fstream 重載 << 運算符,用於向文件中寫入指定數據。
put() (o)fstream 向指定文件流中寫入單個字符。
write() (o)fstream 向指定文件中寫入字符串。
tellp() (o)fstream 用於獲取當前文件輸出流指針的位置。
seekp() (o)fstream 設置輸出文件輸出流指針的位置。
flush() (o)fstream 刷新文件輸出流緩沖區。
good() (i/o)fstream 操作成功,沒有發生任何錯誤。
eof() (i/o)fstream 到達輸入末尾或文件尾。
const string url = "asdfasdfasasdf";

//創建一個 fstream 類對象
fstream fs;
//將 test.txt 文件和 fs 文件流關聯
fs.open("test.txt", ios::out);
//向 test.txt 文件中寫入 url 字符串
fs.write(url,30);
fs.close();

二、open開打模式

打開文件目的:

  • 通過指定文件名,建立起文件和文件流對象的關聯
  • 指明文件的使用方式。使用方式有只讀、只寫、既讀又寫、在文件末尾添加數據、以文本方式使用、以二進制 方式使用等多種。

打開文件可以通過兩種方式:

  • 調用流對象的 open 成員函數打開文件。
  • 定義文件流對象時,通過構造函數打開文件。

void open(const char* szFileName, int mode)

  • 第一個參數:指向文件名的指針
  • 第二個參數:打開模式

文件打開模式

模式標記 適用對象 作用
ios::in (i)fstream 打開文件用於讀取數據。如果文件不存在,則打開出錯。
ios::out (o)fstream 打開文件用於寫入數據。如果文件不存在,則新建該文件;如果文件原來就存 在,則打開時清除原來的內容。
ios::app (o)fstream 打開文件,用於在其尾部添加數據。如果文件不存在,則新建該文件。
ios::ate (i)fstream 打開一個已有的文件,並將文件讀指針指向文件末尾(讀寫指 的概念后面解 釋)。如果文件不存在,則打開出錯。
ios::trunc (o)fstream 打開文件時會清空內部存儲的所有數據,單獨使用時與 ios::out 相同。
ios::binary (i/o)fstream 以二進制方式打開文件。若不指定此模式,則以文本模式打開。
ios::in | ios::out fstream 打開已存在的文件,既可讀取其內容,也可向其寫入數據。文件剛打開時,原有內容保持不變。如果文件不存在,則打開出錯。
ios::in | ios::out ofstream 打開已存在的文件,可以向其寫入數據。文件剛打開時,原有內容保持不變。如果文件不存在,則打開出錯。
ios::in | ios::out | ios::trunc fstream 打開文件,既可讀取其內容,也可向其寫入數據。如果文件本來就存在,則打 開時清除原來的內容;如果文件不存在,則新建該文件。

ios::binary 可以和其他模式標記組合使用,如 ios::in| ios::binary。

定義流對象時,在構造函數中給出文件名和打開模式也可以打開文件。以 ifstream 類為例,它有如下構造函數:

ifstream::ifstream (const char* szFileName, int mode = ios::in, int);

  • szFileName:文件名指針
  • mode:打開文件模式,默認 ios::in
  • 極少使用
ifstream inFile("c:\\tmp\\test.txt", ios::in);
if (inFile) 
    inFile.close();
else 
    cout << "test.txt doesn't exist" << endl;

ofstream oFile("test1.txt", ios::out);
if (!oFile) 
    cout << "error 1"; 
else 
    oFile.close();

fstream oFile2("tmp\\test2.txt", ios::out | ios::in); 
if (!oFile2)
    cout << "error 2"; 
else
    oFile.close();

三、文本打開方式和二進制打開方式

文本文件通常用來保存肉眼可見的字符,比如 .txt 文件、.c 文件、.dat 文件等,文本文件中通常采用 ASCII、UTF-8、GBK 等字符編碼,文本編輯器可以識別出這些編碼格式,並將編碼值轉換成字符展示出來。

二進制文件通常用來保存視頻、圖片、程序等不可閱讀的內容,用文本編輯器打開這些文件,會看到一堆亂碼。

文本方式和二進制方式並沒有本質上的區別,只是對於換行符的處理不同。

  • 在 UNIX/Linux 平台中,用文本方式或二進制方式打開文件沒有任何區別,因為文本文件以 \n(ASCII 碼為 0x0a)作為換行符號。
  • Windows 平台上,文本文件以連在一起的 \r\n 作為換行符號。如果以文本方式打開文件,當讀取文件時, 程序會將文件中所有的 \r\n 轉換成一個字符 \n。
  • 同樣當寫入文件時,程序會將 \n 轉換成 \r\n 寫入。

總的來說,Linux 平台使用哪種打開方式都行;Windows 平台上最好用 "ios::in | ios::out" 等打開文本文件, 用 "ios::binary" 打開二進制文件。但無論哪種平台,用二進制方式打開文件總是最保險的。

四、close() 文件方法

調用 close() 方法關閉已打開的文件,可以理解為是切斷文件流對象和文件之間的關聯。注意,close() 方法的功能僅是切斷文件流與文件之間的關聯,該文件流並會被銷毀,其后續還可用於關聯其它的文件。

close() 方法語法格式:

void close();

實際上,當文件流對象的生命周期結束時,會自行調用其析構函數,該函數內部在銷毀對象之前,會先調用 close() 方法切斷它與任何文件的關聯,最后才銷毀它。但是一定要手動調用 close() 方法關閉,避免出現未知錯誤,如讀寫文件失敗。

如果不想頻繁地打開/關閉文件,可以使用 flush() 方法及時刷新輸出流緩沖區,也能起到防止寫入文件失敗的作用。

C++ 中使用 open() 打開的文件,在讀寫操作執行完畢后,應及時調用 close() 方法關閉文件,或者對 文件執行寫操作后及時調用 flush() 方法刷新輸出流緩沖區。

五、文本文件讀寫操作詳解

  • 文件中存儲的數據並沒有類型上的分別,統統都是字符。所謂以文本形式讀/寫文件,就是直白地 將文件中存儲的字符(或字符串)讀取出來,以及將目標字符(或字符串)存儲在文件中。
  • 以二進制形式讀/寫文件,操作的對象不再是打開文件就能看到的字符,而是文件底層存儲的二進制數據。 更詳細地講,當以該形式讀取文件時,讀取的是該文件底層存儲的二進制數據;同樣,當將某數據以二進制形式寫入到文件中時,寫入的也是其對應的二進制數據。

C++ 標准庫中,提供了 2 套讀寫文件的方法組合,分別是:

  1. 使用 >> 和 << 讀寫文件:適用於以文本形式讀寫文件;
  2. 使用 read() 和 write() 成員方法讀寫文件:適用於以二進制形式讀寫文件。
int x,sum=0; 

ifstream srcFile("in.txt", ios::in);    //以文本模式打開 in.txt 備讀
// 假設 in.txt 中為 "10 20 30 40 50" 字符串
if(!srcFile){       // 打開失敗    
    cout << "error opening source file." << endl;
    return 0;
}

ofstream destFile("out.txt", ios::out);     //以文本模式打開 out.txt 備寫 
if (!destFile) {
    srcFile.close();    //程序結束前不能忘記關閉以前打開過的文件 
    cout << "error opening destination file." << endl; 
    return 0;
}

//可以像用 cin 那樣用 ifstream 對象 
while (srcFile >> x) {      //將"10 20 30 40 50" 字符串解析成 int 整數
    sum += x;
    //可以像 cout 那樣使用 ofstream 對象 destFile << x << " ";
    destFile << x << " ";   // destFile 最終會寫入 "10 20 30 40 50" 字符串
}

cout << "sum:" << sum << endl;     // 150
destFile.close(); 
srcFile.close(); 

六、read()和write()讀寫二進制文件

對於類對象,如果以文本形式寫入,每個對象的信息所占的字節數將不同,不利於查找指定學生信息,查找效率低下,這種情況應該以二進制形式寫入對象。

read() 方法用於以二進制形式從文件中讀取數據;write() 方法用於以二進 制形式將數據寫入文件。

write

ofstream 和 fstream 的 write() 成員方法實際上繼承自 ostream 類,其功能是將內存中 buffer 指向的 count 個字節的內容寫入文件,基本格式如下:

ostream & write(char* buffer, int count);

  • buffer:指定要寫入文件的二進制數據的起始位置
  • count:指定寫入字節的個數

該方法可以被 ostream 類的 cout 對象調用,常用於向屏幕上輸出字符串。同時,它還可以被 ofstream 或者 fstream 對象調用,用於將指定個數的二進制數據寫入文件。

write() 方法會從文件寫指針指向的位置將二進制數據寫入。文件剛打開時,文件寫指針指向的是文件的開頭(如果以 ios::app 方式打開,則指向文件末尾),用 write() 方法寫入 n 個字節,寫指針指向的位置就向后移動 n 個字節。

CStudent s; 
ofstream outFile("students.dat", ios::out | ios::binary); 
while (cin >> s.szName >> s.age)
    outFile.write((char*)&s, sizeof(s));
outFile.close();

read

ifstream 和 fstream 的 read() 方法實際上繼承自 istream 類,其功能正好和 write() 方法相反,即從文件中讀 取 count 個字節的數據。該方法的語法格式如下:

istream & read(char* buffer, int count);

  • buffer:指定讀取字節的起始位置
  • count:指定讀取字節的個數

read() 方法從文件讀指針指向的位置開始讀取若干字節。

CStudent s;
ifstream inFile("students.dat",ios::in|ios::binary); //二進制讀方式打開 if(!inFile) {
    cout << "error" <<endl;
    return 0;
}
while(inFile.read((char *)&s, sizeof(s))) { //一直讀到文件結束 
    cout << s.szName << " " << s.age << endl;
}
inFile.close();

七、get()和 put() 逐個讀寫

逐個讀取文件中存儲的字符,或者逐個將字符存儲到文件中的情形,可以使用 get() 和 put() 成員方法實現。

當 fstream 和 ofstream 文件流對象調用 put() 方法時,該方法的功能就變成了向指定文件中寫入單個字符。 put() 方法的語法格式如下:

ostream& put (char c);

  • c:指定要寫入文件的字符
char c;
// 以二進制形式打開文件
ofstream outFile("out.txt", ios::out | ios::binary);
if (!outFile) { 
    cout << "error" << endl;
    return 0;
}

while (cin >> c) { 
    //將字符 c 寫入 out.txt 文件
    outFile.put(c);
}

outFile.close();

注意,由於文件存放在硬盤中,硬盤的訪問速度遠遠低於內存。如果每次寫一個字節都要訪問硬盤,那么文件 的讀寫速度就會慢得不可忍受。因此,操作系統在接收到 put() 方法寫文件的請求時,會先將指定字符存儲在 一塊指定的內存空間中(稱為文件流輸出緩沖區),等刷新該緩沖區(緩沖區滿、關閉文件、手動調用 flush()方法等,都會導致緩沖區刷新)時,才會將緩沖區中存儲的所有字符“一股腦兒”全寫入文件。

get()方法的語法格式有很多,這里僅介紹常見的2中:

int get(); // 返回讀取到的字符的 ASCII碼,末尾返回EOF
istream& get (char& c); // 將讀到字符賦值給 c

八、getline() 讀取一行字符串

getline() 方法定義在 istream 類中,而 fstream 和 ifstream 類繼承自 istream 類,因此 fstream 和 ifstream 的類對象可以調用 getline() 成員方法。

當文件流對象調用 getline() 方法時,該方法的功能就變成了從指定文件中讀取一行字符串。該方法有以下 2 種 語法格式:

istream & getline(char* buf, int bufSize); // 從文件輸入流緩沖區中讀取 bufSize-1 個字符到 buf,或遇到 \n 為止
istream & getline(char* buf, int bufSize, char delim); // 從文件輸入流緩沖區中讀取 bufSize-1 個字符到 buf,或讀到 delim 字符為止

如果文件輸入流中 \n 或 delim 之前的字符個數達到或超過 bufSize,就會導致讀取失敗。

char c[40]; 
//以二進制模式打開 in.txt 文件
ifstream inFile("in.txt", ios::in | ios::binary); 
//判斷文件是否正常打開 
if (!inFile) {
    cout << "error" << endl;
    return 0;
}

//從 in.txt 文件中讀取一行字符串,最多不超過 39 個 
inFile.getline(c, 40); 
cout << c ;
inFile.close();

九、移動和獲取文件讀寫指針(seekp、seekg、tellg、tellp)

  • ifstream 類和 fstream 類有 seekg 成員函數,可以設置文件讀指針的位置;
  • ofstream 類和 fstream 類有 seekp 成員函數,可以設置文件寫指針的位置。

位置指距離文件開頭多少個字節。

這兩個函數的原型如下:

ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);

mode 代表文件讀寫指針的設置模式,有以下三種選項:

  • ios::beg:讓文件讀指針(或寫指針)指向從文件開始向后的 offset 字節處。offset 等於 0 即代表文件開頭。 在此情況下,offset 只能是非負數。
  • ios::cur:在此情況下,offset 為負數則表示將讀指針(或寫指針)從當前位置朝文件開頭方向移動 offset 字節, 為正數則表示將讀指針(或寫指針)從當前位置朝文件尾部移動 offset 字節,為 0 則不移動。
  • ios::end:讓文件讀指針(或寫指針)指向從文件結尾往前的 |offset|(offset 的絕對值)字節處。在此情況下,offset 只能是 0 或者負數。
  • ifstream 類和 fstream 類還有 tellg 成員函數,能夠返回文件讀指針的位置;
  • ofstream 類和 fstream 類還有 tellp 成員函數,能夠返回文件寫指針的位置。

這兩個成員函數的原型如下:

int tellg(); // 返回文件讀指針的位置
int tellp(); // 返回文件寫指針的位置

class CStudent{
public:
    char szName[20];
    int age;
};

int main(){
    CStudent s; 
    fstream ioFile("students.dat", ios::in|ios::out);   //用既讀又寫的方式打開 
    if(!ioFile) {
        cout << "error" ;
        return 0;   
    }

    ioFile.seekg(0,ios::end); //定位讀指針到文件尾部,以便用以后 tellg 獲取文件長度
    int L = 0,R;    // L 是折半查找范圍內第一個記錄的序號,R 是折半查找范圍內最后一個記錄的序號
    R = ioFile.tellg() / sizeof(CStudent) - 1;
    
    //首次查找范圍的最后一個記錄的序號就是: 記錄總數- 1 
    do {
        int mid = (L + R)/2; //要用查找范圍正中的記錄和待查找的名字比對
        ioFile.seekg(mid *sizeof(CStudent),ios::beg);  //定位到正中的記錄 
        ioFile.read((char *)&s, sizeof(s));
        int tmp = strcmp( s.szName,"Jack"); 
        if( tmp == 0 ) { //找到了 
            s.age = 20;
            ioFile.seekp(mid*sizeof(CStudent),ios::beg); 
            ioFile.write((char*)&s, sizeof(s));
            break;
        }
        else if (tmp > 0) //繼續到前一半查找 
            R = mid - 1 ;
        else //繼續到后一半查找 } 
            L = mid + 1; 
    }while(L <= R); 
    ioFile.close();
    return 0;
}


免責聲明!

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



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