Skip to content

📁 Đọc/Ghi file trong C++

📖 Giới thiệu

Thao tác với file là một kỹ năng quan trọng trong lập trình, cho phép chương trình lưu trữ dữ liệu vĩnh viễn, đọc cấu hình, xử lý dữ liệu lớn và trao đổi thông tin giữa các ứng dụng. Trong C++, chúng ta sử dụng thư viện <fstream> để thực hiện các thao tác này.

Tầm quan trọng của File I/O:

  • Lưu trữ dữ liệu: Lưu trạng thái game, cấu hình ứng dụng
  • Xử lý dữ liệu lớn: Phân tích log files, xử lý CSV
  • Ứng dụng thực tế: Hệ thống quản lý sinh viên, kế toán
  • Trao đổi dữ liệu: Import/Export giữa các hệ thống

Ứng dụng thực tiễn:

  • Hệ thống quản lý thư viện lưu thông tin sách
  • Game lưu progress và high scores
  • Ứng dụng kế toán xuất báo cáo Excel
  • Hệ thống log ghi lại hoạt động của server

🔧 Cú pháp

Các header cần thiết

cpp
#include <fstream>  // File stream
#include <iostream> // Input/output
#include <string>   // String operations

Các lớp file stream

cpp
// Đọc file
ifstream input_file;        // Input file stream
ifstream file("data.txt");  // Khởi tạo trực tiếp

// Ghi file
ofstream output_file;       // Output file stream
ofstream file("output.txt"); // Khởi tạo trực tiếp

// Đọc và ghi
fstream file;               // Bidirectional file stream
fstream file("data.txt", ios::in | ios::out);

Mở và đóng file

cpp
// Mở file
ifstream file;
file.open("filename.txt");

// Kiểm tra mở file thành công
if (file.is_open()) {
    // Thao tác với file
    file.close();
} else {
    cout << "Không thể mở file!" << endl;
}

// Hoặc kiểm tra ngắn gọn
if (!file) {
    cout << "Lỗi mở file!" << endl;
}

Các mode mở file

cpp
// Các mode phổ biến
ios::in       // Đọc (mặc định cho ifstream)
ios::out      // Ghi (mặc định cho ofstream)
ios::app      // Ghi thêm vào cuối file
ios::ate      // Đặt con trỏ ở cuối file
ios::trunc    // Xóa nội dung file hiện tại
ios::binary   // Mode binary

// Kết hợp các mode
ofstream file("data.txt", ios::out | ios::app);
fstream file("data.txt", ios::in | ios::out | ios::binary);

🔬 Phân tích & Giải thích chi tiết

Hiểu về File Streams

File stream hoạt động như một "đường ống" kết nối chương trình với file trên đĩa cứng. Khái niệm này tương tự như cách cincout kết nối với keyboard và màn hình.

Quy trình làm việc với file:

  1. Mở file: Tạo kết nối giữa chương trình và file
  2. Kiểm tra: Đảm bảo file được mở thành công
  3. Thao tác: Đọc hoặc ghi dữ liệu
  4. Đóng file: Giải phóng tài nguyên

Khi nào sử dụng từng loại stream?

ifstream - Chỉ đọc:

cpp
// Đọc cấu hình, đọc dữ liệu input
ifstream config("settings.cfg");
ifstream data("students.txt");

ofstream - Chỉ ghi:

cpp
// Ghi log, xuất báo cáo
ofstream log("app.log");
ofstream report("monthly_report.csv");

fstream - Đọc và ghi:

cpp
// Database files, game save files
fstream database("game_save.dat", ios::in | ios::out | ios::binary);

Text vs Binary Mode

Text Mode (mặc định):

  • Tự động chuyển đổi line endings
  • Thích hợp cho văn bản, CSV, JSON
  • Dễ đọc bằng text editor

Binary Mode:

  • Không chuyển đổi dữ liệu
  • Thích hợp cho hình ảnh, âm thanh, dữ liệu nén
  • Hiệu suất cao hơn

💻 Ví dụ minh họa

Ví dụ 1: Hệ thống quản lý điểm sinh viên

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

struct Student {
    string name;
    int id;
    double grade;
};

// Ghi danh sách sinh viên vào file
void saveStudents() {
    ofstream file("students.txt");
    
    if (!file) {
        cout << "Không thể tạo file!" << endl;
        return;
    }
    
    int n;
    cout << "Nhập số lượng sinh viên: ";
    cin >> n;
    
    for (int i = 0; i < n; i++) {
        Student student;
        cout << "Sinh viên " << (i + 1) << ":" << endl;
        cout << "Tên: ";
        cin.ignore();
        getline(cin, student.name);
        cout << "Mã SV: ";
        cin >> student.id;
        cout << "Điểm: ";
        cin >> student.grade;
        
        // Ghi vào file theo format: name|id|grade
        file << student.name << "|" << student.id << "|" 
             << student.grade << endl;
    }
    
    file.close();
    cout << "Đã lưu thông tin sinh viên!" << endl;
}

// Đọc và hiển thị danh sách sinh viên
void loadStudents() {
    ifstream file("students.txt");
    
    if (!file) {
        cout << "Không tìm thấy file dữ liệu!" << endl;
        return;
    }
    
    cout << left << setw(20) << "Tên" 
         << setw(10) << "Mã SV" 
         << setw(10) << "Điểm" << endl;
    cout << string(40, '-') << endl;
    
    string line;
    while (getline(file, line)) {
        // Parse dữ liệu từ line
        size_t pos1 = line.find('|');
        size_t pos2 = line.find('|', pos1 + 1);
        
        if (pos1 != string::npos && pos2 != string::npos) {
            string name = line.substr(0, pos1);
            int id = stoi(line.substr(pos1 + 1, pos2 - pos1 - 1));
            double grade = stod(line.substr(pos2 + 1));
            
            cout << left << setw(20) << name 
                 << setw(10) << id 
                 << setw(10) << fixed << setprecision(2) << grade << endl;
        }
    }
    
    file.close();
}

int main() {
    int choice;
    
    do {
        cout << "\n=== QUẢN LÝ ĐIỂM SINH VIÊN ===" << endl;
        cout << "1. Nhập dữ liệu sinh viên" << endl;
        cout << "2. Hiển thị danh sách" << endl;
        cout << "0. Thoát" << endl;
        cout << "Chọn: ";
        cin >> choice;
        
        switch (choice) {
            case 1:
                saveStudents();
                break;
            case 2:
                loadStudents();
                break;
            case 0:
                cout << "Tạm biệt!" << endl;
                break;
            default:
                cout << "Lựa chọn không hợp lệ!" << endl;
        }
    } while (choice != 0);
    
    return 0;
}

Kết quả chạy:

=== QUẢN LÝ ĐIỂM SINH VIÊN ===
1. Nhập dữ liệu sinh viên
2. Hiển thị danh sách
0. Thoát
Chọn: 1

Nhập số lượng sinh viên: 2
Sinh viên 1:
Tên: Nguyễn Văn An
Mã SV: 20210001
Điểm: 8.5

Sinh viên 2:
Tên: Trần Thị Bình
Mã SV: 20210002
Điểm: 9.0

Đã lưu thông tin sinh viên!

Ví dụ 2: Ứng dụng đếm từ trong văn bản

cpp
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <sstream>
#include <algorithm>
using namespace std;

// Hàm làm sạch từ (loại bỏ dấu câu)
string cleanWord(string word) {
    // Loại bỏ dấu câu ở đầu và cuối
    while (!word.empty() && !isalnum(word.front())) {
        word.erase(0, 1);
    }
    while (!word.empty() && !isalnum(word.back())) {
        word.pop_back();
    }
    
    // Chuyển về chữ thường
    transform(word.begin(), word.end(), word.begin(), ::tolower);
    
    return word;
}

void analyzeText() {
    string filename;
    cout << "Nhập tên file cần phân tích: ";
    cin >> filename;
    
    ifstream file(filename);
    if (!file) {
        cout << "Không thể mở file: " << filename << endl;
        return;
    }
    
    map<string, int> wordCount;
    string line;
    int totalWords = 0;
    int totalLines = 0;
    
    // Đọc từng dòng
    while (getline(file, line)) {
        totalLines++;
        stringstream ss(line);
        string word;
        
        // Đọc từng từ trong dòng
        while (ss >> word) {
            word = cleanWord(word);
            if (!word.empty()) {
                wordCount[word]++;
                totalWords++;
            }
        }
    }
    
    file.close();
    
    // Xuất thống kê ra file
    ofstream report("word_analysis.txt");
    report << "=== PHÂN TÍCH VĂN BẢN ===" << endl;
    report << "File: " << filename << endl;
    report << "Tổng số dòng: " << totalLines << endl;
    report << "Tổng số từ: " << totalWords << endl;
    report << "Số từ duy nhất: " << wordCount.size() << endl;
    report << "\n=== TOP 10 TỪ XUẤT HIỆN NHIỀU NHẤT ===" << endl;
    
    // Sắp xếp theo tần suất
    vector<pair<int, string>> sortedWords;
    for (auto& p : wordCount) {
        sortedWords.push_back({p.second, p.first});
    }
    sort(sortedWords.rbegin(), sortedWords.rend());
    
    // Hiển thị top 10
    for (int i = 0; i < min(10, (int)sortedWords.size()); i++) {
        report << (i + 1) << ". \"" << sortedWords[i].second 
               << "\" - " << sortedWords[i].first << " lần" << endl;
        
        cout << (i + 1) << ". \"" << sortedWords[i].second 
             << "\" - " << sortedWords[i].first << " lần" << endl;
    }
    
    report.close();
    cout << "\nKết quả đã được lưu vào word_analysis.txt" << endl;
}

int main() {
    analyzeText();
    return 0;
}

Ví dụ 3: Backup và restore dữ liệu binary

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

struct GameData {
    char playerName[50];
    int level;
    int score;
    int lives;
    double playtime;
};

// Lưu game data dưới dạng binary
void saveGameBinary() {
    GameData player;
    
    cout << "Nhập tên người chơi: ";
    cin.ignore();
    cin.getline(player.playerName, 50);
    cout << "Level: ";
    cin >> player.level;
    cout << "Điểm: ";
    cin >> player.score;
    cout << "Số mạng: ";
    cin >> player.lives;
    cout << "Thời gian chơi (giờ): ";
    cin >> player.playtime;
    
    // Ghi binary file
    ofstream file("savegame.dat", ios::binary);
    if (file) {
        file.write(reinterpret_cast<char*>(&player), sizeof(GameData));
        file.close();
        cout << "Game đã được lưu!" << endl;
    } else {
        cout << "Lỗi lưu game!" << endl;
    }
}

// Đọc game data từ binary file
void loadGameBinary() {
    GameData player;
    
    ifstream file("savegame.dat", ios::binary);
    if (file) {
        file.read(reinterpret_cast<char*>(&player), sizeof(GameData));
        file.close();
        
        cout << "\n=== THÔNG TIN GAME ===" << endl;
        cout << "Tên: " << player.playerName << endl;
        cout << "Level: " << player.level << endl;
        cout << "Điểm: " << player.score << endl;
        cout << "Số mạng: " << player.lives << endl;
        cout << "Thời gian: " << player.playtime << " giờ" << endl;
    } else {
        cout << "Không tìm thấy file save game!" << endl;
    }
}

int main() {
    int choice;
    
    cout << "1. Lưu game\n2. Load game\nChọn: ";
    cin >> choice;
    
    if (choice == 1) {
        saveGameBinary();
    } else if (choice == 2) {
        loadGameBinary();
    }
    
    return 0;
}

🏋️ Thực hành

Bài tập 1: Sổ thu chi cá nhân

Viết chương trình quản lý thu chi với các chức năng:

  • Thêm giao dịch (thu/chi, số tiền, mô tả, ngày)
  • Hiển thị lịch sử giao dịch
  • Tính tổng thu, tổng chi, số dư
  • Xuất báo cáo theo tháng

Gợi ý cấu trúc file:

2024-01-15|Thu|5000000|Lương tháng 1
2024-01-16|Chi|500000|Tiền điện
2024-01-20|Chi|200000|Ăn uống

Bài tập 2: Quản lý thư viện sách

Tạo hệ thống quản lý thư viện với:

  • Thông tin sách: mã sách, tên, tác giả, năm xuất bản, số lượng
  • Thêm/sửa/xóa sách
  • Tìm kiếm sách theo tên hoặc tác giả
  • Thống kê số lượng sách theo thể loại

Bài tập 3: Phân tích log web server

Viết chương trình phân tích file log với format:

2024-01-15 10:30:15 GET /homepage 200 1234
2024-01-15 10:31:22 POST /login 401 567

Yêu cầu:

  • Đếm số request theo từng method (GET, POST, PUT, DELETE)
  • Thống kê status code (200, 404, 500, etc.)
  • Tìm trang được truy cập nhiều nhất
  • Tính tổng bandwidth sử dụng

Bài tập 4: Converter CSV sang JSON

Viết chương trình chuyển đổi file CSV sang JSON:

Input (students.csv):

Name,Age,Grade,City
An,20,8.5,Hanoi
Binh,21,9.0,HCMC

Output (students.json):

json
[
  {"Name": "An", "Age": 20, "Grade": 8.5, "City": "Hanoi"},
  {"Name": "Binh", "Age": 21, "Grade": 9.0, "City": "HCMC"}
]

Lời giải chi tiết

Bài tập 1 - Sổ thu chi:

cpp
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iomanip>
using namespace std;

struct Transaction {
    string date;
    string type;  // "Thu" hoặc "Chi"
    double amount;
    string description;
};

void addTransaction() {
    ofstream file("transactions.txt", ios::app);
    
    Transaction t;
    cout << "Ngày (YYYY-MM-DD): ";
    cin >> t.date;
    cout << "Loại (Thu/Chi): ";
    cin >> t.type;
    cout << "Số tiền: ";
    cin >> t.amount;
    cout << "Mô tả: ";
    cin.ignore();
    getline(cin, t.description);
    
    file << t.date << "|" << t.type << "|" << t.amount << "|" << t.description << endl;
    file.close();
    
    cout << "Đã thêm giao dịch!" << endl;
}

void showTransactions() {
    ifstream file("transactions.txt");
    if (!file) {
        cout << "Chưa có giao dịch nào!" << endl;
        return;
    }
    
    cout << left << setw(12) << "Ngày" 
         << setw(8) << "Loại" 
         << setw(15) << "Số tiền" 
         << "Mô tả" << endl;
    cout << string(60, '-') << endl;
    
    string line;
    double totalIncome = 0, totalExpense = 0;
    
    while (getline(file, line)) {
        size_t pos1 = line.find('|');
        size_t pos2 = line.find('|', pos1 + 1);
        size_t pos3 = line.find('|', pos2 + 1);
        
        string date = line.substr(0, pos1);
        string type = line.substr(pos1 + 1, pos2 - pos1 - 1);
        double amount = stod(line.substr(pos2 + 1, pos3 - pos2 - 1));
        string desc = line.substr(pos3 + 1);
        
        cout << left << setw(12) << date 
             << setw(8) << type 
             << setw(15) << fixed << setprecision(0) << amount 
             << desc << endl;
        
        if (type == "Thu") {
            totalIncome += amount;
        } else {
            totalExpense += amount;
        }
    }
    
    cout << string(60, '-') << endl;
    cout << "Tổng thu: " << totalIncome << endl;
    cout << "Tổng chi: " << totalExpense << endl;
    cout << "Số dư: " << (totalIncome - totalExpense) << endl;
    
    file.close();
}

📋 Tóm tắt

Các điểm quan trọng

Thư viện và lớp cần nhớ:

  • #include <fstream> - Thư viện file I/O
  • ifstream - Đọc file
  • ofstream - Ghi file
  • fstream - Đọc và ghi

Các bước làm việc với file:

  1. Mở file với mode phù hợp
  2. Kiểm tra file mở thành công
  3. Thực hiện đọc/ghi dữ liệu
  4. Đóng file khi hoàn thành

Mode file phổ biến:

  • ios::in - Đọc
  • ios::out - Ghi
  • ios::app - Ghi thêm
  • ios::binary - Mode nhị phân

Best Practices

  • Luôn kiểm tra: File có mở thành công không
  • Đóng file: Sử dụng close() hoặc để destructor tự động đóng
  • Error handling: Xử lý trường hợp file không tồn tại
  • Binary vs Text: Chọn mode phù hợp với loại dữ liệu
  • Backup data: Tạo backup trước khi ghi đè file quan trọng

Chuẩn bị cho bài tiếp theo

Bài học tiếp theo sẽ tìm hiểu về Lambda Expressions - một tính năng mạnh mẽ của C++11:

  • Anonymous functions: Hàm không tên ngắn gọn
  • Capture lists: Cách lambda truy cập biến bên ngoài
  • Functional programming: Sử dụng với STL algorithms
  • Modern C++: Viết code ngắn gọn và hiệu quả hơn

File I/O là nền tảng cho việc xử lý dữ liệu trong ứng dụng thực tế. Hãy luyện tập nhiều với các bài tập để thành thạo!

Khóa học C++ miễn phí