Bài 38.4: Bài tập thực hành đọc ghi file trong C++

Chào mừng các bạn trở lại với series blog C++ của chúng ta! Sau khi làm quen với các cấu trúc dữ liệu và giải thuật, đã đến lúc khám phá một khía cạnh thực tếvô cùng quan trọng trong lập trình: Đọc và ghi dữ liệu vào file.

Tại sao lại cần đọc/ghi file? Đơn giản là vì dữ liệu chúng ta làm việc thường cần được lưu trữ bền vững. Khi chương trình kết thúc, dữ liệu trong bộ nhớ sẽ biến mất. Để dữ liệu tồn tại qua các lần chạy chương trình, chúng ta cần lưu nó ra "ngoài" - và file là một nơi phổ biến để làm điều đó.

Trong C++, chúng ta xử lý file thông qua các lớp stream trong thư viện <fstream>. Hãy cùng "bắt tay vào làm" với các bài tập thực hành cụ thể!

1. Chuẩn bị: Thư viện <fstream>

Để làm việc với file, chúng ta cần include header <fstream>. Header này cung cấp các lớp chính:

  • ofstream: Dùng để ghi dữ liệu ra file (output file stream).
  • ifstream: Dùng để đọc dữ liệu từ file (input file stream).
  • fstream: Dùng cho cả đọc và ghi file (file stream).

Các lớp này hoạt động tương tự như coutcin mà chúng ta đã quen thuộc, sử dụng các toán tử <<>>.

2. Bài tập 1: Ghi dữ liệu đơn giản ra file

Hãy bắt đầu với bài tập cơ bản nhất: tạo một file và ghi một dòng văn bản vào đó.

#include <fstream> // Can thiet cho lam viec voi file
#include <iostream> // De in thong bao ra console

int main() {
    // 1. Tao mot doi tuong ofstream (output file stream)
    // Mo file co ten "thongbao.txt" de ghi
    ofstream fileGhi("thongbao.txt");

    // 2. Kiem tra xem file da mo thanh cong chua
    // Day la buoc CUC KY QUAN TRONG!
    if (!fileGhi.is_open()) {
        cerr << "Loi: Khong the mo file de ghi!" << endl;
        return 1; // Bao loi va ket thuc chuong trinh
    }

    // 3. Ghi du lieu vao file su dung toan tu <<
    fileGhi << "Xin chao! Day la noi dung duoc ghi vao file tu C++.\n";
    fileGhi << "Dong nay cung duoc ghi vao file.";

    // 4. Dong file sau khi lam xong
    // Day la thuc hanh tot de giai phong tai nguyen
    fileGhi.close();

    cout << "Da ghi noi dung vao file 'thongbao.txt'." << endl;

    return 0; // Chuong trinh ket thuc thanh cong
}

Giải thích code:

  • Chúng ta tạo một đối tượng ofstream tên là fileGhi và truyền tên file "thongbao.txt" vào constructor. Nếu file này chưa tồn tại, nó sẽ được tạo mới. Nếu đã tồn tại, nội dung cũ sẽ bị xóa sạch trước khi ghi (mở ở chế độ mặc định).
  • Kiểm tra !fileGhi.is_open()bắt buộc. Nếu đường dẫn sai, file không tồn tại và không thể tạo mới, hoặc thiếu quyền ghi, is_open() sẽ trả về false.
  • Chúng ta sử dụng toán tử << để ghi dữ liệu vào fileGhi tương tự như khi dùng cout. \n cũng hoạt động để xuống dòng.
  • Gọi fileGhi.close() để đảm bảo dữ liệu được ghi hoàn toàn xuống đĩa và giải phóng tài nguyên hệ thống liên quan đến file. Lưu ý: Các stream object sẽ tự động đóng file khi chúng bị hủy (ví dụ: khi hàm main kết thúc), nhưng gọi close() tường minh ngay sau khi hoàn thành công việc với file là một thói quen tốt.

Sau khi chạy chương trình này, bạn sẽ thấy một file mới tên là thongbao.txt trong cùng thư mục với file .exe của bạn, chứa hai dòng văn bản trên.

3. Bài tập 2: Đọc dữ liệu từ file

Bây giờ, hãy viết chương trình để đọc nội dung từ file thongbao.txt mà chúng ta vừa tạo.

#include <fstream> // Can thiet cho lam viec voi file
#include <iostream> // De in ra console
#include <string> // Can thiet cho string va getline

int main() {
    // 1. Tao mot doi tuong ifstream (input file stream)
    // Mo file "thongbao.txt" de doc
    ifstream fileDoc("thongbao.txt");

    // 2. Kiem tra xem file da mo thanh cong chua
    if (!fileDoc.is_open()) {
        cerr << "Loi: Khong the mo file de doc!" << endl;
        return 1; // Bao loi
    }

    // 3. Doc du lieu tu file
    string dong; // Bien de luu tung dong duoc doc

    cout << "Noi dung doc tu file 'thongbao.txt':" << endl;

    // Doc tung dong tu file cho den khi gap cuoi file (EOF)
    while (getline(fileDoc, dong)) {
        // In dong vua doc ra console
        cout << dong << endl;
    }

    // 4. Dong file sau khi lam xong
    fileDoc.close();

    return 0; // Chuong trinh ket thuc thanh cong
}

Giải thích code:

  • Chúng ta tạo một đối tượng ifstream tên là fileDoc và mở file "thongbao.txt" ở chế độ đọc.
  • Lại, kiểm tra is_open()cần thiết.
  • Chúng ta sử dụng vòng lặp while và hàm getline(stream, string). Hàm này đọc một dòng văn bản từ stream (fileDoc) cho đến khi gặp ký tự xuống dòng (\n) hoặc cuối file, và lưu nó vào biến dong kiểu string.
  • getline trả về stream (fileDoc). Khi getline không thể đọc thêm dữ liệu (ví dụ: đã đến cuối file hoặc gặp lỗi), stream sẽ ở trạng thái lỗi, và biểu thức getline(fileDoc, dong) trong while sẽ trở thành false, kết thúc vòng lặp.
  • Nội dung của mỗi dòng đọc được sẽ được in ra console.
  • Đóng file bằng fileDoc.close().

Chạy chương trình này sẽ in ra nội dung của file thongbao.txt lên màn hình console của bạn.

4. Bài tập 3: Ghi dữ liệu vào cuối file (Append)

Nếu bạn muốn thêm nội dung vào file mà không xóa nội dung cũ, bạn cần mở file ở chế độ append (thêm vào).

#include <fstream> // Can thiet cho lam viec voi file
#include <iostream> // De in ra console

int main() {
    // 1. Tao mot doi tuong ofstream
    // Mo file "thongbao.txt" o che do THEM VAO (ios::app)
    ofstream fileGhiThem("thongbao.txt", ios::app);

    // 2. Kiem tra mo file
    if (!fileGhiThem.is_open()) {
        cerr << "Loi: Khong the mo file de them vao!" << endl;
        return 1;
    }

    // 3. Ghi du lieu moi vao cuoi file
    fileGhiThem << "\n"; // Them mot dong trong truoc khi them noi dung moi (tuy chon)
    fileGhiThem << "Dong nay duoc them vao file sau khi chay lan 2.";

    // 4. Dong file
    fileGhiThem.close();

    cout << "Da them noi dung vao cuoi file 'thongbao.txt'." << endl;

    return 0;
}

Giải thích code:

  • Điểm khác biệt là khi mở file, chúng ta truyền thêm tham số thứ hai: ios::app. Tham số này là một flag (cờ) chỉ định chế độ mở file. ios::app có nghĩa là "append" - di chuyển con trỏ ghi đến cuối file trước mỗi thao tác ghi.
  • Các bước kiểm tra lỗi và đóng file vẫn tương tự.

Nếu bạn chạy chương trình Ghi file (Bài tập 1) trước, sau đó chạy chương trình Đọc file (Bài tập 2), bạn sẽ thấy nội dung ban đầu. Sau đó, chạy chương trình Ghi thêm (Bài tập 3), và chạy lại chương trình Đọc file (Bài tập 2), bạn sẽ thấy nội dung mới đã được thêm vào cuối file.

5. Bài tập 4: Đọc dữ liệu khác loại (số, chữ)

File không chỉ chứa toàn bộ văn bản. Chúng ta có thể lưu trữ và đọc các loại dữ liệu khác nhau, ví dụ như số. Toán tử >> hoạt động với file stream tương tự như với cin. Nó sẽ đọc dữ liệu cho đến khi gặp khoảng trắng (space, tab, newline) và cố gắng chuyển đổi nó sang kiểu dữ liệu của biến nhận.

Hãy tạo một file chứa các số và sau đó đọc chúng vào chương trình.

Bước 1: Tạo file dữ liệu (có thể làm thủ công hoặc bằng code)

Tạo một file tên dulieu.txt với nội dung sau:

10 25 3.14
-5
100.5 99

Bước 2: Code đọc dữ liệu số từ file

#include <fstream> // Cho ifstream
#include <iostream> // Cho console output
#include <string> // De kiem tra trang thai loi (tuy chon)
#include <limits> // Can thiet neu dung ignore de bo qua du lieu loi

int main() {
    ifstream fileDoc("dulieu.txt");

    if (!fileDoc.is_open()) {
        cerr << "Loi: Khong the mo file 'dulieu.txt'!" << endl;
        return 1;
    }

    double so;
    cout << "Cac so doc tu file 'dulieu.txt':" << endl;

    // Doc tung "tu" du lieu va co gang chuyen thanh double
    while (fileDoc >> so) {
        cout << so << endl;
    }

    // Kiem tra xem vong lap ket thuc do het file hay do gap loi doc
    if (fileDoc.eof()) {
        cout << "\nDa doc het file." << endl;
    } else if (fileDoc.fail()) {
        // Neu fail() la true va eof() la false, nghia la gap du lieu khong hop le
        cerr << "\nLoi khi doc du lieu (co the file chua ky tu khong phai so)." << endl;
        // De tiep tuc doc sau loi:
        // fileDoc.clear(); // Xoa cac co trang thai loi
        // fileDoc.ignore(numeric_limits<streamsize>::max(), '\n'); // Bo qua phan con lai cua dong hien tai
    }


    fileDoc.close();

    return 0;
}

Giải thích code:

  • Chúng ta sử dụng ifstream để mở file.
  • Vòng lặp while (fileDoc >> so) sẽ đọc từng mục được phân tách bằng khoảng trắng (hoặc xuống dòng) từ fileDoc và cố gắng gán nó vào biến so (kiểu double).
  • Toán tử >> sẽ tự động bỏ qua các ký tự trắng trước khi đọc giá trị.
  • Vòng lặp kết thúc khi không thể đọc được giá trị kiểu double nữa (ví dụ: hết file hoặc gặp ký tự không phải số).
  • Chúng ta kiểm tra fileDoc.eof() để biết có phải đã đọc hết file hay không, và fileDoc.fail() để phát hiện lỗi chuyển đổi kiểu dữ liệu trong quá trình đọc.

Chạy chương trình này sẽ in ra từng số từ file dulieu.txt, mỗi số trên một dòng mới.

6. Một điểm quan trọng cần nhớ: RAII

Trong C++, các stream object tuân thủ nguyên tắc RAII (Resource Acquisition Is Initialization). Điều này có nghĩa là khi một đối tượng stream được tạo (khởi tạo), nó sẽ "acquire" (thu nhận) tài nguyên cần thiết (mở file). Khi đối tượng stream bị hủy (ví dụ: khi nó ra khỏi phạm vi hoạt động, như kết thúc hàm main), destructor của nó sẽ tự động "release" (giải phóng) tài nguyên đó (đóng file).

#include <fstream>
#include <iostream>

void ghiDuLieuTamThoi(const string& tenFile) {
    ofstream file(tenFile); // Tai nguyen (file) duoc mo khi doi tuong 'file' duoc tao

    if (!file.is_open()) {
        cerr << "Khong the mo file: " << tenFile << endl;
        return; // Neu mo file that bai, thoat ham som
    }

    file << "Day la du lieu tam thoi.";

    // Khi ham ket thuc (dat den day HOAC thoat som o tren),
    // doi tuong 'file' se ra khoi pham vi va bi huy.
    // Destructor cua ofstream se TU DONG dong file.
    cout << "Da ghi du lieu vao " << tenFile << ". File se tu dong dong khi thoat ham." << endl;

} // <--- Tai day, doi tuong 'file' bi huy va file tu dong dong

int main() {
    ghiDuLieuTamThoi("file_raii.txt");
    // File "file_raii.txt" da duoc dong truoc khi den dong nay.

    return 0;
}

Mặc dù stream sẽ tự động đóng, việc gọi close() tường minh đôi khi vẫn hữu ích nếu bạn muốn đóng file ngay lập tức sau khi hoàn thành công việc với nó, giải phóng tài nguyên sớm hơn, đặc biệt trong các chương trình lớn xử lý nhiều file hoặc cần xử lý lỗi phức tạp hơn.

Tóm lại

Bài tập thực hành đọc ghi file trong C++ sử dụng thư viện <fstream> là nền tảng để xây dựng các ứng dụng có khả năng lưu trữ và truy xuất dữ liệu. Chúng ta đã thấy cách:

  • Sử dụng ofstream để ghi file.
  • Sử dụng ifstream để đọc file.
  • Kiểm tra xem file có mở thành công hay không (is_open()).
  • Đóng file (close()), và hiểu về RAII.
  • Mở file ở chế độ append (ios::app).
  • Đọc dữ liệu theo dòng (getline).
  • Đọc dữ liệu theo token (số, từ) sử dụng toán tử >>.
  • Kiểm tra trạng thái stream (eof(), fail()).

Đây chỉ là khởi đầu! <fstream> còn cung cấp nhiều tính năng nâng cao hơn như làm việc với file nhị phân, di chuyển con trỏ file, v.v. Tuy nhiên, với những kiến thức cơ bản này, bạn đã có thể bắt đầu xây dựng các chương trình tương tác với file dữ liệu rồi đó.

Comments

There are no comments at the moment.