Xử lý tệp văn bản và thống kê dữ liệu trong C

Câu hỏi 1: Vòng lặp đọc tệp kết thúc khi nào?

Đáp án: Khi toàn bộ dữ liệu trong tệp đã được đọc xong, hàm feof() sẽ trả về giá trị khác 0, từ đó điều kiện kiểm tra trong vòng lặp trở thành sai và vòng lặp kết thúc.

Câu hỏi 2: Vì sao cần kiểm tra EOF ngay sau khi gọi fgetc(), thay vì chỉ dựa vào feof() ở đầu vòng lặp?

Đáp án: Hàm feof() chỉ báo hiệu trạng thái sau khi một thao tác đọc thất bại do chạm cuối tệp. Nếu không kiểm tra giá trị trả về của fgetc(), chương trình có thể thực hiện một lần đọc thừa — dẫn đến xử lý ký tự không hợp lệ hoặc tràn bộ đệm.

Câu hỏi 3: Ký tự \' trong chuỗi định dạng hoặc tệp văn bản được xử lý như thế nào?

Đáp án: Trình biên dịch nhận diện \' như một ký tự đơn được thoát (escape sequence), tương đương với dấu nháy đơn đơn thuần '. Khi đọc bằng fscanf() hoặc fgets(), nó sẽ được lưu nguyên dạng trong mảng ký tự.

Bài tập 4: Đếm số ký tự và số dòng trong tệp

Dưới đây là phiên bản cải tiến của đoạn mã gốc — loại bỏ việc sử dụng feof() như điều kiện chính của vòng lặp, đồng thời tối ưu hóa logic đếm và xử lý lỗi:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main() {
    FILE* input_file = fopen("data4.txt", "r");
    if (!input_file) {
        fprintf(stderr, "Lỗi: Không thể mở tệp 'data4.txt'\n");
        return -1;
    }

    int char_count = 0;
    int line_count = 1; // Bắt đầu từ 1 vì dòng đầu tiên luôn tồn tại nếu tệp không rỗng
    int current_char;

    while ((current_char = fgetc(input_file)) != EOF) {
        if (current_char == '\n') {
            line_count++;
        } else if (current_char != ' ' && current_char != '\t') {
            char_count++;
        }
    }

    fclose(input_file);
    printf("Tổng số ký tự (không tính khoảng trắng và tab): %d\n", char_count);
    printf("Tổng số dòng: %d\n", line_count);

    return 0;
}

Bài tập 5: Quản lý danh sách thí sinh và thống kê điểm thi

Phiên bản sau đã được tái cấu trúc để khắc phục các vấn đề tiềm ẩn trong mã gốc — đặc biệt là cách đọc tệp bằng fscanf() trong vòng lặp while(!feof()), vốn dễ gây lỗi khi dữ liệu không khớp định dạng. Đồng thời, bổ sung xử lý rõ ràng cho cột result và đảm bảo tính nhất quán giữa các hàm.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

#define MAX_STUDENTS 10

typedef struct {
    long id;
    char name[20];
    float objective_score;
    float subjective_score;
    float total_score;
    char status[10]; // "Qua", "Truot"
} Student;

void load_from_file(Student students[], int n) {
    FILE* file = fopen("examinee.txt", "r");
    if (!file) {
        fprintf(stderr, "Lỗi: Không thể mở tệp 'examinee.txt'\n");
        return;
    }

    for (int i = 0; i < n && !feof(file); ++i) {
        if (fscanf(file, "%ld %19s %f %f", 
                   &students[i].id, 
                   students[i].name, 
                   &students[i].objective_score, 
                   &students[i].subjective_score) != 4) {
            break; // Dừng nếu thiếu dữ liệu hoặc định dạng sai
        }
        students[i].total_score = students[i].objective_score + students[i].subjective_score;
        strcpy(students[i].status, students[i].total_score >= 60.0f ? "Qua" : "Truot");
    }
    fclose(file);
}

void print_all(const Student students[], int n) {
    printf("ID\tTen\tDiemKhachQuan\tDiemThucHanh\tTong\tTrangThai\n");
    for (int i = 0; i < n; ++i) {
        printf("%ld\t%s\t%.2f\t\t%.2f\t\t%.2f\t%s\n",
               students[i].id,
               students[i].name,
               students[i].objective_score,
               students[i].subjective_score,
               students[i].total_score,
               students[i].status);
    }
}

void save_passed_to_file(const Student students[], int n) {
    FILE* out = fopen("list_pass.txt", "w");
    if (!out) {
        fprintf(stderr, "Lỗi: Không thể tạo tệp 'list_pass.txt'\n");
        return;
    }

    fprintf(out, "ID\tTen\tDiemKhachQuan\tDiemThucHanh\tTong\tTrangThai\n");
    for (int i = 0; i < n; ++i) {
        if (students[i].total_score >= 60.0f) {
            fprintf(out, "%ld\t%s\t%.2f\t\t%.2f\t\t%.2f\t%s\n",
                    students[i].id,
                    students[i].name,
                    students[i].objective_score,
                    students[i].subjective_score,
                    students[i].total_score,
                    students[i].status);
        }
    }
    fclose(out);
}

int count_passed(const Student students[], int n) {
    int passed = 0;
    for (int i = 0; i < n; ++i) {
        if (students[i].total_score >= 60.0f) ++passed;
    }
    return passed;
}

int main() {
    Student candidates[MAX_STUDENTS];
    Student passed_list[MAX_STUDENTS];

    printf("Đang tải dữ liệu từ tệp...\n");
    load_from_file(candidates, MAX_STUDENTS);

    printf("\nDanh sách thí sinh:\n");
    print_all(candidates, MAX_STUDENTS);

    printf("\nĐang ghi danh sách đạt yêu cầu vào tệp...\n");
    save_passed_to_file(candidates, MAX_STUDENTS);

    int passed_count = count_passed(candidates, MAX_STUDENTS);
    double pass_rate = (double)passed_count / MAX_STUDENTS * 100.0;

    printf("\nTỷ lệ đỗ: %.2f%% (%d/%d)\n", pass_rate, passed_count, MAX_STUDENTS);

    return 0;
}

Bài tập 6: Chọn ngẫu nhiên người may mắn từ danh sách

Mã dưới đây sửa lỗi nghiêm trọng trong phiên bản gốc (ví dụ: dùng fp thay vì fin khi đóng tệp, khởi tạo sai mảng ln[], thiếu kiểm tra con trỏ tệp trước khi ghi). Đồng thời áp dụng cơ chế chọn không trùng lặp an toàn hơn bằng tập hợp đánh dấu.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#define MAX_LINES 80
#define MAX_LINE_LEN 80
#define WINNERS_COUNT 5

int main() {
    char lines[MAX_LINES][MAX_LINE_LEN];
    int used_indices[MAX_LINES] = {0};
    char output_filename[MAX_LINE_LEN];

    FILE* input = fopen("list.txt", "r");
    if (!input) {
        fprintf(stderr, "Lỗi: Không thể mở tệp 'list.txt'\n");
        return -1;
    }

    int line_count = 0;
    while (line_count < MAX_LINES && fgets(lines[line_count], MAX_LINE_LEN, input)) {
        // Loại bỏ ký tự newline nếu có
        size_t len = strlen(lines[line_count]);
        if (len > 0 && lines[line_count][len-1] == '\n') {
            lines[line_count][len-1] = '\0';
        }
        line_count++;
    }
    fclose(input);

    if (line_count == 0) {
        fprintf(stderr, "Tệp 'list.txt' rỗng.\n");
        return -1;
    }

    srand((unsigned)time(NULL));
    printf("Người may mắn:\n");

    for (int i = 0; i < WINNERS_COUNT && i < line_count; ++i) {
        int idx;
        do {
            idx = rand() % line_count;
        } while (used_indices[idx]);

        used_indices[idx] = 1;
        printf("%s\n", lines[idx]);
    }

    printf("Nhập tên tệp để lưu kết quả: ");
    scanf("%79s", output_filename);

    FILE* output = fopen(output_filename, "w");
    if (!output) {
        fprintf(stderr, "Lỗi: Không thể tạo tệp '%s'\n", output_filename);
        return -1;
    }

    for (int i = 0; i < line_count; ++i) {
        if (used_indices[i]) {
            fprintf(output, "%s\n", lines[i]);
        }
    }
    fclose(output);
    printf("Đã lưu %d bản ghi vào '%s'.\n", WINNERS_COUNT, output_filename);

    return 0;
}

Thẻ: C-Language file-io stdio fscanf fgets

Đăng vào ngày 23 tháng 9 lúc 23:36