Ваша непосредственная проблема была отмечена в комментарии, и вы были направлены на Почему while (! Feof (file)) всегда неверно? .Всегда проверяйте цикл чтения на самой функции чтения или выполняйте цикл постоянно и проверяйте возвращение функции чтения внутри цикла и break;
цикла, когда произошло успешное чтение и все ограничения выполнены.
fp = fopen("history.txt", "r");
Не кодируйте имена файлов жестко и не используйте magic-numbers в вашем коде.Для имен файлов либо передайте имя файла для чтения в качестве аргумента вашей программе (для этого argv
), либо запросите его ввод.(желательно передать это в качестве аргумента).Если вам нужны константы в вашем коде, #define
один (или более) или используйте глобальный enum
для достижения той же цели.
Теперь суть вашей проблемы.В любое время, когда вам приходится обрабатывать различные типы значений как одну запись или объект, вы должны подумать об использовании структуры для координации различных типов как одного объекта.Здесь вы можете объявить struct
с int
членами для хранения значений процента и типа теста и char[]
(или char*
и выделить) для хранения информации об имени и дате.Затем вы можете просто объявить массив struct для хранения всех значений, прочитанных из вашего history.txt
файла, который можно легко отсортировать с помощью qsort
на любом из выбранных вами членов.
Сначала, объявите простую структуру,Например,
struct users { /* simple struct to hold values read from input */
int pct,
test;
char name[MAXNM],
date[MAXDT];
};
Затем вы можете просто объявить массив с помощью struct users array[50];
. Однако вы можете упростить свою жизнь и избежать необходимости вводить struct users ...
снова и снова, создавая typedef
.В C вы даже можете удалить метку struct users
и добавить метку typedef в конце, например,
typedef struct { /* simple struct w/typedef to hold values read from input */
int pct,
test;
char name[MAXNM],
date[MAXDT];
} userhist_t;
Теперь вы можете просто создать массив struct с помощью userhist_t array[50];
(решать вамвы используете typedef
или нет, это не требуется, это просто экономит набор текста)
Если вы сортируете значения в C, сделайте одолжение и научитесь писать сравнить функции для qsort
.Это швейцарский армейский нож для сортировки в C. (он также довольно эффективен и намного лучше проверен, чем все, что вы будете катать самостоятельно).Нет ничего сложного в написании функции сравнения.Прототип:
int compare (const void *a, const void *b);
Где a
и b
- это просто указатели на смежные элементы в передаваемом вами массиве, и вы возвращаете -1
, если a
сортирует перед b
, 0
если они одинаковые, или 1
, если b
сортирует до a
- точно так же, как strcmp
.Для этого вы просто приводите a
и b
к типу элемента для вашего массива, а затем сравниваете значения, возвращая соответствующее значение.Например:
/* qsort compare function sorting array of struct by percent (descending) */
int compare_pct (const void *a, const void *b)
{
const userhist_t *as = a, /* cast the pointers to correct type */
*bs = b;
/* return descending sort (conditionals avoids potential overflow)
* for ascending sort use (as > bs) - (as < bs)
*/
return (as->pct < bs->pct) - (as->pct > bs->pct);
}
Выше указатели a
и b
приводятся как указатель на вашу структуру (я только что пометил s
для структуры, чтобы создать разные имена переменных, например as
и bs
, вы можете назвать их как угодно).
Хотя вы можете просто вернуть bs->pct - as->pct
, существует риск переполнения, если значения превышают то, что может быть возвращено как int
(например, оба больших отрицательных значения, которые при вычитании будут меньше * 1066)*. Вместо этого условные выражения используются для нормализации возврата к -1, 0, 1
, просто используя результат условных выражений.
(продумайте его, если (as->pct < bs->pct)
, результат оценивается как 1
и (as->pct > bs->pct)
оценивается как 0
, что дает 1 - 0 = 1
(поэтому b
сортирует до a
в порядке убывания))
Чтобы отсортировать массив, вы просто вызываете:
qsort (array, nelements, sizeof element, compare);
Теперь перейдем к чтению / заполнению массива структуры. Итак, давайте объявим наши константы, у вас есть структура выше, давайте объявим массив структуры, а затем прочитаем значения из файла в массив, например,
/* global enum defining constants for use in code */
enum { MAXDT = 12, MAXNM = 16, MAXS = 64 };
...
userhist_t user[MAXS] = {{ .pct = 0 }}; /* array of struct */
size_t n = 0;
...
/* read/fill up to MAXS struct from file */
while (n < MAXS && fscanf (fp, "%d%% %s %d %s", &user[n].pct,
user[n].name, &user[n].test, user[n].date) == 4)
n++;
Хотя вы можете использовать fgets
и sscanf
, чтобы прочитать строку и затем проанализировать значения по отдельности, fscanf
выполнит оба примера для примера. Ключом является проверка возврата к страхованию 4-conversionsуспешно, и вход или совпадение сбой не произошел.
В целом, передавая имя файла в качестве 1-го аргумента программе (или читая из stdin
по умолчанию, если аргумент не указан), обрабатывая объявления, чтение, сортировку с qsort
и вывод,Вы можете сделать что-то похожее на следующее:
#include <stdio.h>
#include <stdlib.h>
/* global enum defining constants for use in code */
enum { MAXDT = 12, MAXNM = 16, MAXS = 64 };
typedef struct { /* simple struct to hold values read from input */
int pct,
test;
char name[MAXNM],
date[MAXDT];
} userhist_t;
/* qsort compare function sorting array of struct by percent (descending) */
int compare_pct (const void *a, const void *b)
{
const userhist_t *as = a, /* cast the pointers to correct type */
*bs = b;
/* return descending sort (conditionals avoids potential overflow)
* for ascending sort use (as > bs) - (as < bs)
*/
return (as->pct < bs->pct) - (as->pct > bs->pct);
}
int main (int argc, char **argv) {
userhist_t user[MAXS] = {{ .pct = 0 }}; /* array of struct */
size_t n = 0;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* read/fill up to MAXS struct from file */
while (n < MAXS && fscanf (fp, "%d%% %s %d %s", &user[n].pct,
user[n].name, &user[n].test, user[n].date) == 4)
n++;
if (fp != stdin) fclose (fp); /* close file if not stdin */
/* sort by calling compare_pct to sort by percent (descending order) */
qsort (user, n, sizeof *user, compare_pct);
for (size_t i = 0; i < n; i++) /* output sorted results */
printf ("%3d%% %-8s %2d %s\n",
user[i].pct, user[i].name, user[i].test, user[i].date);
return 0;
}
Пример входного файла
$ cat dat/history.txt
26% User1 1 01/01/2019
100% User2 3 01/01/2019
73% User3 1 01/01/2019
52% User4 1 01/01/2019
75% User5 2 01/01/2019
60% User6 1 01/01/2019
Пример использования / Вывод
Использование вашего входного файла приведет к сортировке, которую вы описываете:
$ ./bin/userhistsort dat/history.txt
100% User2 3 01/01/2019
75% User5 2 01/01/2019
73% User3 1 01/01/2019
60% User6 1 01/01/2019
52% User4 1 01/01/2019
26% User1 1 01/01/2019
Если вы хотите сохранить результаты в новый файл, просто перенаправьте вывод, например,
$ ./bin/userhistsort dat/history.txt > sortedfile.txt
(или вы можете открыть новый поток файлов в вашем коде и вывести туда информацию)
Просмотрите все и дайте мне знать, если у вас есть дополнительные вопросы.