Một số thao tác đọc dữ liệu từ File trong C++

tutorial
basic
c++

(...) #1

####Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.

Mình đã trình bày đến các bạn một số thao tác cơ bản với file khi sử dụng thư viện cstdio (trong ngôn ngữ lập trình C), sử dụng thư viện này, chúng ta sẽ sử dụng một con trỏ kiểu FILE làm cầu nối trung gian giữa chương trình của chúng ta và file, mọi thao tác trên file đều phải sử dụng con trỏ kiểu FILE này. Trong bài học này, các bạn sẽ cùng mình tìm hiểu thêm một số cách để thao tác với file bằng cách sử dụng các stream mà ngôn ngữ C++ cung cấp.

Như mình giới thiệu ở bài học trước, để thao tác với file trong ngôn ngữ C++, chúng ta sử dụng các class như ifstream (file input), ofstream (file output), hoặc fstream (file input/output) và dữ liệu sẽ được truyền đi một cách tuần tự qua các đối tượng của các stream này, để đi vào file hoặc đi từ file ra chương trình.

###File input

Đầu tiên mình muốn giới thiệu đến các bạn một số thao tác đọc dữ liệu từ file sử dụng class fstream mà ngôn ngữ C++ cung cấp. Để sử dụng class này, chúng ta cần include file fstream vào file chương trình:

#include <fstream>

Do class ifstream và ofstream đều được định nghĩa bên trong file fstream, nên chúng ta chỉ cần include thư viện fstream là có thể sử dụng cả hai class này.

Bây giờ chúng ta cùng tạo một đối tượng file input thuộc kiểu ifstream:

std::ifstream fileInput("C:/Users/ADMIN/Desktop/my_document.txt");

File mà mình muốn đọc có tên là my_document.txt trong thư mục Desktop trên máy tính của mình. Class ifstream có nhiều phương thức khởi tạo khác nhau, nhưng đơn giản nhất là chúng ta truyền vào một string là đường dẫn chính xác của file chúng ta cần đọc dữ liệu. Nếu file đó nằm trong thư mục mà chương trình được build ra thì chúng ta có thể sử dụng đường dẫn tương đối.

Bây giờ mình sẽ kiểm tra xem đối tượng fileInput của mình đã liên kết được đến file cần mở hay chưa bằng cách sau:

if (fileInput.fail())
	std::cout << "Failed to open this file!" << std::endl;

Phương thức fail sẽ trả về false nếu đối tượng của ifstream không liên kết đến file được. Và chính xác là nó đã không liên kết được với file.

Đó là do trong thư mục Desktop của mình chưa có file my_document.txt nào cả. Bây giờ mình tạo một file my_document.txt chưa có dữ liệu trong thư mục Desktop, chương trình không còn thông báo lỗi gì nữa.

Như các bạn thấy, chúng ta sử dụng các phương thức được định nghĩa trong class ifstream, nên chúng ta chỉ cần sử dụng member selection operator (.) để gọi các phương thức đã định nghĩa sẵn.

Một điều chúng ta cần lưu ý khi thao tác đọc ghi file với ngôn ngữ C là chúng ta luôn phải gọi hàm đóng file fclose ngay khi không còn sử dụng đến file nữa. Đối với các stream xử lý file trong C++ thì đơn giản hơn, khi ra khỏi phạm vi khối lệnh, destructor sẽ tự gọi phương thức close và file stream sẽ tự động đóng lại.

Do đó, các bạn có thể xử lý như sau (hoặc không cần thiết cũng được):

std::ifstream input("C:/Users/ADMIN/Desktop/my_document.txt");

if (input.fail())
{
	std::cout << "Failed to open this file!" << std::endl;
	return -1;
}

//read or write data in here

input.close(); //we can forget this line

Bây giờ đến phần xử lý đọc dữ liệu từ file, dữ liệu từ file sẽ truyền qua stream vào các biến trong chương trình. Nhưng trước hết, mình cần thêm một số dữ liệu vào file my_document.txt trước. Nội dung của file my_document.txt như sau:

1 2 3 4 5

Mình sẽ có chương trình đọc dữ liệu từ file thông qua đối tượng fileInput và in ra màn hình như sau:

#include <iostream>
#include <fstream>

int main()
{
	std::ifstream fileInput("C:/Users/ADMIN/Desktop/my_document.txt");

	if (fileInput.fail())
	{
		std::cout << "Failed to open this file!" << std::endl;
		return -1;
	}
	while (!fileInput.eof())
	{
		int n;
		fileInput >> n;
		std::cout << n << " ";
	}
	std::cout << std::endl;

	fileInput.close();

	return 0;
}

Mình đã sử dụng extraction operator (>>) để đưa dữ liệu từ file vào lưu trong biến n và sau đó in ra màn hình thông qua đối tượng std::cout. Lúc trước, các bạn nhập dữ liệu từ bàn phím, dữ liệu đó sẽ được lưu tạm thời trong stdin ở đâu đó trong máy tính, và sử dụng đối tượng cin để đưa dữ liệu vào biến. Bây giờ, dữ liệu đã có sẵn trong file, chúng ta chỉ cần tạo một đối tượng thuộc kiểu ifstream và liên kết đến file, sau đó sử dụng toán tử (>>) để đưa dữ liệu vào biến. Cách hoạt động hoàn toàn tương tự nhau phải không các bạn?

Tuy nhiên, chúng ta cần biết rõ cấu trúc tổ chức dữ liệu bên trong file là như thế nào để tránh các trường hợp ngoại lệ xảy ra. Ví dụ mình giữ nguyên cấu trúc chương trình như trên và sửa lại file my_document.txt có nội dung như sau:

1 2 3 4 5 a b c

Thử chạy lại chương trình trên, chương trình sẽ rơi vào tình trạng lặp vô hạn. Đó là do kiểu dữ liệu mà chúng ta chọn để lưu giá trị đọc từ file là số nguyên, nhưng trong file lại xuất hiện các kí tự. Do đó, khi đọc hết số nguyên trong file, các kí tự không thể đưa vào biến n được do lỗi định dạng, vòng lặp while không thể kết thúc do chúng ta đặt điều kiện là đọc cho đến khi kết thúc file.

Để khắc phục tình trạng này, chúng ta cần kiểm tra dữ liệu đọc từ file đưa vào chương trình có đúng định dạng hay không, chúng ta có thể làm như sau:

std::ifstream fileInput("C:/Users/ADMIN/Desktop/my_document.txt");

if (fileInput.fail())
	std::cout << "Failed to open this file!" << std::endl;

while (!fileInput.eof())
{
	int n;
	if (fileInput >> n)
		std::cout << n << " ";
	else
		return -2;
}
std::cout << std::endl;

fileInput.close();

system("pause");
return 0;

Nhấn F5 để chạy chương trình dưới chế độ Debug, chúng ta sẽ thấy chương trình dừng lại khi đọc xong giá trị 5 và trả về giá trị -2 cho hệ điều hành. Mình đã sử dụng if statement để kiểm tra xem extraction operator có được thực hiện đúng hay không.

Nếu chúng ta vẫn muốn đọc toàn bộ dữ liệu trong file và in ra màn hình, chúng ta có thể đọc từng kí tự trong file vào biến kiểu char, thay vì kiểu số nguyên:

while (!fileInput.eof())
{
	char c;
	if(fileInput >> c)
		std::cout << c << " ";
}
std::cout << std::endl;

Tiếp theo, chúng ta cùng thử đọc từng dòng dữ liệu trong file vào các đối tượng std::string và in chúng ta màn hình. Mình thay đổi nội dung file my_document.txt thành như sau:

This is line 1
This is line 2
This is line 3
This is line 4
This is line 5

Dưới đây là một cách xử lý để đọc từng dòng dữ liệu và in ra màn hình:

while (!fileInput.eof())
{
	char temp[255];
	fileInput.getline(temp, 255);
	std::string line = temp;
	std::cout << line << std::endl;
}

Mình đã sử dụng phương thức getline có sẵn trong class ifstream để đọc dữ liệu từ file và đưa vào một C-style string có kích thước cố định cho trước, sau đó dùng C-style string đó đưa vào đối tượng của std::string để giảm kích thước bộ nhớ dư thừa.

Nếu sử dụng extraction operator (>>) để đọc dữ liệu và std::string trực tiếp, chúng ta sẽ gặp phải trường hợp kí tự khoảng trắng không đọc được như khi sử dụng đối tượng std::cin.

Ngoài một số cách đọc dữ liệu từ file thông qua đối tượng của class ifstream mà mình nói trên, chúng ta còn rất nhiều cách khác, với rất nhiều phương thức khác mà class ifstream đã hổ trợ. Việc chọn cách nào để đọc dữ liệu còn phụ thuộc vào cấu trúc dữ liệu được tổ chức bên trong file như thế nào.

Các bạn có thể tìm hiểu thêm về thư viện ifstream tại đường dẫn sau:

http://www.cplusplus.com/reference/fstream/ifstream/

###File input modes

Cũng tương tự như thao tác mở liên kết đến file ở trong ngôn ngữ C, file input stream trong C++ cũng cho phép chúng ta chọn kiểu để mở file. Dưới đây là một số mode có thể dùng được cho file input stream:

Nếu chúng ta tạo liên kết đến file thông qua đối tượng của class ifstream, mode std::ios::in được sử dụng mặc định. Tuy nhiên, khi sử dụng file input/output stream với class fstream thì chúng ta cần chọn mode cho đối tượng, ví dụ:

std::fstream fileInput("C:/Users/ADMIN/Desktop/my_document.txt", std::ios::in);

if (fileInput.fail())
	std::cout << "Failed to open this file!" << std::endl;

while (!fileInput.eof())
{
	char temp[255];
	fileInput.getline(temp, 255);
	std::string line = temp;
	std::cout << line << std::endl;
}
std::cout << std::endl;

fileInput.close();

Bên cạnh đó, chúng ta cũng có thể mở file dưới dạng nhị phân:

std::fstream fileInput("C:/Users/ADMIN/Desktop/my_document.txt", std::ios::in | std::ios::binary);

Đọc dữ liệu dưới dạng nhị phân có thể được áp dụng cho cả file nhị phân hoặc file văn bản.

###Sử dụng một input stream cho nhiều file

Cũng như việc chúng ta tạo liên kết tới file với một đường dẫn cụ thể và sử dụng phương thức close để đóng stream, chúng ta cũng có thể sử dụng phương thức open để tạo một stream mới đến một đường dẫn khác. Ví dụ:

std::ifstream fileInput("C:/Users/ADMIN/Desktop/my_document.txt");
//..............
fileInput.close();

fileInput.open(""C:/Users/ADMIN/Desktop/my_document2.txt"");
//..............
fileInput.close();

Phương thức open hoạt động tương tự phương thức khởi tạo của class ifstream, nên chúng ta có thể tạo kết nối nhiều lần. Tuy nhiên, chúng ta chỉ có thể kết nối đến một file duy nhất tại một thời điểm. Và trước khi tạo liên kết đến file khác, chúng ta nên đóng liên kết trước đó.


###Tổng kết

Thao tác đọc dữ liệu từ file thông qua các file input stream mà C++ cung cấp khá đơn giản so với sử dụng thư viện cstdio trong ngôn ngữ C. Các bạn có thể tham khảo thêm nhiều cách đọc dữ liệu từ file khác qua topic dưới đây:

Đối với các trường hợp cần lưu trữ dữ liệu trong file với nội dung lớn và có cấu trúc, chúng ta thường sử dụng các định dạng file như *.xml hoặc *.json… Ngày nay đã có nhiều thư viện hổ trợ cho việc đọc dữ liệu cho các định dạng file này, các bạn có thể tham khảo thêm tại đây:

http://rapidxml.sourceforge.net/index.htm

Một điểm hạn chế mà file input stream trong C++ gặp phải là chúng không hỗ trợ đọc toàn bộ nội dung vào một biến kiểu struct như hàm fread trong ngôn ngữ C. Do đó, để làm được điều này, chúng ta cần định nghĩa lại toán tử (>>) bên trong struct hoặc class có liên quan, phần này sẽ được trình bày trong các bài học sau.


Hẹn gặp lại các bạn trong bài học tiếp theo trong khóa học lập trình C++ hướng thực hành.

Mọi ý kiến đóng góp hoặc thắc mắc có thể đặt câu hỏi trực tiếp tại diễn đàn.

www.daynhauhoc.com


Tổng hợp: Khóa học lập trình C++ dành cho người mới bắt đầu
Xử lý string trong C++
(Đình Nhì) #4

khi dùng toán tử (>>) thì làm sao để lấy được dấu cách


(rogp10) #5

istream::getline() nhé :smiley:

Nhập “string” và std::string thì phải dùng hàm khác chớ.


(Đình Nhì) #6

khi đọc một một dãy số từ một file làm sao để biết hết một dòng

vd:
file:
1 2 3 4 5
6 7 8 9 10

khi đọc file làm sao để biết đọc xong dòng “1 2 3 4 5”


(Nong Ngoc Hoang) #7

Ví dụ có 1 dãy kí tự trong file text như sau:
0123456789abcdef, làm sao để có thể đọc được thành số: 01, 23, 45, 67, 89,ab,cd,ef theo hệ hexa thế bạn ơi


(HK boy) #8

Mình biết là bạn thắc mắc, nhưng đừng đi cmt lung tung vào topic khác được không bạn?


Đọc cả dãy rồi bạn viết hàm tách từ xâu đã đọc ra nhé. Bạn viết liền các số như thế thì bạn muốn tách kiểu gì?


(Nong Ngoc Hoang) #9

trong C chỉ cần fscanf(FlLE,"%2x",a[i]) là ok.
Mà trong C++ thì phức tạp quá. k có định dạng dữ liệu giống bên C nhỉ :frowning:


(Nong Ngoc Hoang) #10

Toppic này đang thảo luận về thao tác đọc dữ liệu trong C++ mà bạn


(HK boy) #11

C++ cũng có định dạng dữ liệu mà. Bê nguyên fscanf vào C++ cũng được, nhưng trông code hơi hổ lốn C với C++.

*Topic.

Ý mình là cmt mình dẫn ở dưới là bạn cmt off-topic.


(Nong Ngoc Hoang) #12

Mình muốn đồng nhất hẳn code sang C++ thôi. nếu bê nguyên thì thấy kì quá


(Nong Ngoc Hoang) #13

Sry . mình không để ý. vậy bên C++ có định dạng nào để đọc 2 ký tự như bên C không bạn?

Mình thử dùng File.getline(x,2) mà nó bị lỗi ở dòng lệnh


(HK boy) #14

Thế bạn định bỏ qua toàn bộ phần sau đi à?

Mình vẫn sẽ làm thủ công: đọc cả dòng, sau đó split sau.


(‏) #15
std::string s;
std::cin >> std::setw(2) >> s; //đọc 2 ký tự vô `s`
a[i] = std::stoi(s, nullptr, 16); //đổi `s` thành số nguyên, hệ 16

ko biết có cách nào ngắn hơn ko, setw(2) >> hex >> a[i] ko ăn thua @_@


(Nong Ngoc Hoang) #16

mình dùng FILE>>std::setw(2)>>s mà không đọc được 2 ký tự bạn ak


(‏) #17

file là kiểu FILE * à? Vậy thì xài fscanf… Còn kiểu C++ thì file phải là kiểu std::ifstream ~.~


(Nong Ngoc Hoang) #18

Mình dùng std::ifstream File(“input.txt”);


(‏) #19

vậy “không đọc được 2 ký tự” là sao? biên dịch lỗi? chỉ đọc 1 ký tự?

thêm include <iomanip> với <string>


(Nguyễn Phương Hảo) #20

muốn đọc 1 file txt mà có dạng như này thành một ma trận với 2 cột hoặc đọc thành 2 mảng trong c++ thì làm sao ạ
123, 0.117
76, 0.145
88, 0.002


(vũ xuân quân) #21

đọc từng dòng một rồi cắt chuổi.
vd:
đọc được dòng 123, 0.117. do 2 số này cách nhau bới dấu “,”. nên mình dùng dấu “,” để cắt chuối.
sau khi cắt xong thì lưu vào mảng 2 chiều.


83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?