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

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à 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ư cout
và cin
mà chúng ta đã quen thuộc, sử dụng các toán tử <<
và >>
.
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()
là 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àofileGhi
tương tự như khi dùngcout
.\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àmmain
kết thúc), nhưng gọiclose()
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()
là cần thiết. - Chúng ta sử dụng vòng lặp
while
và hàmgetline(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ếndong
kiểustring
. getline
trả về stream (fileDoc
). Khigetline
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ứcgetline(fileDoc, dong)
trongwhile
sẽ trở thànhfalse
, 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ếnso
(kiểudouble
). - 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