Bài 37.2: Đọc file trong C++

Bài 37.2: Đọc file trong C++
Chào mừng quay trở lại với chuỗi bài về C++! Hôm nay, chúng ta sẽ cùng khám phá một kỹ năng cực kỳ quan trọng trong lập trình: đọc dữ liệu từ file. Dữ liệu thường không chỉ tồn tại trong bộ nhớ khi chương trình chạy mà còn cần được lưu trữ lâu dài trên đĩa cứng dưới dạng các file. Việc đọc file cho phép chương trình của bạn truy cập và xử lý thông tin đã được lưu lại.
Trong C++, chúng ta làm việc với file thông qua các luồng (streams). Khái niệm luồng này tương tự như cách chúng ta đã dùng cin
để đọc từ bàn phím và cout
để ghi ra màn hình. Thư viện chính hỗ trợ việc này là <fstream>
.
Thành phần cốt lõi: ifstream
Để đọc dữ liệu từ một file, chúng ta sử dụng lớp ifstream
(input file stream) được định nghĩa trong thư viện <fstream>
. Đối tượng của lớp này sẽ đóng vai trò là "cánh cổng" để bạn truy cập nội dung bên trong file.
Mở File: Bước đầu tiên và Quan trọng nhất
Trước khi đọc bất cứ thứ gì, bạn phải mở file đó ra. Có hai cách phổ biến để mở file:
Khi khai báo đối tượng
ifstream
: Bạn có thể truyền tên file (dưới dạng chuỗi C-styleconst char*
hoặcstring
) vào constructor củaifstream
.#include <fstream> // Bao gồm thư viện fstream #include <string> ifstream inputFile("du_lieu.txt"); // File 'du_lieu.txt' sẽ được cố gắng mở ngay lập tức
Sử dụng phương thức
open()
: Bạn khai báo đối tượngifstream
trước, sau đó gọi phương thứcopen()
để mở file. Điều này hữu ích khi bạn muốn sử dụng cùng một đối tượngifstream
để mở nhiều file khác nhau lần lượt.#include <fstream> #include <string> ifstream inputFile; inputFile.open("du_lieu.txt"); // File 'du_lieu.txt' sẽ được cố gắng mở khi gọi open()
Luôn Kiểm tra File Mở Thành Công Hay Không!
Đây là bước cực kỳ quan trọng và không thể bỏ qua. Việc mở file có thể thất bại vì nhiều lý do (file không tồn tại, không có quyền truy cập, tên file sai...). Nếu bạn cố gắng đọc từ một file chưa được mở thành công, chương trình của bạn sẽ gặp lỗi hoặc hành vi không xác định.
Cách đơn giản và phổ biến nhất để kiểm tra là sử dụng đối tượng ifstream
như một giá trị boolean:
#include <fstream>
#include <iostream> // Để in thông báo lỗi
ifstream inputFile("du_lieu.txt");
if (inputFile.is_open()) {
// Hoặc đơn giản hơn: if (inputFile) { ... }
cout << "File mo thanh cong!" << endl;
// Tiếp tục đọc file ở đây
} else {
cerr << "Khong the mo file du_lieu.txt" << endl; // In lỗi ra stderr
// Xử lý trường hợp lỗi: có thể thoát chương trình, thử lại, v.v.
}
Đối tượng ifstream
có thể được chuyển đổi ngầm định sang bool
. Giá trị true
biểu thị luồng đang ở trạng thái tốt (sẵn sàng đọc), và false
biểu thị có lỗi hoặc đã hết file.
Các Phương Pháp Đọc Dữ Liệu
Sau khi file đã được mở thành công, chúng ta có thể bắt đầu đọc dữ liệu. C++ cung cấp nhiều cách đọc khác nhau tùy thuộc vào định dạng dữ liệu của bạn.
1. Đọc theo Định dạng (sử dụng toán tử >>
)
Giống như cin
, bạn có thể dùng toán tử >>
để đọc dữ liệu được phân tách bởi khoảng trắng (khoảng trắng, tab, xuống dòng).
Đọc từng từ (chuỗi):
Giả sử file
data.txt
có nội dung:Lap trinh C++ that thu vi!
#include <fstream> #include <iostream> #include <string> int main() { ifstream inputFile("data.txt"); if (inputFile.is_open()) { string word; // Vòng lặp đọc từng từ cho đến khi hết file hoặc gặp lỗi while (inputFile >> word) { cout << word << endl; } inputFile.close(); // Nên đóng file khi xong } else { cerr << "Khong the mo file!" << endl; } return 0; }
Giải thích: Vòng lặp
while (inputFile >> word)
sẽ cố gắng đọc một chuỗi (từ) từ luồnginputFile
vào biếnword
. Phép đọc này sẽ bỏ qua khoảng trắng, tab, và xuống dòng trước khi đọc từ. Vòng lặp tiếp tục cho đến khi phép đọc thất bại (ví dụ: hết file hoặc dữ liệu không đúng định dạng).Đọc dữ liệu số:
Giả sử file
numbers.txt
có nội dung:10 20 3.14 -5 99
#include <fstream> #include <iostream> int main() { ifstream inputFile("numbers.txt"); if (inputFile.is_open()) { int num_int; double num_double; // Đọc số nguyên if (inputFile >> num_int) { cout << "Doc duoc so nguyen: " << num_int << endl; } else { cerr << "Khong doc duoc so nguyen dau tien!" << endl; } // Đọc số thực if (inputFile >> num_double) { cout << "Doc duoc so thuc: " << num_double << endl; } else { cerr << "Khong doc duoc so thuc!" << endl; } // Tiếp tục đọc các số nguyên còn lại trong vòng lặp while (inputFile >> num_int) { cout << "Doc duoc so nguyen tiep theo: " << num_int << endl; } inputFile.close(); } else { cerr << "Khong the mo file!" << endl; } return 0; }
Giải thích: Toán tử
>>
tự động chuyển đổi dữ liệu đọc được sang kiểu của biến đích (ở đây làint
vàdouble
). Nó cũng bỏ qua khoảng trắng giữa các số. Vòng lặpwhile (inputFile >> num_int)
là cách hiệu quả để đọc tất cả các số nguyên còn lại trong file cho đến khi hết.
2. Đọc từng Dòng (sử dụng getline
)
Khi bạn cần đọc toàn bộ nội dung của từng dòng văn bản, bao gồm cả khoảng trắng bên trong dòng đó, hàm getline
là lựa chọn phù hợp.
Giả sử file lines.txt
có nội dung:
Day la dong dau tien.
Day la dong thu hai, co nhieu khoang trang hon.
Dong cuoi cung!
#include <fstream>
#include <iostream>
#include <string>
int main() {
ifstream inputFile("lines.txt");
if (inputFile.is_open()) {
string line;
int line_count = 0;
// Vòng lặp đọc từng dòng cho đến khi hết file hoặc gặp lỗi
while (getline(inputFile, line)) {
line_count++;
cout << "Dong " << line_count << ": " << line << endl;
}
inputFile.close();
} else {
cerr << "Khong the mo file!" << endl;
}
return 0;
}
Giải thích: Hàm getline(inputFile, line)
đọc dữ liệu từ luồng inputFile
và lưu vào biến line
kiểu string
. Nó đọc cho đến khi gặp ký tự xuống dòng \n
(và loại bỏ ký tự này), hoặc cho đến khi hết file. Vòng lặp while
sẽ dừng khi getline
thất bại (thường là do đã đọc hết file).
3. Đọc từng Ký tự
Bạn có thể đọc từng ký tự một bằng cách sử dụng toán tử >>
với biến kiểu char
, hoặc dùng phương thức get()
. Phương thức get()
đặc biệt hữu ích vì nó không bỏ qua khoảng trắng, tab hay xuống dòng.
Giả sử file chars.txt
có nội dung:
A 1
B 2
#include <fstream>
#include <iostream>
int main() {
ifstream inputFile("chars.txt");
if (inputFile.is_open()) {
char ch;
// Vòng lặp đọc từng ký tự (bao gồm cả khoảng trắng và xuống dòng)
while (inputFile.get(ch)) {
cout << "[" << ch << "]"; // In ký tự và bao quanh để dễ nhìn
}
inputFile.close();
} else {
cerr << "Khong the mo file!" << endl;
}
return 0;
}
Giải thích: Phương thức inputFile.get(ch)
đọc ký tự tiếp theo từ luồng và lưu vào biến ch
. Nó trả về đối tượng luồng, có thể dùng trong điều kiện while
để kiểm tra thành công (tức là chưa hết file hoặc chưa gặp lỗi).
Đóng File (close()
)
Khi bạn đã hoàn thành việc đọc file, bạn nên đóng nó lại bằng cách gọi phương thức close()
trên đối tượng ifstream
:
inputFile.close();
Việc đóng file giúp giải phóng tài nguyên hệ thống và đảm bảo dữ liệu được xử lý đúng cách. Tuy nhiên, ifstream
tuân thủ nguyên tắc RAII (Resource Acquisition Is Initialization). Điều này có nghĩa là khi đối tượng ifstream
bị hủy (ví dụ: khi nó ra khỏi phạm vi - scope - của nó), destructor của lớp ifstream
sẽ tự động gọi close()
cho bạn. Vì vậy, 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, nhưng việc gọi nó giúp code rõ ràng hơn và bạn kiểm soát được thời điểm đóng file.
Kiểm tra trạng thái luồng và Xử lý lỗi nâng cao
Ngoài việc kiểm tra is_open()
hoặc sử dụng đối tượng luồng như boolean, bạn có thể kiểm tra các cờ trạng thái chi tiết hơn:
file.good()
: Trả về true nếu không có cờ lỗi nào được bật (luồng ở trạng thái tốt).file.eof()
: Trả về true nếu đã đọc đến cuối file. Lưu ý: Cờeofbit
chỉ được bật sau khi một thao tác đọc cố gắng đọc qua cuối file.file.fail()
: Trả về true nếu cờfailbit
hoặcbadbit
được bật. Điều này xảy ra do lỗi định dạng (ví dụ: cố đọc số vào biếnint
nhưng gặp chữ) hoặc lỗi đọc chung.file.bad()
: Trả về true nếu cờbadbit
được bật. Điều này thường chỉ ra lỗi nghiêm trọng không thể phục hồi (ví dụ: lỗi phần cứng).
Thông thường, cách tốt nhất để kiểm tra kết thúc file trong vòng lặp là kiểm tra kết quả của phép đọc (while (file >> data)
) thay vì kiểm tra file.eof()
trước mỗi lần đọc.
Comments