Реальность вашей проблемы состоит в том, что (1) без проверки входных данных существует так много потенциальных источников ошибок, которые могут привести к неопределенному поведению и ошибке сегментации, которую трудно определить (2) любойкогда адрес списка может измениться в функции (например, изменяется первый узел), вам нужно передать адрес patient_headp
, чтобы функция получила фактический указатель списка вместо копии указателяудержание адреса списка, и (3) ваш read_patients()
не функционирует (по ряду причин), но в основном потому, что установка patient_currentp = patient_currentp->nextp;
гарантирует while(patient_currentp != NULL);
тест false.
Нет причин дляпередать patient_currentp
в качестве параметра.Нет смысла передавать num_patients
в его текущей форме, параметр не используется, и для его полезности вам нужно будет передать указатель на num_patients
, чтобы вы могли обновить его в своих функциях добавления и чтения и сделатьобновленные значения доступны в вызывающей функции.
Прежде чем мы даже посмотрим на код, при вводе пользовательского ввода с помощью scanf
возникают ловушки для неосторожных в случае совпадения или вход сбой.Чтобы даже начать использовать scanf
правильно, вы должны подтверждать возврат каждый раз.Это означает обработку EOF
, совпадение или вход сбой и обработку допустимого случая ввода.Как минимум, вы должны проверить, что ожидаемое количество конверсий имело место перед использованием ввода.
В случае ошибки совпадения извлечение символа из входного буфера прекращается, оставляя непрочитанный символ непрочитанным, ожидая, что вы укусите вас при следующей попытке чтения.Чтобы облегчить восстановление после ошибки , совпадающей с , вы должны удалить поврежденные символы из входного буфера.Обычный подход для stdin
- это просто чтение с getchar()
, пока не встретится '\n'
или EOF
.Короткая вспомогательная функция облегчает жизнь, например,
void empty_stdin (void)
{
int c = getchar();
while (c != '\n' && c != EOF)
c = getchar();
}
В дополнение к проблеме проверки, вы можете найти более надежным указание успеха / неудачи в вашей функции add_patients()
, возвращая указатель на узелдобавлено (или NULL
при сбое).Это несколько усложняется тем, что вы зацикливаетесь внутри функции для добавления нескольких пациентов, а не просто снова вызываете функцию из меню.В любом случае, возврат указателя на последний добавленный узел работает одинаково хорошо.
Невозможно пройтись по каждой проблеме в вашем коде с помощью символов, выделенных для этого ответа.Вместо этого я привел в порядок ваш код таким образом, чтобы он обращался к каждой проверке пользовательского ввода, удалял ненужные параметры из объявлений функций и изменил типы возвращаемых значений для save_patients()
и read_patients()
на int
, чтобы обеспечить 1
при успешной записи или чтении, 0
в противном случае.
( note проверка fclose
в save_patients()
. Каждый раз, когда вы записываете в файл, вы должны проверить fclose
, так как он перехватитпотоковые ошибки, а также ошибки с вашей последней записью, которые не могли быть в состоянии сообщить до закрытия)
Код соответствует вашему подходу, он только что был усовершенствован в нескольких местах.Просмотрите это:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define DB_NAME "database"
#define MAXRX 100 /* if you need constants, #define on (or more) */
#define MAXNM 20 /* (don't use "magic numbers" in your code ) */
typedef struct {
int day, month, year;
} dob_t;
typedef struct {
int medicine_id;
char medicine_name[MAXRX];
} medicine_t;
typedef struct patient {
int patient_id;
dob_t date_db;
char patient_name[MAXNM];
medicine_t patient_med;
struct patient* nextp;
} patient_t;
void empty_stdin (void)
{
int c = getchar();
while (c != '\n' && c != EOF)
c = getchar();
}
void print_menu (void);
patient_t *add_patients (patient_t **patient_headp, int *num_patients);
void view_patients (patient_t *patient_headp);
int save_patients (patient_t *patient_headp);
int read_patients (patient_t **patient_headp, int *num_patients);
int main (void) {
patient_t *patient_headp = NULL;
int option_picked = 0,
num_patients = 0;
while(option_picked != 5)
{
print_menu ();
if (scanf("%d", &option_picked) != 1) { /* VALIDATE EVERY USER INPUT */
fputs ("\n error: invalid input.\n", stderr);
empty_stdin();
continue;
}
if (option_picked == 1)
add_patients (&patient_headp, &num_patients);
else if (option_picked == 2)
view_patients (patient_headp);
else if (option_picked == 3)
save_patients (patient_headp);
else if (option_picked == 4)
read_patients (&patient_headp, &num_patients);
}
return 0;
}
void print_menu (void)
{
printf ("\n"
"1. add a patient\n"
"2. display all patients\n"
"3. save the patients to the database file\n"
"4. load the patients from the database file\n"
"5. exit the program\n\n"
"Enter choice (number between 1-5)> ");
}
patient_t *add_patients (patient_t **patient_headp, int *num_patients)
{
patient_t *patient_currentp = *patient_headp,
*temp = NULL;
char choice = 0;
do
{
temp = malloc (sizeof *temp); /* allocate */
if (temp == NULL){ /* validate */
perror ("add_patients-malloc");
return NULL;
}
temp->nextp = NULL; /* initialize */
printf ("Enter Patient ID: ");
if (scanf ("%d", &temp->patient_id) != 1)
goto error_add_pt;
printf ("Enter Patient DOB(DD MM YY): ");
if (scanf ("%d %d %d", &temp->date_db.day, &temp->date_db.month,
&temp->date_db.year) != 3)
goto error_add_pt;
printf ("Enter Patient Name: ");
if (scanf ("%s", temp->patient_name) != 1)
goto error_add_pt;
printf ("Enter Patient Medicine Prescription: ");
if (scanf ("%s", temp->patient_med.medicine_name) != 1)
goto error_add_pt;
printf ("Enter Patient Medicine Prescription ID: ");
if (scanf ("%d", &temp->patient_med.medicine_id) != 1)
goto error_add_pt;
if (*patient_headp == NULL){
*patient_headp = patient_currentp = temp;
}
else {
while (patient_currentp->nextp != NULL){
patient_currentp = patient_currentp->nextp;
}
patient_currentp->nextp = temp;
}
(*num_patients)++;
printf ("Add more patients? (Y/N) ");
if (scanf (" %c", &choice) < 1) {
fputs (" user canceled input.\n", stderr);
break;
}
}
while (choice == 'Y' || choice == 'y');
return temp; /* return pointer to most recent node added */
error_add_pt:;
fputs ("error: invalid input\n", stderr);
empty_stdin();
free (temp);
return NULL;
}
void view_patients (patient_t *patient_headp)
{
patient_t *patient_currentp = patient_headp;
while (patient_currentp != NULL) {
printf ("%05d %02d/%02d/%02d %s %s %d\n", patient_currentp->patient_id,
patient_currentp->date_db.day, patient_currentp->date_db.month,
patient_currentp->date_db.year, patient_currentp->patient_name,
patient_currentp->patient_med.medicine_name,
patient_currentp->patient_med.medicine_id);
patient_currentp = patient_currentp->nextp;
}
}
int save_patients (patient_t *patient_headp)
{
patient_t *patient_currentp = patient_headp;
FILE *output = fopen(DB_NAME, "a");
if (output == NULL) { /* validate file open to append */
fprintf (stderr, "error: file open failed '%s'\n", DB_NAME);
return 0;
}
while(patient_currentp != NULL) {
fprintf (output, "%05d %02d/%02d/%02d %s %s %d\n",
patient_currentp->patient_id,
patient_currentp->date_db.day, patient_currentp->date_db.month,
patient_currentp->date_db.year, patient_currentp->patient_name,
patient_currentp->patient_med.medicine_name,
patient_currentp->patient_med.medicine_id);
patient_currentp = patient_currentp->nextp;
}
if (fclose (output) == EOF) {
fputs ("error: stream error on fclose.\n", stderr);
return 0;
}
return 1;
}
int read_patients (patient_t **patient_headp, int *num_patients)
{
patient_t tmp = {0},
*patient_currentp = *patient_headp;
FILE *input = fopen(DB_NAME, "r");
if (input == NULL){ /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'\n", DB_NAME);
return 0;
}
while (patient_currentp && patient_currentp->nextp != NULL)
patient_currentp = patient_currentp->nextp;
while (fscanf (input, "%05d %02d/%02d/%02d %19s %99s %d",
&tmp.patient_id, &tmp.date_db.day,
&tmp.date_db.month,
&tmp.date_db.year,
tmp.patient_name,
tmp.patient_med.medicine_name,
&tmp.patient_med.medicine_id) == 7) {
patient_t *node = malloc (sizeof *node);
if (node == NULL) {
perror ("read_patients-malloc");
return 0;
}
node->nextp = NULL;
*node = tmp;
if (!patient_currentp)
*patient_headp = patient_currentp = node;
else {
patient_currentp->nextp = node;
patient_currentp = patient_currentp->nextp;
}
(*num_patients)++;
printf ("%05d %02d/%02d/%02d %s %s %d\n", node->patient_id,
node->date_db.day, node->date_db.month,
node->date_db.year, node->patient_name,
node->patient_med.medicine_name,
node->patient_med.medicine_id);
}
fclose (input);
return 1;
}
( note: при повторном выделении patient_currentp
в read_patients()
произошла утечка памяти и произошла перезапись значений указателей, которые вы ранее присвоили своему списку.поэтому используется дополнительная переменная node
)
Пример использования / вывод - ввод данных
$ ./bin/llpatients
1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program
Enter choice (number between 1-5)> 1
Enter Patient ID: 10001
Enter Patient DOB(DD MM YY): 1 1 72
Enter Patient Name: Epoch
Enter Patient Medicine Prescription: Clonapin
Enter Patient Medicine Prescription ID: 2001
Add more patients? (Y/N) y
Enter Patient ID: 10002
Enter Patient DOB(DD MM YY): 31 10 72
Enter Patient Name: Halloween
Enter Patient Medicine Prescription: Potion
Enter Patient Medicine Prescription ID: 2002
Add more patients? (Y/N) n
1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program
Enter choice (number between 1-5)> 2
10001 01/01/72 Epoch Clonapin 2001
10002 31/10/72 Halloween Potion 2002
1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program
Enter choice (number between 1-5)> 3
1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program
Enter choice (number between 1-5)> 5
Пример использования / вывод - чтение из файла
$ ./bin/llpatients
1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program
Enter choice (number between 1-5)> 4
10001 01/01/72 Epoch Clonapin 2001
10002 31/10/72 Halloween Potion 2002
1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program
Enter choice (number between 1-5)> 2
10001 01/01/72 Epoch Clonapin 2001
10002 31/10/72 Halloween Potion 2002
1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program
Enter choice (number between 1-5)> 5
Снова посмотрите, поймите, почему были сделаны изменения, и спросите, есть ли у вас какие-либо дополнительные вопросы.