То, как вы учли свой код (1) для обработки списка;и (2) добавить данные в список, он настолько запутан и настолько не проверен, что неудивительно, что у вас возникают трудности с его сортировкой.
Считывание данных некорректно с самого начала.См. Почему while (! Feof (file)) всегда неверно? .Кроме того, вы не можете проверить один возврат fscanf
.Если одно чтение не удалось, вы вызываете неопределенное поведение , слепо используя неопределенное значение (и, вероятно, каждое значение с этого момента будет неопределенным).На этом этапе все ставки закончились.
Тем не менее, вам следует рекомендовать использовать #define
для определения необходимых вам констант, но тогда вы не сможете защитить границы своего массива, включив field-width модификаторы со всеми char*
спецификаторами преобразования.Пока вы #define
константы, вы затем поворачиваетесь и жестко кодируете свое имя файла.Не делай этого.Передайте ваше имя файла в качестве аргумента вашей программе или запросите его ввод.
Всякий раз, когда обрабатываете «строку данных», вы должны использовать строчно-ориентированную функцию вводанапример, fgets
или POSIX getline
, а затем проанализируйте нужные значения из строки данных.Это обеспечивает возможность отдельной проверки (1) чтения данных из файла;и (2) анализ значений из результирующего буфера.Если по какой-либо причине возникает ошибка в формате, ваш анализ не удастся, и вы можете просто continue
цикл чтения и прочитать следующую строку - без риска Неопределенное поведение .
Когда вы создаете список, все, что вам нужно, это отдельная функция append()
, которая создаст список, если он не существует, и выделит и добавит каждый дополнительный узел в список по мере необходимости.Ваш код пытается выполнить простую прямую цепочку для добавления узлов в ваш список (что нормально, но без дополнительных значений приведет к тому, что список будет храниться в памяти в обратном порядке)
Не смешивайте данные чтения ссписок операций.Вместо этого прочитайте и передайте данные, необходимые для вашей функции append()
.Хотя это в значительной степени зависит от вас, неспособность разделить чтение / анализ и добавление приведет к тому, что функции списка не будут использоваться повторно.
Например, вместо того, чтобы пытаться читать и анализировать данные в вашей функции списка, откройтеваш файл в main()
и проанализируйте данные во временную goodsinfo
1 структуру и передайте адрес списка и указатель на ваши временные данные в функцию добавления.Вы можете сделать что-то похожее на следующее для чтения и анализа данных и передачи необходимых значений в вашу функцию:
int main (int argc, char **argv)
{
char buf[MAXC]; /* read buffer */
size_t linecnt = 0; /* line counter */
goodslist *list = NULL; /* linked list pointer */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("fopen-file");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line of data */
goodsinfo tmp = { .goods_id = "" }; /* temp data struct */
/* parse and validate data in buf (can be separate function) */
if (sscanf (buf, "%29s %29s %d %29s %d %d", tmp.goods_id,
tmp.goods_name, &tmp.goods_price, tmp.goods_discount,
&tmp.goods_amount, &tmp.goods_remain) != 6) {
fprintf (stderr, "error: invalid format line %zu.\n", linecnt+1);
continue;
}
if (!append (&list, &tmp)) /* append to list/validate */
break;
}
if (fp != stdin) /* close file if not stding */
fclose (fp);
prn_list (list); /* print list */
free_list (list); /* free list data */
return 0;
}
( note: программа берет имя файла для чтения данныхиз первого аргумента или читает по умолчанию stdin
, если имя файла не указано. Также обратите внимание, что вы объявляете список как указатель на goodslist
, а не указатель на указатель на goodslist
)
Когда ваши данные прочитаны и проанализированы, ваша функция append()
просто должна выделить хранилище для data
и выделить хранилище для нового узла списка.Он имеет только два случая для обработки (1) список пуст?- оставить node->next = NULL
;в противном случае (2) установите node->next
равным текущему адресу списка, прежде чем назначать адрес для вашего нового узла в качестве нового адреса списка, чтобы связать вместе ваши узлы, например,
/* function to allocate goodslist node and append allocated goodsinfo
* data to list. Takes address of list pointer and pointer to goodsinfo data
* to append to list. Returns pointer new node on success, NULL otherwise.
*/
goodsinfo *append (goodslist **l, goodsinfo *tmp)
{
goodsinfo *data = malloc (sizeof *data); /* allocate/validate data */
if (!data) {
perror ("malloc-data");
return NULL;
}
*data = *tmp; /* fill allocated data block with tmp values */
/* allocate/validate list node */
goodslist *node = malloc (sizeof *node);
if (!node) {
perror ("malloc-node");
free (data);
return NULL;
}
node->data = data; /* initialize data and set next NULL */
node->next = NULL;
if (*l) /* if list exists, chain next to list */
node->next = *l;
return ((*l = node)->data); /* assign new node as list, return data */
}
Сложив все вместе, вы могли бы что-то сделатьнапример:
#include <stdio.h>
#include <stdlib.h>
#define MAX_ID_LEN 30
#define MAX_NAME_LEN MAX_ID_LEN
#define MAX_PRICE_LEN MAX_NAME_LEN
#define MAX_DISCOUNT_LEN MAX_PRICE_LEN
#define MAXC 1024 /* read buffer size (don't skimp) */
typedef struct {
char goods_id[MAX_ID_LEN];
char goods_name[MAX_NAME_LEN];
int goods_price;
char goods_discount[MAX_DISCOUNT_LEN];
int goods_amount;
int goods_remain;
} goodsinfo;
typedef struct goodslist {
goodsinfo *data; /* make data a pointer and allocate */
struct goodslist *next;
} goodslist;
/* bool check_nullfile(void)
* (poor test, if first char not 0-9, test fails)
*/
/* function to allocate goodslist node and append allocated goodsinfo
* data to list. Takes address of list pointer and pointer to goodsinfo data
* to append to list. Returns pointer new node on success, NULL otherwise.
*/
goodsinfo *append (goodslist **l, goodsinfo *tmp)
{
goodsinfo *data = malloc (sizeof *data); /* allocate/validate data */
if (!data) {
perror ("malloc-data");
return NULL;
}
*data = *tmp; /* fill allocated data block with tmp values */
/* allocate/validate list node */
goodslist *node = malloc (sizeof *node);
if (!node) {
perror ("malloc-node");
free (data);
return NULL;
}
node->data = data; /* initialize data and set next NULL */
node->next = NULL;
if (*l) /* if list exists, chain next to list */
node->next = *l;
return ((*l = node)->data); /* assign new node as list, return data */
}
/* simple print list function */
void prn_list (goodslist *l)
{
if (!l)
return;
while (l) {
printf (" %-8s %-8s %8d %-8s %8d %9d\n", l->data->goods_id,
l->data->goods_name, l->data->goods_price,
l->data->goods_discount, l->data->goods_amount,
l->data->goods_remain);
l = l->next;
}
}
/* simple free list function */
void free_list (goodslist *l)
{
if (!l)
return;
goodslist *iter = l;
while (iter) {
goodslist *victim = iter;
free (iter->data);
iter = iter->next;
free (victim);
}
}
int main (int argc, char **argv)
{
char buf[MAXC]; /* read buffer */
size_t linecnt = 0; /* line counter */
goodslist *list = NULL; /* linked list pointer */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("fopen-file");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line of data */
goodsinfo tmp = { .goods_id = "" }; /* temp data struct */
/* parse and validate data in buf (can be separate function) */
if (sscanf (buf, "%29s %29s %d %29s %d %d", tmp.goods_id,
tmp.goods_name, &tmp.goods_price, tmp.goods_discount,
&tmp.goods_amount, &tmp.goods_remain) != 6) {
fprintf (stderr, "error: invalid format line %zu.\n", linecnt+1);
continue;
}
if (!append (&list, &tmp)) /* append to list/validate */
break;
}
if (fp != stdin) /* close file if not stding */
fclose (fp);
prn_list (list); /* print list */
free_list (list); /* free list data */
return 0;
}
( примечание: ваш bool check_nullfile(void)
приносит больше вреда, чем пользы, и потерпит неудачу, если первый непробельный символ не является цифрой)
Пример использования / Вывод
( примечание: при использовании сцепления без сохранения указателя "last"
приводит к тому, что узлы списка сохраняются в обратном порядке)
$ ./bin/ll_goodslist dat/goodsinfo.txt
1004 new5 100 0.8 70 80
1003 new4 88 0.8 70 80
1002 new3 70 0.8 10 10
1001 new2 80 0.9 80 80
1000 new1 90 0.9 90 80
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блокапамяти, таким образом, (2) он может быть освобожден , когда он больше не нужен.
Обязательно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памятиили написать за пределами / за пределами выделенного блока, попытаться прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор.Для каждой платформы есть похожие проверки памяти.Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/ll_goodslist dat/goodsinfo.txt
==3493== Memcheck, a memory error detector
==3493== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3493== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==3493== Command: ./bin/ll_goodslist dat/goodsinfo.txt
==3493==
1004 new5 100 0.8 70 80
1003 new4 88 0.8 70 80
1002 new3 70 0.8 10 10
1001 new2 80 0.9 80 80
1000 new1 90 0.9 90 80
==3493==
==3493== HEAP SUMMARY:
==3493== in use at exit: 0 bytes in 0 blocks
==3493== total heap usage: 11 allocs, 11 frees, 1,152 bytes allocated
==3493==
==3493== All heap blocks were freed -- no leaks are possible
==3493==
==3493== For counts of detected and suppressed errors, rerun with: -v
==3493== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Посмотрите вещии дайте мне знать, если у вас есть дополнительные вопросы.
сноски
- Хотя это не ошибка, C обычно избегает использования
camelCase
или MixedCase
имена переменных в пользу всех строчных букв при резервировании прописных имен для использования с макросами и константами.Это вопрос стиля - так что это полностью зависит от вас, но если вы не будете следовать ему, то в некоторых кругах может произойти неправильное первое впечатление.