Lập trình tân binh | 1.9. Thao tác với các tệp

Các chương trình tất cả chúng ta viết từ trước đến giờ vẫn còn khá cơ bản. Đấy là thông thường, những bạn vừa mới khởi đầu học C + + thôi mà. Chỉ cần thêm 1 chút cố gắng nỗ lực với rèn luyện thì sớm sẽ viết được những ứng dụng thật sự thôi .
Những kiến thức và kỹ năng cơ bản cũng gần vừa đủ rồi. Trong bài học kinh nghiệm này, tất cả chúng ta sẽ tìm hiểu và khám phá thêm 1 tác vụ cũng khá quan trọng trong C + + : thao tác với những tệp .
Những chương trình mà tất cả chúng ta luận bàn trong những bài học kinh nghiệm mới trước chỉ hoàn toàn có thể hiển thị thông điệp ra màn hình hiển thị cũng như là nhận tài liệu từ người dùng. Thế nhưng như vậy vẫn chưa đủ. Chúng ta đều biết những ứng dụng như Notepad, MS Word hay MS Excel còn có năng lực đọc cũng như là đổi khác nội dung của những tệp. Kể cả trong quốc tế game show cũng vậy : chắc rằng sống sót những tệp có trách nhiệm tàng trữ thông tin về mức độ triển khai xong game show của người chơi ( mà bạn sử dụng khi có những điểm nhớ ) cũng như là những tệp hình ảnh, âm thanh được sử dụng. Tóm lại, nếu một chương trình mà không hề tiếp xúc với những tệp thì sẽ rất dễ dẫn đến nhiều tính năng bị hạn chế .

Trong bài học này, chúng ta sẽ xem làm sao để làm được điều đó. Nếu bạn đã làm chủ được các lệnh cincout thì các bạn đã biết gần hết rồi đấy !

Viết dữ liệu vào trong tệp

Điều tiên phong cần biết khi muốn thao tác với những tệp là để mở tệp đấy ra. Trong C + + cũng vậy !

Một khi tệp đã được mở ra, mọi chuyện sẽ diễn ra giống như  khi chúng ta dùng cincout. Ví dụ dễ thấy nhất là chúng ta cũng sẽ sử dụng <<>>. Tin tôi đi, rất nhanh thôi các bạn sẽ quen với việc này.

Chúng ta sử dụng khái niệm luồng (flux) dữ liệu khi nói về việc giao tiếp giữa chương trình máy tính với bên ngoài. Bài học này tập trung vào luồng dữ liệu tới các tệp nhưng mà nói « thao tác với các tệp » thì hình như là dễ hiểu hơn nhiều đúng không.

Tiêu đề fstream

Như thường lệ, khi chúng ta muốn sử dụng 1 tính năng trong C++, cần phải bắt đầu bằng việc bao gồm đúng tệp tiêu đề. Trong trường hợp này, bạn sẽ phải thêm #include vào đầu mã nguồn chương trình của bạn.

! Các bạn đều đã biết về gói iostream cung cấp những công cụ để nhập/xuất dữ liệu với console. iostream thật ra là input/output stream nghĩa là luồng nhập/xuất. Tương ứng của chúng ta là fstream cho file stream nghĩa là luồng dữ liệu tới tệp.

Khác biệt là bạn cần luồng riêng cho mỗi tệp. Sau đây hãy cùng xem cách để mở ra 1 luồng xuất được cho phép tất cả chúng ta ghi tài liệu vào tệp .

Mở tệp để ghi

Các luồng thực chất là các đối tượng (object) bởi vì như chúng ta đã từng nhắc đến, C++ là ngôn ngữ lập trình hướng đối tượng (object oriented).

Đừng lo ngại, tất cả chúng ta sẽ dành cả 1 chương sau để nói về khái niệm này. Trước mắt, hãy coi nó như 1 biến cải tiển phức tạp. Các đối tượng người dùng mà tất cả chúng ta nhắc đến lưu khá nhiều thông tin về tệp đang được mở cũng như cung ứng nhiều tình năng được cho phép tất cả chúng ta đóng tệp hay đưa con trỏ về đầu tệp, vv …

Quan trọng là chúng ta có thể khai báo 1 luồng giống như cách chúng ta đã làm để khai báo 1 biến. Biến đặc biệt này sẽ có « kiểu dữ liệu » là ofstream và giá trị là đường dẫn tới vị trí tệp cần thao tác.

Giống như khi đặt tên biến, có 1 vài quy tắc cần nhớ khi đặt tên luồng :

  • Tên chỉ gồm chữ, số và dấu _
  • Ký tự đầu tiên phải là chữ (viết hoa hoặc thường)
  • Các chữ không được có chứa các dấu
  • Không có dấu cách

Hẳn là những bạn đã nhận ra là những quy tắc này không khác gì những quy tắ để đặt tên biến. Ngoài ra là những quy ước về đặt tên mà rất nhiều lập trình viên sử dụng và tất cả chúng ta đã nhất trí dùng trong giáo trình này. Trong phần tiếp theo, tôi sẽ chon dùng luong làm tên luồng mà tất cả chúng ta sẽ thao tác trong những ví dụ .

#include 
#include 
using namespace std;

int main(){
   ofstream luong("C:/Applis/lttb/files/scores.txt");
   //Khai bao 1 luong cho phep ghi vao tep C:/Applis/lttb/files/scores.txt
   return 0;
}

Trong dấu ngoặc kép là đường dẫn tới vị trí tệp. Bạn hoàn toàn có thể dùng 1 trong 2 dạng sau đây :

  • Đường dẫn tuyệt đối : chỉ ra vị trí của tệp so với ổ đĩa của máy tính. Ví dụ : C:/Applis/lttb/files/scores.txt
  • Đường dẫn tương đối : vị trí của tệp so với thư mịc chứa tệp chạy của chương trình. Ví dụ : files/scores.txt nếu tệp chạy nằm trong C:/Applis/lttb

Sau đó, tất cả chúng ta hoàn toàn có thể khởi đầu sử dụng luồng để ghi tài liệu vào tệp .
! Nếu tệp tin đích không sống sót, chương trình sẽ tạo ra nó ! Thế nhưng những thư mục trong đường dẫn phải sống sót. Như trong ví dụ bên trên C : / Applis / lttb / files phải sống sót, nếu không tệp tin scores.txt sẽ không được tạo ra .

Thường thường thì đường dẫn sẽ được chứa trong 1 chuỗi ký tự string. Trong trường hợp đó thì cần dung thêm hàm c_str() để mở tệp.

string const tenTep("C:/Applis/lttb/files/scores.txt");

ofstream luong(tenTep.c_str());
//Khai bao luong de ghi du lieu vao tep

1 số vấn đề có thể xảy ra khi ta cố mở 1 tệp như là tệp không thuộc sở hữu của bạn hay là ổ cứng đã đầy. Vì thế phải luôn luôn kiểm tra để chắc chắn là mọi thứ diễn ra suôn sẻ. Chúng ta dùng cú pháp if(luong) để thử. Nếu phép thử thất bại nghĩa là đã có vấn đề trong quá trình mở tệp nên ta không thể thao tác với tệp được

ofstream luong("C:/Applis/lttb/files/scores.txt");

if(monFlux){   //Kiem tra xem moi thu dien ra suon se
    //Tat ca OK, thao tac voi tep
} else {
    cout << "LOI: Khong mo duoc tep." << endl;
}

Tất cả sau cuối đã chuẩn bị sẵn sàng. Chuẩn bị ghi tài liệu vào tệp !

Ghi dữ liệu vào luồng

Tôi đã nói với các bạn là việc này sẽ giống như với cout. Chúng ta sẽ sử dụng dấu << để gửi dữ liễu từ chương trình vào luồng.

#include 
#include 
#include 
using namespace std;

int main(){
    string const tenTep("C:/Applis/lttb/files/scores.txt");
    ofstream luong(tenTep.c_str());

    if(luong){
        luong << "Xin chao Tan Binh, day la cau se duoc ghi vao trong tep." << endl;
        luong << 42 << endl;

        int tuoi(23);
        luong << "Toi " << tuoi << " tuoi." << endl;
    }else{
        cout << "LOI: Khong mo duoc tep." << endl;
    }
    return 0;
}

Nếu tôi chạy chương trình này, tệp scores.txt sẽ được tạo ra với nội dung sau đây .

Để thực hành thực tế, bạn hãy thử viết 1 chương trình nhu yếu người dung nhập vào tên và tuổi của họ và ghi thông tin này vào 1 tệp thử xem .

Lựa chọn chế độ khi mở tệp

? Thế chuyện gì sẽ xảy ra nếu tệp đã tồn tại sẵn trong ổ cứng?

Nội dung của tệp đó sẽ bị xóa và sửa chữa thay thế bằng những tài liệu mà bạn vừa ghi vào. Điều này sẽ thành yếu tố nếu tất cả chúng ta chỉ muốn thêm nội dung vào cuối 1 tệp đã có sẵn. Ví dụ hoàn toàn có thể là 1 tệp ghi lại tổng thể những hoạt động giải trí của người dung mà tất cả chúng ta không muốn xóa đi nội dung của nó mỗi lần chạy lại chương trình .

Để thêm nội dung vào cuối 1 tệp, chúng ta cần bổ sung thêm 1 thông số khi khởi tạo luồng tới tệp đó : ofstream luong("C:/Applis/lttb/files/scores.txt ", ios::app);.

! app là viết tắt của append nghĩa là « nối thêm »
Nhờ việc này, bạn không cần lo về yếu tố sẽ xóa mất nội dung tệp nữa. Mọi tài liệu sẽ được ghi thêm vào cuối tệp .

Đọc nội dung 1 tệp tin

Đã biết cách để ghi tài liệu vào trong 1 tệp, giờ đây tất cả chúng ta sẽ chuyển qua xem cách để đọc được nội dung từ đó. Các bạn sẽ thấy là những điều tôi trình diễn dưới đây sẽ không quá lạ lẫm .

Mở tệp để đọc

Nguyên lý vẫn không thay đổi, chỉ khác là chúng ta sẽ dùng ifstream thay vì ofstream. Việc kiểm tra vẫn luôn luôn cần thiết để chắc là quá trình mở tệp không có vấn đề gì.

ifstream luong("C:/Applis/lttb/files/scores.txt");  //Mo tep de doc noi dung
if(luong){
    //Tep da mo, san sang de doc
}else{
    cout << "LOI: Khong mo duoc tep de doc." << endl;
}

… và đọc tệp

Có 3 cách khác nhau để đọc 1 tệp :

  1. Đọc theo dòng, sử dụng hàm getline();
  2. Đọc từng chữ, sử dụng dấu >>.
  3. Đọc từng ký tự, sử dụng hàm get().

Sau đây là cụ thể .

Đọc theo dòng

Phương pháp tiên phong được cho phép đọc hàng loạt 1 dòng và lưu nó vào trong 1 chuỗi ký tự .

string dong;
getline(luong, dong); //Doc 1 dong hoan chinh

Cách sử dụng giống hệt như chúng ta đã làm với cin, không có gì đặc biệt.

Đọc từng chữ

Phương pháp thứ 2 cũng quen thuộc không kém. Tôi sẽ trình diễn 1 ví dụ để gợi nhớ cho những bạn .

double so;
monFlux >> so; //Doc 1 so thap phan tu tep

string chu;
monFlux >> chu;    //Doc 1 chu tu tep

Phương pháp này cho phép đọc thông tin giữa vị trí hiện tại của con trỏ trong tệp và dấu cách gần nhất sau đấy. Dữ liệu đọc được sẽ được chuyển đổi thành double, int hay string hoặc bất kỳ kiểu dữ liệu nào tùy theo kiểu mà chúng ta khai báo.

Đọc từng ký tự

Phương pháp sau cuối là giải pháp lạ lẫm duy nhất ở đây, nhưng nó cũng không có gì phức tạp cả. Tôi xin bảo vệ .

char a;
luong.get(a);

Đoạn mã đọc 1 ký tự duy nhất và lưu nó vào trong biến a.

! Phương pháp này đọc tổng thể những ký tự, có nghĩa là tính cả những dấu cách, dấu tab hay cả ký tự xuống dòng ( vâng, xuống dòng cũng là 1 ký tự, có điều nó hơi đặc biệt quan trọng 1 chút ). Dù đặc biệt quan trọng đến mấy thì những ký tự này cũng vẫn được lưu trong biến .

Bạn có còn nhớ trong bài 5, khi chúng ta nói về cin, tôi đã chỉ cho các bạn là cần thêm cin.ignore() khi chúng ta chuyển từ đọc từng chữ qua đọc theo dòng. Chúng ta cũng cần làm điều tương tự trong trường hợp này.

ifstream luong("C:/Applis/lttb/files/scores.txt");
string chu;
luong >> chu;          //Doc tung chu

luong.ignore();        //Thay doi cach doc
string dong;
getline(luong, dong); //Doc theo dong

Nhưng tôi cũng thú thực với những bạn là hiếm khi người ta biến hóa liên tục giữa những cách đọc tệp .

Đọc toàn bộ tệp

Việc phải đọc hàng loạt tệp là rất liên tục. Tôi đã nói ở trên cách để đọc nhưng vẫn chưa nhắc đến với những bạn cách để dừng khi tất cả chúng ta đọc đến cuối tệp .

Để biết là chúng ta có thể tiếp tục đọc không, cần phải sử dụng kết quả trả về của hàm getline(). Trong thực tế, ngoài việc đọc 1 dòng dữ liệu, hàm này còn trả về cho chúng ta 1 biến bool thông báo xem chúng ta có thể tiếp tục đọc không. Nếu kết quả trả về là true nghĩa là mọi việc vẫn ổn, chúng ta có thể tiếp tục đọc. Nếu hàm này trả về false, vậy có nghĩa là chúng ta đã đọc tới cuối tệp hoặc có vấn đề xảy ra khi đọc. Trong cả 2 trường hợp đó thì chúng ta đều phải dừng việc đọc.

Các bạn có thấy quen không? Nghe như kiểu một điều kiện của vòng lặp không xác định vậy nhỉ. Đúng thế, trong tình huống này, vòng lặp while là sự lựa chọn hợp lý nhất cho chúng ta.

#include 
#include 
#include 
using namespace std;

int main(){
   ifstream tep("C:/Applis/lttb/files/scores.txt");
   if(tep){
      //Mo tep khong co van de gi, chung ta co the doc
      string dong; //Bien de luu tru dong vua doc
      while(getline(tep, dong)){  //Khi con chua het tep va khong co van de gi, doc 1 dong trong tep
         cout << dong << endl;
         //In dong do ra man hinh
         //Hoac lam gi do voi dong du lieu nay, tuy ban
      }
   }else{
      cout << "LOI: Khong the mo tep de doc." << endl;
   }
   return 0;
}

Khi mà chúng ta đã đọc được dữ liệu thì việc thao tác trở nên quá dễ dàng. Ở đây tôi chỉ đơn giản là in ra màn hình nhưng trong 1 chương trình thực thì chúng ta có thể làm nhiều việc khác. Đây là cách thức thường được sử dụng nhất khi phải đọc dữ liệu trong 1 tệp. Thật ra điều này rất dễ hiểu vì khi dữ liệu được lưu trong 1 biến string, chúng ta có thể sử dụng hàng đống hàm để thao tác với chuỗi ký tự trên dữ liệu vừa đọc được !

1 vài mẹo vặt

Sau đây là 1 vài mẹo vặt hữu dụng khi bạn thao tác với những tệp .

Đóng 1 tệp

Ở bên trên, tôi đã nói với những bạn về cách để mở 1 tệp nhưng vẫn chưa hề đề cập đến cách để đóng nó lại. Thật ra không phải do tôi quên mà là việc đó là không thiết yếu. Những tệp đang mở sẽ tự động hóa được đóng lại khi chương trình thoát ra khỏi đoạn mã mà trong đấy những luồng được khởi tạo .

void f(){
   ofstream luong("C:/Applis/lttb/files/scores.txt");  //Mo tep ra de su dung
   //Su dung tep
}  //Sau khi thoat ra khoi doan ma nay, tep tu dong dong lai

Tuy nhiên cũng có những trường hợp xảy đến là chúng ta cần phải đóng tệp trước khi nó được đóng tự động. Trong những trường hợp đó, chúng ta sử dụng đến hàm close().

void f(){
   ofstream luong("C:/Applis/lttb/files/scores.txt");  //Mo tep ra de su dung
   //Utilisation du fichier
   luong.close();  //Dong tep lai
   //Tu dong nay, khong the ghi them du lieu vao tep
}

Cũng tương tự như thế, chúng ta không nhất thiết phải mở tệp lúc khởi tạo mà có thể chờ thực hiện 1 vài xử lý rồi mới sử dụng hàm open() để mở tệp.

void f(){
   ofstream luong;  //Khoi tao 1 luong doc lap, khong lien quan den bat cu tep nao
   luong.open("C:/Applis/lttb/files/scores.txt");  //Mo tep C:/Applis/lttb/files/scores.txt
   //Su dung tep
   luong.close();  //Dong tep lai
  //Tu dong nay, khong the ghi them du lieu vao tep
}

Như những bạn hoàn toàn có thể thấy, trong hầu hết trường hợp thì việc này là vô dụng. Cứ để tệp được mở và đóng tự động hóa là được .

! 1 vài người thích sử dụng những hàm open()close() mặc dù là không cần thiết. Lợi ích của việc này là kiểm soát được lúc nào thì tệp được mở ra và lúc nào thì nó được đóng lại. Nói chúng đấy là sở thích của từng người, tôi để các bạn tự lựa chọn.

Con trỏ trong tệp

Hãy cùng đi sâu 1 chút vào những khái niệm kỹ thuật và xem xem chuyện gì thật sự diễn ra khi mở tệp ra đọc. Khi bạn sử dụng Notepad để mở tệp ví dụ điển hình, bạn sẽ nhìn thấy 1 con trỏ hiển thị ở vị trí mà bạn sẽ viết. Ví dụ trong hình sau, con trỏ nằm ngay sau chữ “ Tan Binh ” .

Nếu tất cả chúng ta gõ thêm nội dung vào tệp, tài liệu sẽ hiện ra ở ngay vị trí đó. Điều này chắc hẳn là ai cũng biết rồi. Thế nhưng hoàn toàn có thể những bạn chưa biết, đó là trong C + + cũng có 1 con trỏ như vậy .
Khi chương trình chạy dòng lệnh sau

ifstream fichier("C:/Applis/lttb/files/scores.txt");

thì tệp C : / Applis / lttb / files / scores.txt sẽ được mở ra và con trỏ nằm ở đầu tệp. Nếu tất cả chúng ta đọc chữ tiên phong thì trong chuỗi ký tự tác dụng sẽ có là “ Xin ” và con trỏ sẽ nằm ở vị trí đầu chữ tiếp theo. Chữ tiếp theo máy sẽ đọc được là “ chao ”, “ Tan ”, “ Binh, ”, vv … cho tới cuối tệp. Tóm lại là tất cả chúng ta bắt buộc phải đọc tệp 1 cách tuần tự, không được thực dụng cho lắm .
Thật may là tất cả chúng ta cũng hoàn toàn có thể nhu yếu con trỏ vận động và di chuyển với 1 số nhu yếu đơn thuần như hãy đi đến vị trí ký tự thứ 20 tính từ đầu tệp hoặc hãy tiến thêm 10 ký tự. Nhờ đó tất cả chúng ta hoàn toàn có thể chỉ cần đọc những tài liệu tất cả chúng ta cần .
Để làm thế, trong bước đầu cần biết vị trí hiện tại của con trỏ trong tệp sau đó mới đến di dời nó .

Vị trí hiện tại của con trỏ  

Chúng ta có hàm được cho phép biết vị trí của con trỏ trong tệp, đơn cử là cho biết xem nó đang ở ký tự thứ bao nhiêu. Đáng buồn là hàm này lại có 2 tên khác nhau dành cho 2 trường hợp là luồng nhập và luồng xuất. Thêm vào đấy thì 2 tên này còn là 2 cái tên khó nhớ .

  • Vớiifstream:tellg()
  • Vớiofstream: tellp ( )

Cách dùng của 2 hàm này thế nhưng lại trọn vẹn giống nhau .

ofstream teo("C:/Applis/lttb/files/scores.txt");
int viTri = tep.tellp(); //Tim ra vi tri con tro
cout << "Chung ta dang o ky tu thu " << viTri << " trong tep." << endl;

Dịch chuyển con trỏ

Để thao tác này tất cả chúng ta cũng có đến 2 hàm .

  • Vớiifstream : seekg ( )
  • Vớiofstream :seekp()

Cách sử dụng của chúng cũng giống nhau nên sau đây tôi sẽ chỉ trình diễn 1 trong 2 hàm .
Các hàm này nhận vào 2 thông số kỹ thuật : vị trí hiện tại của con trỏ và khoảng cách ( tính theo số ký tự ) bạn muốn thêm vào so với vị trí hiện tại .

luong.seekp(khoangCach, viTri);

Có 3 vị trí mốc ở trong tệp :

  • Đầu tệp :ios::beg
  • Cuối tệp :ios::end
  • Vị trí hiện tại :ios::cur

Ví dụ nếu muốn di chuyển 20 ký tự tính từ vị trí hiện tại, bạn có thể viết luong.seekp(20, ios::cur) hay 10 ký tự tính từ đầu tệp luong.seekp(10, ios::beg).

Xác định kích thước tệp

Chúng ta có thể thực hiện điều này dựa vào những điều vừa nói ở trên. Để xác định kích thước tệp, chúng ta sẽ đưa con trỏ tới cuối tệp và lấy ra kết quả vị trí của con trỏ. Do mỗi ký tự có giá trị là 1 byte, chúng ta sẽ xác định được dung lượng dữ liệu của tệp theo đơn vị byte. 1 ví dụ cụ thể để giúp bạn dễ hiểu.

#include 
#include 
using namespace std;

int main(){
    ifstream tep("C:/Applis/lttb/files/scores.txt");  //Mo tep
    tep.seekg(0, ios::end);  //Di toi cuoi tep
    int kichThuoc;
    kichThuoc = tep.tellg();
    //Lay ra gia tri kich thuoc tep
    cout << "Kich thuoc cua tep la : " << kichThuoc << " byte." << endl;
    return 0;
}

Thế là tất cả chúng ta đã điểm hết những khái niệm cơ bản tương quan đến tệp rồi. Phần phía sau còn cần những bạn tự học hỏi thêm nhiều .

Tóm tắt bài hoc :

  • Trong C++, để đọc và viết vào trong tệp, cần sử dụng gói .
  • Cần khởi tạo 1 đối tượng ofstream để ghi vào tệp và 1 đối tượng ifstream nếu muốn đọc dữ liệu từ tệp.
  • Thao tác ghi vào tệp giống với cout : luong << "Du lieu"; trong khi thao tác đọc từ tệp thì giống với cin : luong >> tenBien;
  • Có thể đọc theo dòng bằng cách sử dụng lệnh getline().
  • Con trỏ cho biết vị trí hiện tại của chúng ta ở trong tệp. Nếu cần thiết, chúng ta có thể dịch chuyển con trỏ đến vị trí chúng ta muốn.