【问题标题】:fscanf in C with a text file with no spacesC中的fscanf,带有没有空格的文本文件
【发布时间】:2014-10-22 08:30:25
【问题描述】:

我有一个名称如下的文本文件:

"玛丽","帕特里夏","琳达","芭芭拉","伊丽莎白","詹妮弗","玛丽亚","苏珊","玛格丽特",

我已使用以下代码尝试将名称放入数组中:

char * names[9];
int i = 0;
FILE * fp = fopen("names.txt", "r");

for (i=0; i < 9; i++) {
  fscanf(fp, "\"%s\",", names[i]);
}

当我尝试运行该程序时出现分段错误。我已经仔细调试过,当我尝试读取第二个名称时,我注意到错误出现了。

有人知道我的代码为什么不能工作,以及为什么会发生分段错误吗?

【问题讨论】:

  • 您必须为扫描的名称保留空间。例如。定义 char names[MAX_NAME_COUNT][MAX_NAME_LENGTH]; 并使用您提供的代码读入它。
  • 编译时启用所有警告(-Wall 或类似的),你会明白为什么。
  • 代码创建了一个包含 9 个指向 char 的指针的数组。但是,没有分配存储实际数据输入所需的内存区域。
  • 您的代码缺少对 fopen() 返回代码的检查,因此代码不知道 fopen() 是成功还是失败。您的代码缺少对 fscanf() 返回代码的检查,因此代码不知道 fscanf() 是成功还是失败。
  • 我很想投票结束这个问题。我们看到很多关于scanf 的问题,只是缺少将适当的缓冲区传递给它,并且在 SO 上有大量重复项。但是这里其实有两个问题,另一个在标题中比在正文中更能体现出来,所以我不知道。

标签: c scanf


【解决方案1】:

您的代码中有undefined behavior,因为您没有为在fscanf 调用中写入的指针分配内存。

您有一个由九个未初始化指针组成的数组,并且由于它们是局部变量的一部分,它们具有不确定的值,即它们将指向看似随机的位置。写入内存中的随机位置(当您调用 fscanf 时会发生这种情况)会做坏事。

解决问题的最简单方法是使用数组数组,例如

char names[9][20];

这将为您提供一个由九个数组组成的数组,每个子数组为 20 个字符(允许您的名称最长为 19 个字符)。

为了不超出范围,你还应该修改你的调用,这样你就不会读到很多字符:

fscanf(fp, "\"%19s\",", names[i]);

但是,您使用fscanf 函数还有另一个问题,那就是读取字符串的格式"%s" 会一直读取,直到在输入中找到空格(或直到达到限制,如果提供了字段宽度)。

简而言之:您不能使用fscanf 来读取您的输入。

相反,我建议您使用fgets 一次将整行读入内存,然后使用逗号分割字符串,例如strtok.


一种处理任意长行作为文件输入的方法(伪代码):

#define SIZE 256

size_t current_size = SIZE;
char *buffer = malloc(current_size);
buffer[0] = '\0';  // Terminator at first character, makes the string empty

for (;;)
{
    // Read into temporary buffer
    char temp[SIZE];
    fgets(temp, sizeof(temp), file_pointer);

    // Append to actual buffer
    strcat(buffer, temp);

    // If last character is a newline (which `fgets` always append
    // if it reaches the end of the line) then the whole line have
    // been read and we are done
    if (last_character_is_newline(buffer))
        break;

    // Still more data to read from the line
    // Allocate a larger buffer
    current_size += SIZE;
    buffer = realloc(buffer, current_size);

    // Continues the loop to try and read the next part of the line
}

// After the loop the pointer `buffer` points to memory containing the whole line

[注意:以上代码sn-p不包含任何错误处理。]

【讨论】:

  • 大多数时候 ;-)。千载难逢的事情是有帮助的,否则我们不会在这里写这篇文章。
  • 谢谢!我执行此方法,然后将 MARY","PATRICIA","L 作为第一个数组条目,然后将其余部分作为垃圾。为什么??
  • @lhay86 啊,是的,我忘记在答案中写了,但是"%s" 格式会读取 everything 直到输入中有空格。您不能将它与您的输入一起使用。很快就会更新我的答案。
  • @JoachimPileborg 感谢 fgets 和 strtok 很棒!另一个问题:如果文本文件有数千个名称怎么办?没有字符串能够保存所有名称。我将如何修改您的解决方案?
  • @lhay86 在 POSIX 系统(如 Linux 或 OSX)上,您可以使用 getline。我确定 Windows 也有类似的功能(但我现在找不到),否则您可以在循环中使用 fgets(请参阅我的更新答案以获取解决方案)。
【解决方案2】:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    char *names[9], buff[32];
    int i = 0;
    FILE *fp = fopen("names.txt", "r");

    for(i = 0; i < 9; i++) {
        if(1==fscanf(fp, "\"%31[^\"]\",", buff)){//"\"%s\"," does not work like that what you want
            size_t len = strlen(buff) + 1;
            names[i] = malloc(len);//Space is required to load the strings of each
            memcpy(names[i], buff, len);
        }
    }
    fclose(fp);
    //check print & deallocate
    for(i = 0; i< 9; ++i){
        puts(names[i]);
        free(names[i]);
    }
    return 0;
}

【讨论】:

  • 您能否解释一下这是什么意思:"\"%31[^\"]\"," 特别是 %31 是什么以及为什么要包含方括号?
  • 前者是最大长度(我猜是为尾随的零字节留出空间);方括号表示一种模式,在这种情况下是“不是双引号的所有内容”(请注意反斜杠,它可以在由双引号分隔的字符串中包含双引号字符)。通常 scanf 将读取出现在方括号内的所有字符;带有前导插入符号的它将读取任何内容,这使得它读取名称直到但不包括此处的第一个双引号。
  • @lhay86 31 : 最大读取字符数,[char list] : 读取指定字符(只读包括char list)。 [^char list] : 读取指定字符以外的字符。所以 31[^\"] :最多读取 31 个字符,最多可达 "
  • 并且应该补充一点,scanf 的普通简单“用空格分隔项目”规则对于 [] 语法指定的项目是暂停的。这就是为什么你可以在这里使用它。但是不要想到突然在数据中引入空格;-)。 (谨慎的做法可能是在忽略模式中包含空格并制作第二个 - 标记为被* 忽略 - 尾随\"," 的模式,现在是一个非常有趣的序列。)。
【解决方案3】:

试试这个...

for (i=0; i < 9; i++) 
{
   names[i]=malloc(15);// you should take care about size
   fscanf(fp, "\"%s\",", names[i]);
}

【讨论】:

  • 并记住在使用完数组的每个成员后free()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多