C – Xuất/Nhập File (File I/O)

Học Lập Trình C - Xuất Nhập File (File I/O)

Xuất / Nhập File là một chủ đề không thể thiếu khi học lập trình C. Chương này trình bày cách để lập trình viên C tạo, mở, đóng file văn bản hoặc file nhị phân để lưu trữ dữ liệu của họ.

Một tệp (file) đại diện cho một chuỗi các byte, bất kể nó là file văn bản (text) hay file nhị phân (binary). Ngôn ngữ lập trình C cung cấp quyền truy cập vào các hàm cấp cao cũng như các lệnh gọi cấp thấp (cấp hệ điều hành) để xử lý file trên thiết bị lưu trữ của bạn. Chúng ta sẽ học các hàm gọi quan trọng để quản lý file.

Mở File

Bạn có thể sử dụng hàm fopen() để tạo (create) một file mới hoặc để mở (open) một file hiện có. Lệnh gọi này sẽ khởi tạo một đối tượng kiểu FILE, chứa tất cả thông tin cần thiết để kiểm soát stream. Prototype của lệnh gọi hàm này như sau:

FILE *fopen( const char * filename, const char * mode );

Ở đây, tên tệp là một chuỗi ký tự, bạn sẽ sử dụng để đặt tên tệp của mình và chế độ truy cập có thể có một trong các giá trị sau:

Sr.No.Mode & Description
1r
Mở một file text đang tồn tại để đọc (read).
2w
Mở một file text để ghi (write). Nếu nó chưa tồn tại, thì một file mới sẽ được tạo. Ở đây, chương trình của bạn sẽ bắt đầu ghi nội dung từ phần đầu của file.
3a
Mở một file text để ghi (write) ở chế độ bổ sung (appending mode). Nếu nó chưa tồn tại, thì một file mới sẽ được tạo. Ở đây, chương trình của bạn sẽ bắt đầu ghi bổ sung nội dung từ phần hiện có của file.
4r+
Mở một file text để đọc (read) và ghi (write).
5w+
Mở một file text để đọc (read) và ghi (write). Đầu tiên, nó cắt bỏ độ dài file thành 0 nếu nó tồn tại, ngược lại, tạo một file nếu không tồn tại.
6a+
Mở một file text để đọc (read) và ghi (write). Tạo một file nếu nó không tồn tại. Việc đọc sẽ bắt đầu từ đầu file, nhưng việc ghi có thể từ chỗ hiện có của file.

Nếu bạn muốn xử lý file binary, bạn cần sử dụng các chế độ truy cập sau thay vì các chế độ đã đề cập ở trên.

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

Đóng file

Để đóng một file, bạn hãy sử dụng hàm fclose(). Prototype của hàm này là.

int fclose( FILE *fp );

Hàm fclose() trả về 0 nếu thành công, hoặc EOF nếu có lỗi khi đóng file. Hàm này thực sự tống (flush) hết dữ liệu nào còn sót lại trong bộ đệm (buffer) vào file, đóng (close) file và giải phóng (release) bộ nhớ được sử dụng cho file. EOF là một hằng số được định nghĩa trong file header stdio.h.

Có nhiều hàm khác nhau được cung cấp bởi thư viện tiêu chuẩn C (C standard library) để đọc và ghi một file, bằng từng ký tự một, hoặc một chuỗi có độ dài cố định.

Ghi File

Dưới đây là hàm đơn giản nhất để ghi dữ liệu.

int fputc( int c, FILE *fp );

Hàm fputc() ghi giá trị ký tự (character) của đối số c tới stream output được tham chiếu bởi fp. Nó trả về ký tự được ghi thành công, ngược lại trả về EOF nếu có lỗi. Bạn có thể sử dụng hàm sau để ghi một chuỗi kết thúc bằng null (null-terminated string) tới một stream.

int fputs( const char *s, FILE *fp );

Hàm fputs() ghi chuỗi s vào stream output được tham chiếu bởi fp. Nó trả về một giá trị không âm (non-negative) nếu thành công, ngược lại EOF được trả về trong trường hợp có bất kỳ lỗi nào. Bạn cũng có thể sử dụng hàm int fprintf(FILE *fp,const char *format, …) để ghi một chuỗi vào một file. Hãy thử ví dụ sau.

Đảm bảo rằng bạn có sẵn thư mục /tmp. Nếu không, bạn phải tạo thư mục này trên máy của mình trước khi thực thi.

#include <stdio.h>

main() {
   FILE *fp;

   fp = fopen("/tmp/test.txt", "w+");
   fprintf(fp, "This is testing for fprintf...\n");
   fputs("This is testing for fputs...\n", fp);
   fclose(fp);
}

Khi đoạn code trên được biên dịch và thực thi, nó sẽ tạo một file test.txt mới trong thư mục /tmp và ghi hai dòng bằng hai hàm khác nhau. Chúng ta hãy để việc đọc file này ở phần tiếp theo.

Đọc File

Để đọc nội dung của một file, đơn giản nhất, ta có hàm đọc một ký tự từ một file.

int fgetc( FILE * fp );

Hàm fgetc() đọc một ký tự (character) từ file input được tham chiếu bởi fp. Giá trị trả về là ký tự được đọc, hoặc khi có lỗi thì trả về EOF. Hàm tiếp theo cho phép đọc một chuỗi (string) từ một stream.

char *fgets( char *buf, int n, FILE *fp );

Hàm fgets() đọc tới ký tự n-1 từ stream input được tham chiếu bởi fp. Nó copy chuỗi đọc được vào bộ đệm buf, bổ sung một ký tự null để kết thúc chuỗi.

Nếu hàm này gặp một ký tự xuống dòng (newline) ‘\n’ hoặc kết thúc file (end of file) EOF trước khi đọc hết số ký tự lớn nhất n, thì nó sẽ trả về chỉ những ký tự đọc được tới đây, bao gồm ký tự xuống dòng.

Bạn cũng có thể sử dụng hàm int fscanf(FILE *fp, const char *format, …) để đọc chuỗi từ một file. Nhưng nó dừng đọc sau khi gặp ký tự khoảng cách (space) đầu tiên.

#include <stdio.h>

main() {

   FILE *fp;
   char buff[255];

   fp = fopen("/tmp/test.txt", "r");
   fscanf(fp, "%s", buff);
   printf("1: %s\n", buff );

   fgets(buff, 255, (FILE*)fp);
   printf("2: %s\n", buff );
   
   fgets(buff, 255, (FILE*)fp);
   printf("3: %s\n", buff );
   fclose(fp);

}

Chúng ta hãy biên dịch và thực thi chương trình trên. Chương trình này đọc file đã được tạo ở phần trước và in ra kết quả như sau.

1: This
2: is testing for fprintf...

3: This is testing for fputs...

Hãy xem kỹ hơn một chút về những gì đã xảy ra ở đây. Đầu tiên, fscanf() chỉ đọc This này bởi vì sau đó, nó gặp phải một ký tự khoảng cách (space). Lệnh gọi thứ hai là fgets() để đọc phần còn lại của dòng đầu cho đến khi nó gặp ký tự xuống dòng (newline). Cuối cùng, lệnh gọi fgets() thứ hai đọc toàn bộ dòng cuối.

Học lập trình C - Các hàm xuất nhập file

Hàm I/O Binary

Có 2 hàm được dùng để đọc input và output binary.

size_t fread(void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);   
size_t fwrite(const void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);

Cả hai hàm này nên được dùng để đọc (read) và ghi (write) từng khối bộ nhớ (blocks of memories), thường là mảng (array) hoặc cấu trúc (structure).

Để hiểu chi tiết về các hàm xuất nhập file vừa học, tham khảo nội dung thư viện stdio.h.

(Tiếp theo)

Icons made by Freepik from www.flaticon.com