c - 从文件读取后打印链表的分段错误
问题描述
我几乎完成了添加、查看、保存和加载患者详细信息的 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 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"
"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 memory\n");
}
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 memory\n");
}
patient_currentp = patient_headp;
do{
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;
}while(patient_currentp->nextp != NULL);*/
printf("%05d %02d/%02d/%02d %s %s %d\n", 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 file\n");
}
patient_currentp = patient_headp;
do{
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;
}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 file\n");
}
do{
temp = (patient_t*) malloc(sizeof(patient_t));
if (temp == NULL){
printf("Error allocating memory\n");
}
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 %d\n", 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();
}
除了验证问题之外,您可能会发现add_patients()
通过返回指向添加的节点(或NULL
失败时)的指针来指示函数中的成功/失败更加健壮。这有点复杂,因为您在函数中循环添加多个患者,而不是简单地从菜单中再次调用该函数。无论如何,返回指向添加的最后一个节点的指针同样有效。
无法以分配给此答案的字符来逐步解决代码中的每个问题。相反,我整理了您的代码,以解决每个用户输入验证问题,从您的函数声明中删除不必要的参数,并更改返回类型 forsave_patients()
和read_patients()
toint
以1
在成功写入或读取时提供,0
否则。
(请注意fclose
in的验证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;
}
(注意:patient_currentp
您对in的重复分配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
再次检查,了解为什么要进行更改,并询问您是否还有其他问题。
推荐阅读
- openssl - Debian Buster 上的 openssl 1.1.1d 卡住无法启用自定义密码
- python - 如何在mysql中查询带有标签的字符串
- php - Mysql 链接后选择照片位置
- linux - TypeError: '>' 在 'method' 和 'int' 的实例之间不支持,但在这种情况下,'method' 是一个数字
- reverse-proxy - 使用 Traefik 的反向代理:如何在 URL mydom.com/subfolder01 和 mydom.com/subfolder02 中路由子文件夹
- javascript - javascript中switch语句中预期的声明或语句
- c++ - Xcode - 终端程序的 C++ 代码将无法运行
- javascript - 多人连接onicecandidate事件不会触发
- python - 使用 Pulp 进行线性规划 - 约束分配
- c - 如何使用 CMake 复制现代 IDE 的“构建和运行”功能?