Я пытаюсь создать связанный список, в который кто-то может ввести названия некоторых фильмов и свой рейтинг. Вот что у меня есть на данный момент:
#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#define TITLE_MAX 50
void mygets(char buffer[], size_t limit);
// linked list
struct film {
char title[TITLE_MAX];
int rating;
struct film *next;
};
int main(void)
{
struct film *head = NULL, *current = NULL;
char tbuffer[TITLE_MAX];
while (true)
{
printf("Enter the name of the film (empty line to stop)n");
mygets(tbuffer, TITLE_MAX);
if (*tbuffer==' ') break;
if (!head)
current = head = malloc (sizeof(struct film));
else
// assignment associates right-to-left so can do this trick
current = current->next = malloc (sizeof(struct film));
current->next = NULL;
strcpy(current->title, tbuffer);
printf("Now enter in your rating for it: ");
scanf("%d", &(current->rating));
for (int c; (c=getchar()) != 'n' && c != EOF; ); // clean up garbage in scanf -- should probably write a function for this
}
// print the Linked List
current = head;
for (int i=1; current != NULL; current=current->next)
printf("Linked List Item #%d: %s (Rating: %d)n",
i++, current->title, current->rating
);
for(struct film *current=head, *next; current != NULL; current=next) {
next = current->next;
free(current);
}
}
void mygets(char buffer[], size_t limit)
{
// first non-space char until newline or EOF,
// eating any extra chars in buffer
// will add at the end, so will get up to N-1 chars
int c, idx=0;
bool started=false;
while ((c=getchar()) != EOF && c != 'n')
{
if (!started && isspace(c));
if (idx < limit-1)
buffer[idx++] = c;
}
buffer[idx] = ' ';
}
Как это выглядит до сих пор? Что можно улучшить? Кроме того, в реальных реализациях связанного списка, какие дополнительные метаданные хранятся — например, такие вещи, как размер списка и тому подобное.
Кроме того, как free загляните туда. Для связанного списка имеет значение, в каком направлении я освобождаю данные?
1 ответ
Ошибки
Быстрая ошибка:
if (!started && isspace(c));не имеет никакого эффекта. Ты хотелcontinue;или что-то там? Как бы то ни было, вы все же сохраните ведущие пробелы в буфере, что, по-видимому, не то, что вам нужно.gcc -Wextraдает предупреждение, кстати.(Вы тестировали программу на строках ввода с ведущими пробелами? Я не думаю, что это могло бы когда-либо сработать.)
И в настоящее время у вас нет возможности
startedкогда-либо стать правдой …
Улучшения качества кода
mygetsне лучший выбор для имени вашей функции. Читатель неправильно догадается, что он ведет себя как стандартныйfgets(или того хуже, ранее стандартныйgets), но отличается тем, что (предполагается) убирает ведущие пробелы. Я бы выбрал что-то более похожее наget_film_title. Вы можете попытаться объяснить в имени функции, что она удаляет пробелы, но это может сделать его слишком длинным.Я бы разделил три задачи по заполнению списка, его распечатке и освобождению на три отдельные функции, вместо того, чтобы сбрасывать их все в
main.В
while (true)идиома сbreak;заставляет читателя искать в теле цикла условие завершения. Посмотрите, сможете ли вы придумать чистый способ реструктуризации, чтобы избежать этого. Если нет, по крайней мере добавьте комментарий, чтобы объяснить, когда цикл завершится.Посмотрите, сможете ли вы реструктурировать
if (!head)чтобы не писатьmallocвызовите дважды, что представляет опасность, если вы в конечном итоге захотите изменить его, но пропустите обновление обоих экземпляров.Некоторые предложат
malloc(sizeof *current)вместоsizeof(struct film). Я тоже не испытываю сильных чувств.Каждый раз, когда вам нужен комментарий для объяснения «трюка», когда комментарий длиннее, чем дополнительный код, который его не нужно вводить, вы, вероятно, пытаетесь быть слишком умным. (И я нашел это сбивающим с толку даже комментарием.) Просто запишите два утверждения.
Рассмотрите возможность переноса задачи чтения рейтинга и «очистки входного потока» в отдельную функцию. Это неудобно, когда часть операций ввода-вывода передается подпрограмме (
mygets), а некоторые остались позади.for (int c; ...): Аforцикл, в котором ничего делать не нужно либо из предложение init или итерация-выражение вероятно, должен быть простоwhile.В коде печати хорошо сохранить
forсосредоточен на собственном итераторе цикла, который здесьcurrent, а не смешивать его междуcurrentиi(и поскольку последний не является итератором, он может использовать другое имя). И незачем хоронить приращениеiвнутри заявления, которое делает что-то еще. Я бы написал:
int film_number = 1;
for (current = head; current != NULL; current = current->next) {
printf("...", film_number, current->title, current->rating);
film_number++;
}
Почему в коде освобождения вы объявляете новую переменную с именем
current, слежка за существующей?В
forцикл и использование переменнойnextв коде освобождения может быть умнее, чем читабельнее. Я бы написал проще:
current = head;
while (current) {
struct film *tmp = current;
current = current->next;
free(tmp);
}
Я думаю, что ваш план по освобождению списка от начала до конца подходит. С односвязным списком не так-то просто сделать наоборот. (Если вы не хотите вместо этого создать список с двойной связью.
Проверка ошибок для каждого ввода и функции распределения памяти. Да, это скучно, но необходимо для любой реальной программы, и даже для игрушечных программ это очень полезно для отладки.
Предлагаемые улучшения
Что касается дополнительных метаданных: храните только то, что вам нужно. Если знание размера или последнего элемента станет полезным для вашего приложения, добавьте его. Но я не предлагаю переоценивать полностью общий и многофункциональный связанный список для конкретной программы; если вы хотите заново изобрести общий контейнер связанного списка, это отдельный проект (и тот, который может быть лучше для языка, который действительно поддерживает универсальное программирование).
С помощью
scanfчтение пользовательского ввода не является идеальным за пределами игрушечных программ, так как затрудняет обнаружение неправильного ввода и восстановление после него. Лучше всего прочитать всю строку ввода и проанализировать ее с помощьюsscanf.Рассмотрите возможность использования динамического размещения, чтобы названия фильмов имели произвольную длину. В противном случае пользователь, который хочет оценить Англичанин, который поднялся на холм, но спустился с горы (57) будет разозлен.
Программы командной строки, которые запрашивают интерактивный ввод и заканчиваются чем-то вроде пустой строки, редко встречаются за пределами игрушечных примеров. Во-первых, их сложно адаптировать для пакетной обработки или конвейеров. Полезные программы командной строки чаще всего не запрашивают и читают ввод до тех пор, пока не встретится конец файла.
