我几乎完成了我的C程序来添加,查看,保存和加载患者详细信息。我已经完成了添加、保存和加载,但似乎无法正确实现保存功能。
该程序将允许用户添加患者详细信息,然后将详细信息保存到文本文件数据库中。然后,用户可以退出程序,启动程序并加载文本文件数据库。最后,用户将能够在程序中查看患者详细信息。
在我的代码中,我还添加了一些代码,用于在读取文本文件后打印出内容,该代码有效,将所有患者详细信息打印到终端上。但是,在我从文本文件中读取后,我尝试使用查看患者功能并得到分段错误。任何帮助将不胜感激。谢谢!
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define DB_NAME "database"
struct dob
{
int day, month, year;
};
typedef struct dob dob_t;
struct medicine
{
int medicine_id;
char medicine_name[100];
};
typedef struct medicine medicine_t;
struct patient
{
int patient_id;
dob_t date_db;
char patient_name[20];
medicine_t patient_med;
struct patient* nextp;
};
typedef struct patient patient_t;
void print_menu (void);
patient_t* add_patients (patient_t* patient_headp, patient_t* temp, patient_t* patient_currentp, int num_patients);
void view_patients (patient_t* patient_currentp, patient_t* patient_headp);
void save_patients (patient_t* patient_currentp, patient_t* patient_headp);
patient_t* read_patients (patient_t* patient_currentp, patient_t* patient_headp, patient_t* temp);
int main (void){
patient_t* patient_headp = NULL;
patient_t* temp = NULL;
patient_t* patient_currentp = NULL;
int option_picked = 0;
int num_patients = 0;
while(option_picked != 5)
{
print_menu ();
scanf("%d", &option_picked);
if (option_picked == 1){
patient_headp = add_patients(patient_headp, temp, patient_currentp, num_patients);
}
else if (option_picked == 2){
view_patients (patient_currentp, patient_headp);
}
else if (option_picked == 3){
save_patients (patient_currentp, patient_headp);
}
else if (option_picked == 4){
patient_headp = read_patients (patient_currentp, patient_headp, temp);
}
}
return 0;
}
void print_menu (void)
{
printf("n"
"1. add a patientn"
"2. display all patientsn"
"3. save the patients to the database filen"
"4. load the patients from the database filen"
"5. exit the programn"
"Enter choice (number between 1-5)>n");
}
patient_t* add_patients (patient_t* patient_headp, patient_t* temp, patient_t* patient_currentp, int num_patients){
char choice;
do
{
temp = (patient_t*) malloc(sizeof(patient_t));
if (temp == NULL){
printf("Error allocating memoryn");
}
printf("Enter Patient ID: ");
scanf("%d", &temp->patient_id);
printf("Enter Patient DOB(DD MM YY): ");
scanf("%d %d %d", &temp->date_db.day, &temp->date_db.month,
&temp->date_db.year);
printf("Enter Patient Name: ");
scanf("%s", temp->patient_name);
printf("Enter Patient Medicine Prescription: ");
scanf("%s", temp->patient_med.medicine_name);
printf("Enter Patient Medicine Prescription ID: ");
scanf("%d", &temp->patient_med.medicine_id);
temp->nextp = NULL;
if(patient_headp == NULL){
patient_headp = temp;
}
else{
patient_currentp = patient_headp;
while(patient_currentp->nextp != NULL){
patient_currentp = patient_currentp->nextp;
}
patient_currentp->nextp = temp;
}
printf("Add more patients? (Y/N) ");
scanf(" %c", &choice);
num_patients++;
}
while (choice == 'Y');
return patient_headp;
}
void view_patients (patient_t* patient_currentp, patient_t* patient_headp){
/*patient_currentp = (patient_t*) malloc(sizeof(patient_t));
if (patient_currentp == NULL){
printf("Error allocating memoryn");
}
patient_currentp = patient_headp;
do{
printf("%05d %02d/%02d/%02d %s %s %dn", 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;
}while(patient_currentp->nextp != NULL);*/
printf("%05d %02d/%02d/%02d %s %s %dn", patient_headp->patient_id,
patient_headp->date_db.day, patient_headp->date_db.month,
patient_headp->date_db.year, patient_headp->patient_name,
patient_headp->patient_med.medicine_name,
patient_headp->patient_med.medicine_id);
}
void save_patients (patient_t* patient_currentp, patient_t* patient_headp){
FILE *output = fopen(DB_NAME, "a");
if (output == NULL){
printf("Failed to open filen");
}
patient_currentp = patient_headp;
do{
fprintf(output, "%05d %02d/%02d/%02d %s %s %dn", 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;
}while(patient_currentp != NULL);
fclose(output);
}
patient_t* read_patients (patient_t* patient_currentp, patient_t* patient_headp, patient_t* temp){
FILE *input = fopen(DB_NAME, "r");
if (input == NULL){
printf("Failed to open filen");
}
do{
temp = (patient_t*) malloc(sizeof(patient_t));
if (temp == NULL){
printf("Error allocating memoryn");
}
while ((fscanf(input, "%05d %02d/%02d/%02d %s %s %d",
&temp->patient_id, &temp->date_db.day,
&temp->date_db.month, &temp->date_db.year,
temp->patient_name,
temp->patient_med.medicine_name,
&temp->patient_med.medicine_id)) != EOF)
printf("%05d %02d/%02d/%02d %s %s %dn", temp->patient_id,
temp->date_db.day, temp->date_db.month,
temp->date_db.year, temp->patient_name,
temp->patient_med.medicine_name,
temp->patient_med.medicine_id);
temp->nextp = NULL;
if(patient_headp == NULL){
patient_headp = temp;
}
else{
patient_currentp = patient_headp;
while(patient_currentp->nextp != NULL){
patient_currentp = patient_currentp->nextp;
}
patient_currentp->nextp = temp;
}
}while(patient_currentp != NULL);
return patient_headp;
}
问题的现实是 (1( 如果没有输入验证,有太多潜在的错误源可能导致未定义的行为和分段错误,以至于很难确定 (2( 每当列表的地址在函数中发生变化(例如,第一个节点发生变化(时,您需要传递patient_headp
,以便函数接收实际的列表指针,而不是保存列表地址的指针的副本,并且 (3( 您的read_patients()
不起作用(出于多种原因(,但从根本上说,因为设置patient_currentp = patient_currentp->nextp;
保证while(patient_currentp != NULL);
测试错误。
没有理由将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();
}
除了验证问题之外,您可能会发现通过返回指向添加的节点(或失败时NULL
节点的指针来指示add_patients()
函数中的成功/失败更为可靠。这有点复杂,因为您在函数中循环以添加多个患者,而不是简单地从菜单中再次调用函数。无论如何,返回指向最后一个添加的节点的指针同样有效。
不可能以为此答案分配的字符逐步完成代码中的每个问题。相反,我整理了代码,以解决每个用户输入验证,从函数声明中删除不必要的参数,并将save_patients()
和read_patients()
的返回类型更改为int
,以提供成功写入或读取1
,0
其他方式。
(请注意save_patients()
中对fclose
的验证。任何时候写入文件时,都应验证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 patientn"
"2. display all patientsn"
"3. save the patients to the database filen"
"4. load the patients from the database filen"
"5. exit the programnn"
"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 inputn", 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 %dn", 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 %dn",
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 %dn", 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;
}
(注意:您在read_patients()
中重复分配patient_currentp
会泄漏内存并覆盖您之前分配给列表的指针值。这就是为什么使用额外的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
再次,仔细检查一下,了解为什么进行了更改,并询问您是否还有其他问题。
add_patients
发生以下情况
创建节点
/* Create a temporary node */
temp = (patient_t*) malloc(sizeof(patient_t));
用任何数据填充节点
/* Copy Data to temporary Node */
printf("Enter Patient ID: ");
..
..
temp->nextp = NULL;
链表变更
if(patient_headp == NULL){
patient_headp = temp;
}
else{
patient_currentp = patient_headp;
while(patient_currentp->nextp != NULL){
patient_currentp = patient_currentp->nextp;
}
patient_currentp->nextp = temp;
}
此时temp
仍然指向分配的内存,linked list
然后这个
free(temp);
结果segfault
。 所以删除free