📁 Đọ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
#include <fstream> // File stream
#include <iostream> // Input/output
#include <string> // String operationsCác lớp file stream
// Đọ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
// 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
// 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 cin và cout kết nối với keyboard và màn hình.
Quy trình làm việc với file:
- Mở file: Tạo kết nối giữa chương trình và file
- Kiểm tra: Đảm bảo file được mở thành công
- Thao tác: Đọc hoặc ghi dữ liệu
- Đóng file: Giải phóng tài nguyên
Khi nào sử dụng từng loại stream?
ifstream - Chỉ đọc:
// Đọc cấu hình, đọc dữ liệu input
ifstream config("settings.cfg");
ifstream data("students.txt");ofstream - Chỉ ghi:
// Ghi log, xuất báo cáo
ofstream log("app.log");
ofstream report("monthly_report.csv");fstream - Đọc và ghi:
// 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
#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
#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
#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ốngBà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 567Yê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,HCMCOutput (students.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:
#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/Oifstream- Đọc fileofstream- Ghi filefstream- Đọc và ghi
Các bước làm việc với file:
- Mở file với mode phù hợp
- Kiểm tra file mở thành công
- Thực hiện đọc/ghi dữ liệu
- Đóng file khi hoàn thành
Mode file phổ biến:
ios::in- Đọcios::out- Ghiios::app- Ghi thêmios::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!