Похоже, вам нужна помощь, чтобы собрать все кусочки головоломки вместе. Во-первых, в typedef
из date
вы включаете char *month
. Это указатель, который будет неинициализирован при выделении ar
, что означает, что вам потребуется отдельное выделение для ar[i].month
. Вы можете сделать это (в этом случае вы можете эффективно использовать strdup
), но почему? Если вы вводите строку месяца, то максимальная длина составляет 10 символов (September
+ nul-byte
). Просто используйте статически объявленные month
или 10
или более символов и избегайте динамического выделения month
.
Например, вы можете объявить полезные константы для использования в вашем коде либо с отдельными директивами #define
, либо вы можете использовать глобальную директиву enum
для достижения той же цели, например.
/* constants for max chars, max day, max year, max size */
enum { MAXC = 12, MAX_DAY = 31, MAX_YEAR = 2017, MAX_SIZE = 1000 };
typedef struct {
char month[MAXC]; /* either make static or allocate separately */
unsigned day;
unsigned year;
} date;
Следующая проблема, с которой вы столкнетесь, — это смешивание символьного и числового ввода в scanf
, которое не очищает буфер ввода (например, stdin
) при каждом вызове. Это означает, что если пользователь вводит что-то отличное от действительного десятичного числа для 'n'
(например, если он случайно нажимает 'q'
вместо '1'
), "q\n"
остается во входном буфере, который будет принят в качестве вашего ввода для ar[0].month
ниже. Чтобы этого не произошло, вам нужно вручную очистить входной буфер (или вместо этого использовать fgets
, а затем sscanf
для разбора пользовательского ввода — при использовании scanf
для пользовательского ввода есть много ловушек).
Несмотря на это, вы можете довольно легко опустошить stdin
. Вы можете сделать это встроенным с помощью int c; while ((c = getchar()) != '\n' && c != EOF) {}
или создать короткую функцию, если вы будете вызывать ее неоднократно, чтобы сократить набор текста, например:
/* empty character remaining in stdin */
void empty_stdin ()
{
int c;
while ((c = getchar ()) != '\n' && c != EOF) {}
}
Когда вы вводите данные (будь то с помощью семейства функций scanf
или с помощью fgets
(или любым другим способом), всегда проверяйте пользовательский ввод. Насколько вам известно, кошка может наступать на клавиатуру. Кроме того, всегда проверяйте наличие EOF
, которое указывает, что пользователь отменил ввод с помощью Ctrl+d или Ctrl+z (в windows). Например:
while (1) { /* obtain valid 'n', compare with using fgets below */
int rtn; /* varaible to save return of scanf -- always validate */
printf ("Enter number of dates to be entered (between 1 & 1000): ");
if ((rtn = scanf ("%d", &n)) != 1) { /* if conversion failed */
if (rtn == EOF) { /* test for user cancelation of input */
fprintf (stderr, "note: user canceled input, exiting.\n");
return 0;
} /* otherwise simply an invalid input */
fprintf (stderr, "error: invalid input.\n");
goto tryagain;
}
if (n < 0) { /* invalid input < 0 */
fprintf (stderr, "error: invalid input (n < 0).\n");
goto tryagain;
}
if (n > MAX_SIZE) { /* invalid input > MAX_SIZE */
fprintf (stderr, "error: invalid input (n > %d).\n", MAX_SIZE);
goto tryagain;
}
break; /* if we are here - we have a good value, break */
tryagain:; /* label for goto to jump over break */
empty_stdin (); /* empty characters that remain in input buffer */
}
Сравните с использованием fgets
и sscanf
для чтения/анализа ввода month
, day
, year
. Вы можете сделать что-то простое, например:
for (i = 0; i < n;) { /* loop until all elements filled */
char buf[MAX_DAY + 1] = "", ans[MAXC] = "";
/* if fgets return is NULL, EOF encountered */
if (fgets (buf, MAX_DAY + 1, stdin) == NULL) {
fprintf (stderr, "note: user canceled input, exiting.\n");
return 0;
}
/* parse with sscanf, validate 3 conversion took place */
if (sscanf (buf, "%11s %u %u", ar[i].month, &ar[i].day, &ar[i].year) != 3)
{
fprintf (stderr, "error: invalid input.\n");
continue;
}
i++; /* only increment if valid sscanf conversion took place */
}
Нет необходимости передавать int i
в качестве параметра функции output
, просто объявите его локально, например:
/* output n elements of array of struct date */
void output (date *ar, int n)
{
int i;
printf ("\nOutput sorted by year:\n\n");
for (i = 0; i < n; i++)
printf (" %s %d %d\n", ar[i].month, ar[i].day, ar[i].year);
}
Далее, хотя ваша функция sort
может работать, вы можете сжать сортировку по годам, избегая потенциального переполнения, используя вместо этого неравенства:
/* sort struct date on year */
int sort (const void *a, const void *b)
{
date *date1 = (date *) a;
date *date2 = (date *) b;
if (date2->year != date1->year)
return (date1->year > date2->year) - (date1->year < date2->year);
return 0;
}
Наконец, если вы выделяете память, вы несете ответственность за сохранение указателя на начало блока, а затем на free
память, когда она больше не нужна. Пока она будет освобождена exit
, заведите привычку отслеживать и освобождать всю выделенную память. Хорошие привычки сослужат вам хорошую службу, когда вы будете работать над более сложными проектами.
Собрав все вместе и добавив запрос на выход, если пользователь просто нажимает Enter вместо ввода даты, вы можете сделать что-то вроде следующего:
#include <stdio.h>
#include <stdlib.h>
/* constants for max chars, max day, max year, max size */
enum { MAXC = 12, MAX_DAY = 31, MAX_YEAR = 2017, MAX_SIZE = 1000 };
typedef struct {
char month[MAXC]; /* either make static or allocate separately */
unsigned day;
unsigned year;
} date;
/* empty character remaining in stdin */
void empty_stdin ()
{
int c;
while ((c = getchar ()) != '\n' && c != EOF) {}
}
/* sort struct date on year */
int sort (const void *a, const void *b)
{
date *date1 = (date *) a;
date *date2 = (date *) b;
if (date2->year != date1->year)
return (date1->year > date2->year) - (date1->year < date2->year);
return 0;
}
/* output n elements of array of struct date */
void output (date *ar, int n)
{
int i;
printf ("\nOutput sorted by year:\n\n");
for (i = 0; i < n; i++)
printf (" %s %d %d\n", ar[i].month, ar[i].day, ar[i].year);
}
int main (void) {
int i, n;
date *ar = NULL;
while (1) { /* obtain valid 'n', compare with using fgets below */
int rtn; /* varaible to save return of scanf -- always validate */
printf ("Enter number of dates to be entered (between 1 & 1000): ");
if ((rtn = scanf ("%d", &n)) != 1) { /* if conversion failed */
if (rtn == EOF) { /* test for user cancelation of input */
fprintf (stderr, "note: user canceled input, exiting.\n");
return 0;
} /* otherwise simply an invalid input */
fprintf (stderr, "error: invalid input.\n");
goto tryagain;
}
if (n < 0) { /* invalid input < 0 */
fprintf (stderr, "error: invalid input (n < 0).\n");
goto tryagain;
}
if (n > MAX_SIZE) { /* invalid input > MAX_SIZE */
fprintf (stderr, "error: invalid input (n > %d).\n", MAX_SIZE);
goto tryagain;
}
break; /* if we are here - we have a good value, break */
tryagain:; /* label for goto to jump over break */
empty_stdin (); /* empty characters that remain in input buffer */
}
empty_stdin (); /* empty characters that remain in input buffer */
/* allocate array of struct ar, n elements */
if ((ar = malloc (sizeof *ar * n)) == NULL) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
/* provide format instructions */
printf ("Enter the date (month day year)\n"
" format, e.g.: Jan 18 2017\n\n");
for (i = 0; i < n;) { /* loop until all elements filled */
char buf[MAX_DAY + 1] = "", ans[MAXC] = "";
printf (" date[%2d] : ", i + 1); /* prompt for input */
/* if fgets return is NULL, EOF encountered */
if (fgets (buf, MAX_DAY + 1, stdin) == NULL) {
fprintf (stderr, "note: user canceled input, exiting.\n");
return 0;
}
if (*buf == '\n') { /* if first char is '\n', user just hit enter */
printf ("no input provided, quit (y/n)? ");
if (fgets (ans, MAXC, stdin) && (*ans == 'y' || *ans == 'Y'))
return 0;
else if (!*ans) { /* if ans NULL, EOF encountered */
fprintf (stderr, "note: user canceled input, exiting.\n");
return 0;
}
}
/* parse with sscanf, validate 3 conversion took place */
if (sscanf (buf, "%11s %u %u", ar[i].month, &ar[i].day, &ar[i].year) != 3)
{
fprintf (stderr, "error: invalid input.\n");
continue;
}
i++; /* only increment if valid sscanf conversion took place */
}
qsort (ar, n, sizeof (date), sort); /* sort by year */
output (ar, n); /* output results */
free (ar); /* free ar - you allocate it, you free it */
return 0;
}
Примечание: существует множество способов приблизиться практически к каждой части кода. Если вы посмотрите, где находится большая часть строк, они используются для проверки ввода. Это всего лишь минимум проверки. В идеале вы должны сравнить значения для каждого day
и year
с максимальными/минимальными значениями, и вы должны сравнить каждый month
с таблицей поиска (или хэш-таблицей), чтобы подтвердить, что каждый месяц является допустимым месяцем (вы также можете использовать функции даты/времени, но осталось для другого вопроса)
Пример использования/вывода
$ ./bin/qsortstruct
Enter number of dates to be entered (between 1 & 1000): 4
Enter the date (month day year)
format, e.g.: Jan 18 2017
date[ 1] : September 11 2001
date[ 2] : April 22 2010
date[ 3] : June 2 1968
date[ 4] : February 13 1979
Output sorted by year:
June 2 1968
February 13 1979
September 11 2001
April 22 2010
Просмотрите все, убедитесь, что вы понимаете каждую часть того, что происходит, и спросите, есть ли у вас дополнительные вопросы.
person
David C. Rankin
schedule
18.03.2017
sort()
, когда год совпадает. Компилятор должен был выдать предупреждение: не все пути управления возвращают значение. Пока вы этого не сделаете,return 0
. - person Weather Vane   schedule 18.03.2017return 0
не решает мою проблему? Вывод по-прежнему 0 - person LookingWest   schedule 18.03.2017date* ar = malloc(sizeof(int) * n);
не хватает памяти. - person aschepler   schedule 18.03.2017sizeof(*ar) * n
(или, что то же самое,sizeof(date) * n
. - person ShadowRanger   schedule 18.03.2017