Xử lý tệp trong C: Mở, đóng, đọc/ghi tuần tự và truy cập ngẫu nhiên

Mở và đóng tệp

Trong ngôn ngữ C, thao tác với tệp bắt đầu bằng việc mở tệp thông qua hàm fopen(). Hàm này nhận hai đối số: tên tệp và chế độ mở. Một số chế độ phổ biến bao gồm:

  • "r": Chỉ đọc, tệp phải tồn tại.
  • "w": Ghi, nếu tệp đã tồn tại sẽ bị ghi đè; nếu không, tạo mới.
  • "a": Ghi thêm vào cuối tệp mà không làm mất dữ liệu cũ.

Nếu mở tệp thất bại (ví dụ: quyền hạn không đủ hoặc đường dẫn sai), fopen() trả về NULL. Do đó, luôn cần kiểm tra giá trị trả về để đảm bảo thao tác an toàn.

FILE* file_pointer = fopen("du_lieu.txt", "w");
if (file_pointer == NULL) {
    perror("Loi khi mo tep");
    return 1;
}

Sau khi hoàn tất thao tác, dùng fclose() để đóng tệp, giải phóng tài nguyên và đảm bảo mọi dữ liệu được lưu đầy đủ lên ổ đĩa.

fclose(file_pointer);
file_pointer = NULL; // Tránh con trỏ treo

Đọc và ghi tuần tự

Cung cấp nhiều hàm để xử lý dữ liệu theo từng đơn vị nhỏ hoặc khối lớn.

Đọc/ghi ký tự

Dùng fgetc() để đọc một ký tự từ luồng tệp. Khi đến cuối tệp hoặc có lỗi, hàm trả về EOF.

int ky_tu = fgetc(file_pointer);

Dùng fputc() để ghi một ký tự ra tệp.

fputc('x', file_pointer);

Đọc/ghi chuỗi

Hàm fgets() đọc một dòng từ tệp, bao gồm cả ký tự xuống dòng nếu còn chỗ trong bộ đệm.

char buffer[512];
fgets(buffer, sizeof(buffer), file_pointer);

Hàm fputs() ghi chuỗi vào tệp nhưng KHÔNG tự động thêm ký tự \n.

fputs("Xin chao, day la du lieu!", file_pointer);

Đọc/ghi khối dữ liệu

Phù hợp khi làm việc với cấu trúc hoặc mảng lớn. Dùng fread()fwrite() để truyền dữ liệu dạng nhị phân.

// Giả sử có một cấu trúc
struct SinhVien {
    char ten[50];
    int tuoi;
};

struct SinhVien sv = {"Nguyen Van A", 20};

// Ghi cấu trúc ra tệp
fwrite(&sv, sizeof(sv), 1, file_pointer);

// Đọc lại cấu trúc
fread(&sv, sizeof(sv), 1, file_pointer);

Di chuyển con trỏ tệp (truy cập ngẫu nhiên)

Cho phép di chuyển đến bất kỳ vị trí nào trong tệp để đọc/ghi không theo thứ tự.

  • fseek(file_pointer, offset, vi_tri_goc): Di chuyển con trỏ tới vị trí xác định bởi offset so với gốc. Các hằng gốc:
    • SEEK_SET: Từ đầu tệp.
    • SEEK_CUR: Từ vị trí hiện tại.
    • SEEK_END: Từ cuối tệp.
fseek(file_pointer, 0L, SEEK_SET); // Về đầu tệp
  • ftell(): Trả về vị trí hiện tại của con trỏ tệp tính từ đầu tệp (đơn vị byte).
long vi_tri = ftell(file_pointer);
printf("Vi tri hien tai: %ld\n", vi_tri);
  • rewind(): Đặt lại con trỏ về đầu tệp, tương đương fseek(fp, 0L, SEEK_SET), đồng thời xóa các cờ lỗi và EOF.
rewind(file_pointer);

Kiểm tra trạng thái tệp: ferror() và feof()

Để phát hiện lỗi hoặc kết thúc tệp trong quá trình xử lý, C cung cấp hai hàm quan trọng.

ferror()

Kiểm tra xem có lỗi nào xảy ra trong lần thao tác gần nhất trên luồng tệp hay không. Nếu có lỗi, trả về giá trị khác 0.

FILE* fp = fopen("input.txt", "r");
if (fp != NULL) {
    int c;
    while ((c = fgetc(fp)) != EOF) {
        putchar(c);
    }
    if (ferror(fp)) {
        printf("Loi doc du lieu!\n");
    }
    fclose(fp);
}

feof()

Kiểm tra xem con trỏ đã đạt đến cuối tệp chưa. Trả về giá trị khác 0 nếu đúng.

while (!feof(fp)) {
    int c = fgetc(fp);
    if (c != EOF) {
        // Xử lý ký tự
    }
}
Lưu ý: Không nên dùng while(!feof(fp)) như điều kiện lặp chính vì có thể dẫn đến đọc dư dữ liệu. Cách tốt hơn là kiểm tra giá trị trả về của hàm đọc (như fgetc() trả về EOF).

Khuyến nghị thực hành

  • Luôn kiểm tra kết quả trả về của các hàm thao tác tệp.
  • Dùng perror() hoặc strerror(errno) để hiển thị thông báo lỗi chi tiết.
  • Đảm bảo đóng tệp sau khi sử dụng để tránh rò rỉ tài nguyên.
  • Sử dụng clearerr() nếu muốn đặt lại trạng thái lỗi hoặc EOF trước khi tiếp tục thao tác.

Thẻ: fopen fclose fgetc fputc fgets

Đăng vào ngày 23 tháng 6 lúc 00:01