Текстовые файлы и списки
Зачем файлы
До сих пор данные вводились с клавиатуры и жили только во время запуска. Файл позволяет сохранить данные между запусками и обрабатывать большие наборы без ручного ввода.
В ЛР3 используются два фиксированных файла:
input.txt— входные данные;output.txt— результат.
Данные — только числа. Строковые функции и массивы char как данные не используются.
Открытие и закрытие
#include <stdio.h>
FILE *in = fopen("input.txt", "r");
if (in == NULL)
{
perror("input.txt");
return 1;
}
/* ... чтение ... */
fclose(in);
Режимы:
| Режим | Что делает |
|---|---|
"r" | чтение |
"w" | запись, файл создаётся/перезаписывается |
"a" | дописывание в конец |
Для output.txt:
FILE *out = fopen("output.txt", "w");
if (out == NULL)
{
perror("output.txt");
return 1;
}
/* ... запись ... */
if (fclose(out) != 0)
{
return 1;
}
fclose тоже может завершиться ошибкой, особенно для записи. Проверяйте результат.
Чтение чисел
int n;
if (fscanf(in, "%d", &n) != 1 || n < 0)
{
fclose(in);
return 1;
}
Для double используйте %lf:
double score;
if (fscanf(in, "%lf", &score) != 1)
{
return 1;
}
fscanf возвращает число успешно прочитанных полей. Сравнивайте с ожидаемым количеством,
а не с EOF напрямую.
Запись чисел
fprintf(out, "%d %d %.2f\n", student.id, student.course, student.score);
Для double в fprintf используется %f или %.2f (не %lf).
Чтение списка из файла
typedef struct
{
int id;
int course;
double score;
} Student;
typedef struct Node
{
Student data;
struct Node *next;
} Node;
Node *load_list(FILE *in, int n)
{
Node *head = NULL;
Node *tail = NULL;
for (int i = 0; i < n; i++)
{
Student s;
if (fscanf(in, "%d %d %lf", &s.id, &s.course, &s.score) != 3)
{
/* освободить уже созданные узлы */
return NULL;
}
Node *node = malloc(sizeof(*node));
if (node == NULL)
{
/* освободить список */
return NULL;
}
node->data = s;
node->next = NULL;
if (tail == NULL)
{
head = tail = node;
}
else
{
tail->next = node;
tail = node;
}
}
return head;
}
В реальной функции при ошибке нужно освободить уже созданные узлы, а не просто вернуть
NULL. Это важно, иначе получится утечка.
Запись списка в файл
void save_list(FILE *out, const Node *head)
{
for (const Node *p = head; p != NULL; p = p->next)
{
fprintf(out, "%d %d %.2f\n", p->data.id, p->data.course, p->data.score);
}
}
Обработка ошибок
Проверяйте:
fopen— файл существует и доступен;fscanf— прочитано ожидаемое число полей;malloc— память выделена;fprintf/fclose— запись завершилась успешно.
При ошибке:
- вывести диагностику в
stderr/консоль; - освободить память;
- закрыть открытые файлы;
- вернуть ненулевой код.
Результат в штатном режиме пишется только в output.txt.
Частые ошибки
| Ошибка | Что не так |
|---|---|
не проверили fopen | работа с NULL |
%f вместо %lf в fscanf для double | неверное чтение |
%lf в fprintf для double | лишний l, но обычно допустим; лучше %f |
не проверили fscanf | «мусор» в данных |
| не закрыли файл | потеря данных при записи |
не проверили fclose | ошибка записи осталась незамеченной |
| результат вывели в консоль | требование не выполнено |
| забыли освободить список при ошибке | утечка |
Документация
Что запомнить
fopenоткрывает файл,fcloseзакрывает; проверяйте оба.fscanf/fprintfработают какscanf/printf, но сFILE *.- Для
doubleпри чтении —%lf, при записи —%f. - Загрузка списка из файла — это чтение записей и создание узлов.
- При ошибке освободите память и верните ненулевой код.