Bài 37.3: Ghi file trong C++

Bài 37.3: Ghi file trong C++
Chào mừng các bạn đến với hành trình khám phá thế giới C++ cùng FullhouseDev! Sau khi đã thành thạo việc nhập và xuất dữ liệu trên màn hình console, đã đến lúc chúng ta nâng cấp khả năng của chương trình bằng cách cho phép nó lưu trữ dữ liệu một cách lâu dài – tức là, ghi dữ liệu vào các tập tin (file). Đây là một kỹ năng cực kỳ quan trọng, mở ra cánh cửa cho việc xây dựng các ứng dụng thực tế hơn, từ việc lưu cài đặt, ghi lại nhật ký hoạt động (log), đến xử lý các tập dữ liệu lớn.
Trong bài viết này, chúng ta sẽ tập trung vào việc ghi dữ liệu vào file văn bản (text file) trong C++. Khái niệm cơ bản rất giống với việc sử dụng cout
để in ra màn hình, nhưng thay vì gửi dữ liệu đến console, chúng ta sẽ gửi nó đến một đối tượng đại diện cho file trên ổ đĩa.
Tại sao phải ghi file?
Hãy tưởng tượng bạn viết một chương trình quản lý danh sách sinh viên. Nếu dữ liệu chỉ tồn tại trong bộ nhớ khi chương trình đang chạy, mọi thông tin sẽ biến mất ngay khi bạn đóng chương trình. Thật bất tiện phải không? Ghi file giúp chúng ta:
- Lưu trữ lâu dài: Dữ liệu tồn tại ngay cả khi chương trình kết thúc.
- Chia sẻ dữ liệu: File có thể được mở và đọc bởi các chương trình khác hoặc thậm chí là con người (đối với file văn bản).
- Xử lý lượng lớn dữ liệu: Dễ dàng làm việc với dữ liệu không thể chứa hết trong bộ nhớ.
Thư viện cần thiết: <fstream>
và Đối tượng ofstream
Để làm việc với file trong C++, chúng ta cần sử dụng thư viện <fstream>
(file stream). Thư viện này cung cấp các lớp để thao tác với file như là các luồng (stream) dữ liệu, tương tự như cin
và cout
.
Đối tượng chính mà chúng ta sẽ sử dụng để ghi file là ofstream
. Chữ 'of' trong ofstream
là viết tắt của "output file", nghĩa là luồng dữ liệu đầu ra đến một file.
Các bước cơ bản để ghi dữ liệu vào file là:
- Khai báo một đối tượng
ofstream
. - Mở file (liên kết đối tượng
ofstream
với một file cụ thể trên ổ đĩa). - Kiểm tra xem file có mở thành công không.
- Ghi dữ liệu vào file bằng cách sử dụng toán tử
<<
. - Đóng file.
Nghe có vẻ đơn giản đúng không? Hãy đi sâu vào từng bước và xem các ví dụ cụ thể.
Bước 1 & 2: Khai báo và Mở file
Bạn có thể khai báo và mở file cùng lúc ngay khi tạo đối tượng ofstream
, hoặc khai báo trước rồi mở sau.
Cách 1: Mở file bằng constructor (phổ biến nhất)
#include <fstream>
#include <iostream>
int main() {
ofstream f("output.txt");
return 0;
}
Khi bạn tạo ofstream outFile("output.txt");
, chương trình sẽ cố gắng mở hoặc tạo một file có tên output.txt
trong cùng thư mục với file thực thi của chương trình (hoặc theo đường dẫn đầy đủ nếu bạn cung cấp).
- Nếu file
output.txt
chưa tồn tại, nó sẽ được tạo mới. - Nếu file
output.txt
đã tồn tại, toàn bộ nội dung cũ sẽ bị xóa sạch trước khi ghi dữ liệu mới vào (đây là chế độ mặc định -ios::out
).
Cách 2: Mở file bằng phương thức open()
Cách này hữu ích khi bạn cần làm việc với nhiều file hoặc muốn trì hoãn việc mở file.
#include <fstream>
#include <iostream>
int main() {
ofstream f;
f.open("another_output.txt");
return 0;
}
Bước 3: Kiểm tra xem file có mở thành công không
Đây là bước cực kỳ quan trọng mà nhiều người mới bắt đầu hay bỏ qua. Việc mở file có thể thất bại vì nhiều lý do (ví dụ: không đủ quyền ghi, tên file không hợp lệ, ổ đĩa đầy...). Nếu bạn cố gắng ghi vào một file không được mở thành công, chương trình có thể gặp lỗi hoặc hành vi không mong muốn.
Cách đơn giản nhất để kiểm tra là sử dụng toán tử !
hoặc phương thức is_open()
.
#include <fstream>
#include <iostream>
int main() {
ofstream f("output.txt");
if (!f.is_open()) {
cerr << "Lỗi: Không thể mở file." << endl;
return 1;
}
cout << "File output.txt đã mở." << endl;
f.close();
return 0;
}
Output:
File output.txt đã mở.
Bước 4: Ghi dữ liệu vào file
Khi file đã được mở thành công, bạn có thể sử dụng toán tử <<
để ghi dữ liệu vào file, giống hệt như cách bạn sử dụng với cout
. Bạn có thể ghi các kiểu dữ liệu khác nhau như chuỗi (string
), số nguyên (int
), số thực (double
), v.v.
#include <fstream>
#include <iostream>
#include <string>
int main() {
ofstream f("my_data.txt");
if (!f.is_open()) {
cerr << "Lỗi: Không thể mở file my_data.txt." << endl;
return 1;
}
f << "Xin chào từ C++!" << endl;
int s = 100;
f << "Gia tri so: " << s << endl;
double p = 3.14159;
f << "Gia tri Pi: " << p << endl;
string t = "Alice";
int u = 25;
f << "Ten: " << t << ", Tuoi: " << u << endl;
f.close();
cout << "Da ghi du lieu vao file my_data.txt." << endl;
return 0;
}
Output:
Da ghi du lieu vao file my_data.txt.
Giải thích:
outFile << "..."
hoạt động tương tựcout << "..."
.- Toán tử
<<
được quá tải (overloaded) để xử lý nhiều kiểu dữ liệu khác nhau. endl
không chỉ chèn ký tự xuống dòng (\n
) mà còn thực hiện thao tác flush luồng đệm (buffer). Điều này đảm bảo rằng dữ liệu bạn vừa ghi sẽ được đẩy ngay lập tức từ bộ nhớ đệm của chương trình ra file trên ổ đĩa. Điều này quan trọng nếu bạn muốn đảm bảo dữ liệu được lưu ngay lập tức, nhưng có thể làm chậm chương trình nếu thực hiện quá thường xuyên. Chỉ chèn\n
thường hiệu quả hơn nếu không cần flush ngay.
Bước 5: Đóng file
Sau khi hoàn thành việc ghi dữ liệu, bạn phải đóng file. Việc đóng file có một số mục đích quan trọng:
- Đẩy dữ liệu còn lại: Đảm bảo mọi dữ liệu còn sót lại trong bộ nhớ đệm của luồng được ghi ra file trên ổ đĩa.
- Giải phóng tài nguyên hệ thống: Hệ điều hành giới hạn số lượng file mà một chương trình có thể mở cùng lúc. Đóng file sẽ giải phóng tài nguyên này.
- Hoàn tất quá trình ghi: Một số hệ điều hành có thể không cập nhật file hoàn chỉnh cho đến khi nó được đóng.
Bạn đóng file bằng cách gọi phương thức close()
của đối tượng ofstream
.
#include <fstream>
#include <iostream>
int main() {
ofstream f("temp.txt");
if (!f.is_open()) {
cerr << "Lỗi: Không thể mở file temp.txt." << endl;
return 1;
}
f << "Day la du lieu tam thoi." << endl;
f.close();
cout << "Da ghi va dong file temp.txt." << endl;
return 0;
}
Output:
Da ghi va dong file temp.txt.
Lưu ý quan trọng: Đối tượng ofstream
được thiết kế theo nguyên tắc RAII (Resource Acquisition Is Initialization). Điều này có nghĩa là khi đối tượng outFile
bị hủy (ví dụ: khi nó ra khỏi phạm vi hàm main
), destructor của nó sẽ tự động gọi close()
. Do đó, trong nhiều trường hợp đơn giản, bạn không cần gọi close()
một cách tường minh. Tuy nhiên, việc gọi close()
tường minh đôi khi giúp bạn kiểm tra xem việc đóng file có thành công không (mặc dù việc kiểm tra lỗi khi đóng file ít phổ biến hơn so với khi mở file) và giúp mã nguồn rõ ràng hơn về thời điểm tài nguyên file được giải phóng.
Ghi dữ liệu vào cuối file (Append Mode)
Như đã đề cập, chế độ mặc định khi mở file bằng ofstream
là ghi đè (xóa sạch nội dung cũ). Nếu bạn muốn thêm dữ liệu vào cuối file mà không làm mất nội dung đã có, bạn cần mở file ở chế độ nối thêm (append mode) bằng cách sử dụng cờ ios::app
.
Bạn truyền cờ này làm tham số thứ hai cho constructor hoặc phương thức open()
.
#include <fstream>
#include <iostream>
#include <string>
#include <chrono>
#include <ctime>
int main() {
ofstream f("application.log", ios::app);
if (!f.is_open()) {
cerr << "Loi: Khong mo duoc file application.log." << endl;
return 1;
}
auto tg = chrono::system_clock::now();
time_t t = chrono::system_clock::to_time_t(tg);
char str_tg[100];
strftime(str_tg, sizeof(str_tg), "%Y-%m-%d %H:%M:%S", localtime(&t));
f << "[" << str_tg << "] Ung dung da chay." << endl;
f << "[" << str_tg << "] Ghi them mot dong log." << endl;
f.close();
cout << "Da ghi log vao file application.log." << endl;
return 0;
}
Output:
Da ghi log vao file application.log.
Giải thích:
ofstream logFile("application.log", ios::app);
: Tham số thứ haiios::app
yêu cầu luồng file mở file ở chế độ nối thêm. Con trỏ ghi sẽ được đặt ở cuối file ngay khi mở.- Nếu file
application.log
chưa tồn tại, chế độios::app
vẫn sẽ tạo mới file đó. - Ví dụ này minh họa việc sử dụng
ios::app
để ghi log, một ứng dụng rất phổ biến của chế độ nối thêm. Chúng ta cũng thêm thông tin thời gian để làm cho log hữu ích hơn.
Tóm kết (cho phần này)
Ghi file trong C++ với <fstream>
và ofstream
là một quá trình trực quan, tương tự như sử dụng cout
. Các bước cốt lõi bao gồm:
- Bao gồm header
<fstream>
. - Tạo đối tượng
ofstream
và mở file (truyền tên file và optionally cờ mode nhưios::app
). - Luôn kiểm tra xem file có mở thành công không (
!outFile.is_open()
). - Sử dụng toán tử
<<
để ghi dữ liệu. - Đóng file (
outFile.close()
) khi hoàn thành hoặc để destructor tự xử lý.
Thành thạo việc ghi file là bước đệm vững chắc để bạn tiếp tục khám phá các thao tác file phức tạp hơn, bao gồm cả việc đọc file, xử lý file nhị phân và cấu trúc file phức tạp hơn.
Comments