2021年4月5日 星期一

C++ 讀取檔案 範例代碼 (行讀取 空格讀取 一次讀取整個文件)

C++ 讀取檔案 範例代碼 (行讀取 或 空格讀取)

純C的讀取可以看這篇站內文
https://charlottehong.blogspot.com/2021/04/c_7.html


大概是新手時常會遇到的問題,撇除大部分的教學都是C的方式讀取,更推薦用C++的方案讀取坑真的少很多,雖然現在主流教學還是用C讀取覺得很奇怪就是了,整理一篇給大家參考。

先簡單說一下會用到的物件是 fstream 基本已經封裝的超級容易使用了,不需要管指標什麼的直接上手就用就是了。

此文範例包含

  • 逐行讀取
  • 按照空格或跳行讀取
  • 一次性讀取整個文件
  • 按照所需長度讀取到C字串char*
  • 獲取檔案長度

開檔

就很簡單,直接宣告一個物件把檔名打進去就好

string buffer;
fstream fs("a.txt");
fs  >> buffer;

就這樣搞定。容易吧,直接一個箭頭就可以把檔案內容推到 string 裡面去了,只是基 stream 的關係遇到”空格”或是”跳行”就會停下來,所以 buffer 內容只會讀取到該處就停下來了,要繼續往下推才可以。

檢查是不是真的開成功了

這個程序沒讀到檔案也不會報錯,要檢查檔案是不是真的讀到了用 is_open() 檢查,有錯誤就自己噴一個例外標記一下哪行炸了吧。

if (!fs.is_open())
    throw runtime_error("Reading error.");

什麼時候讀完

是否推到檔案結束點,可以用 eof() 判定讀取指標是否到檔案結束標記了,當他回傳 1 的時候就是結束了。

cout << fs.eof() << endl;

所以直接把他放進for迴圈判斷中,讓他自己跑到結尾就可以了

fstream fs(file_name);
for(string str; fs >> str;) {
    cout << str << endl;
}

調整游標位置

檔案讀取是有一個類似游標的東西存在的,比如說你打開一個記事本游標一般就停在0的位置,讀取就是按方向鍵,一直往右邊刷這樣。

開檔的時候可以控制的屬性包含游標位置和權限、游標位置可以選擇起始位置在開頭或是結束處,這取決於你的行為如何,如果你要從檔案結尾處新增文字那當然一開始就設置在結尾處比較合適了。

檔案開啟後還可以調整游標位置,比如說

// 調整游標至,開始位置(beg)開始計算第0個。
fs.seekg(0, ios::beg);
// 調整游標至,當前位置(cur)開始計算第-1個。
fs.seekg(-1, ios::cur);
// 調整游標至,結束位置(end)開始計算第-2個。
fs.seekg(-2, ios::end);

順帶一提一個中文占2個位元,想跳過一個中文字要+2。

權限

就是對檔案是要讀檔還是寫入,參數在檔名之後,什麼都不打預設是可讀可寫。

// 只讀檔
fstream fs(file_name, ios::in);

// 只寫檔
fstream fs(file_name, ios::out);

// 可讀可寫(預設)
fstream fs(file_name, ios::out | ios::out);

還有兩個比較特別的參數 ate 和 app 詳情可以參考這裡。
https://charlottehong.blogspot.com/2017/05/c-iosapp-iosate.html

獲取檔案大小或長度

檔案大小指的其實就是游標最尾端的位置在哪裡,利用剛剛那個移動到最尾端之後回報他的所在位置就可以得到檔案大小了

size_t FileSize(fstream& fs) {
    size_t curr = fs.tellg();
    fs.seekg(0, ios::end);
    size_t len = fs.tellg();
    fs.seekg(curr);
    return len;
};

這是一個封裝好的函式,多寫一個功能是把游標恢復到原本的位置,執行的話像這樣。

fstream fs(file_name, ios::in);
if (!fs.is_open())
    throw runtime_error("Reading error.");

size_t fileSize = FileSize(fs);

size_t 是 unsigned long long 無負號的長長整數。

關閉檔案

結束對檔案的讀取,通常沒用到建議就馬上關了,會出問題的當你的程序不只一個地方讀取檔案的時候,可能會出現同時兩個物件讀同一個檔案,這就不行了。讀取的時候檔案會被占用,不能再有第二個物件來讀取。

fs.close();

順帶一提當物件生命周期結束時會自動關閉檔案的,底下的範例我把讀檔寫在函式內,關檔寫在最後一行其實是可以省略的,因為函式馬上就結束了意味著,物件的生命週期結束了,解構子會自動關閉檔案的,不寫也是合法不會出問題的。(但是我推薦養成習慣寫上,哪天花一個小時Debug最後才發現沒關檔真的會氣死)

最後是讀取到 C 字串

有些人可能一開始寫的代碼是用 char 完成的一時之間實在不轉過來,有兩個解法。一個是直接把 string 轉 char* 就好了,這很容易有內建函式。


string str="chg";
const char* c_str = str.c_str();

如果要直接轉進去 char* 就是不想用 string 那還是有函式可以處理

// 按照所需長度讀取到C字串char*
void ReadFile_buffer(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    char buffer[256]{};
    size_t readSize = sizeof(buffer);
    fs.read(buffer, readSize);
    cout << buffer << endl;
    fs.close();
}

順帶一提這裡的 read() 是可以換成 getline() 的

範例代碼

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// 逐行讀取
void ReadFile_line(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");

    for (string str; getline(fs, str);){
        cout << str << endl;
    } fs.seekg(0, fs.beg);
    fs.close();
}

// 按照空格或跳行讀取
void ReadFile_space(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    for(string str; fs >> str;) {
        cout << str << endl;
    } fs.seekg(0, ios::beg);
    fs.close();
}

// 一次性讀取整個文件
void ReadFile_all(const string file_name){
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    string str(
        (istreambuf_iterator<char>(fs)),
        istreambuf_iterator<char>()
    );
    cout << str << endl;
    fs.close();
}

// 按照所需長度讀取到C字串char*
void ReadFile_buffer(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    char buffer[256]{};
    size_t readSize = sizeof(buffer);
    fs.getline(buffer, readSize);
    cout << buffer << endl;
    fs.close();
}

// 獲取檔案長度
size_t FileSize(fstream& fs) {
    size_t curr = fs.tellg();
    fs.seekg(0, ios::end);
    size_t len = fs.tellg();
    fs.seekg(curr);
    return len;
};

int main(int argc, char const* argv[]) {
    string filename = "a.txt";

    //ReadFile_line(filename);
    //ReadFile_space(filename);
    ReadFile_all(filename);
    //ReadFile_buffer(filename);

    return 0;
}

沒有留言:

張貼留言