我正在为大学做一个小项目(第一学期做书店实施)我有一个问题,从文本文件读取到结构列表,其中有一个存储作者的二维字符数组。但是,它无法正常工作(每次启动程序时,它显示列表为空)。写入文件有效(我认为,因为它用空数据覆盖了我的文本文件)。
示例数据:
Adam Mickiewicz///Pan Tadeusz/Publisher 1/1833/24.99
Jules Verne///Around The World in 80 days/Publisher 1/1904/19.99
Jean-Jacques Sempe/Rene Goscinny//Little Nicholas/Publisher 2/1963/22.99
我的结构:
#define AK 3 // Constant denoting max number of authors
typedef struct
{
char authors[AK][100];
char title[255];
char publisher[100];
unsigned int year;
double price;
struct Book *next;
} Book;
Book *first; // Address of the first element in a list
从文件中读取:
void read_from_file(const char *path)
{
int no_of_authors;
int i;
printf("Loading...\n");
FILE *fp = fopen(path, "r"); // Opening a file
// Checking for errors
if (!fp) {
printf("Error reading from file!");
return;
}
// The loading mechanism
no_of_authors = 0;
while (!feof(fp)) {
Book *new = (Book*) malloc(sizeof(Book));
for (i = 0; i < AK; i++) {
fscanf(fp, "%s/", new->authors[i]);
}
fscanf(fp, "%s/%s/%u/%lf", new->title, new->publisher,
&new->year, &new->price);
fscanf(fp, "\n");
new = new->next;
}
fclose(fp);
printf("Loading successful.");
}
写入文件(以防万一):
void write_to_file(const char *path, Book *first)
{
int i;
printf("Saving...\n");
FILE *fp = fopen(path, "w");
Book* current = first;
if (!fp) {
printf("Error opening the file!");
dump_list(first); // Dumping the list to prevent memory leaks, this works okay
}
// Saving mechanism
while (first != NULL) {
for (i = 0; i < AK; i++) {
fprintf(fp, "%s/", current->authors[i]);
}
fprintf(fp, "%s/%s/%u/%lf", current->title, current->publisher,
¤t->year, ¤t->price);
fprintf(fp, "\n");
}
fclose(fp);
printf("Saved successfully");
}
答案 0 :(得分:3)
OP最大的失败是没有检查fscanf()
的返回值。如果这样做了代码,就会更快地发现问题。
在阅读数据行时,首先要考虑的是:
输入有问题吗?
对于学习者应用程序,这通常被认为是否定的。输入是“好”或文件结束。我们不要假设数据格式太合理了。
事实证明,虽然数据文件可能没有错误,但读取它的代码可能是错误的。代码检查*scanf()
返回值的第二个微妙原因 - 自我验证。
对于行定向数据,最好是使用fgets()
而不是feof(), fscanf()...
读取数据的行。另请参阅{{3 }}
char buf[sizeof(Book) * 2]; // Use an ample sized buffer
while (fgets(buf, sizeof buf, fp)) {
使用"%s"
读取不包含空格的文字。这也将在'/'
中读取。由于'/'
用于分隔,"%s"
不是可接受的输入说明符。诸如“Adam Mickiewicz”之类的其他名称包括空格。不使用"%s"
的第二个原因。
考虑fscanf(fp, "%s/", new->authors[i]);
正在做什么。 "%s"
会扫描new->authors[i]
个非空白字符。非空白字符后面的字符是空白字符,绝不是'/'
。
使用"%[^/]"
读取不包含'/'
的文字。
使用"%n"
跟踪当前的扫描偏移。
现在解析这条线。
char *p = buf;
Book *nu = malloc(sizeof *nu); // no cast needed
for (size_t i = 0; i < AK; i++) {
int n = 0;
sscanf(p, "%99[^/]/%n", nu->authors[i], &n);
if (n == 0) {
Handle_Parse_Error();
}
p += n;
}
if (sscanf(p, "%254[^/]/%99[^/]/%u/%lf",
nu->title, nu->publisher, &nu->year, &nu->price) != 4) {
Handle_Parse_Error();
}
强大的代码会在每个成员上添加检查。适合编码目标。
if (nu->year < -6000 || nu->year > 2999) Fail_Year_Range();
需要进一步的工作来将数据链接在一起。 OP的代码在这个问题上还不清楚,因此留给了OP。一种可能的方法:
Book *read_from_file(const char *path) {
Book first; // Only the .next member is used.
first.next = NULL;
Book *current = &first;
// setup while () loop
...
while (fgets(buf, sizeof bu, fp)) {
...
Book *nu = malloc(sizeof *nu);
nu->next = NULL;
...
// parse data, fill in rest of nu->
....
current->next = nu; // update current pointer
current = nu;
}
// close up file ...
return first.next;
}