首页 > 解决方案 > 当 fscanf 返回一个不同于 1 的值时,循环中的条件语句检查变量集会导致无限循环

问题描述

我希望循环在到达文件末尾时结束。我知道当它到达它时,从 fscanf 返回的值将不同于 1,因为它在读取某些内容时返回 1。

如果我设置i=3循环是无限的,但是如果我设置i=2循环结束,我觉得这很奇怪,因为控制表达式 (i!=3) 应该在调整之前被评估,所以当我设置时i=3,它会中断并且应该测试比确实i!=3如此循环会结束(它不会)。当我将它设置为 2 时,它会结束,所以它必须再增加一次,然后检查。

所以,我的第一个问题是为什么会这样?

我的第二个问题是,%[^\n]s它只保存文件的开头,但只%s保存整个文件,但错误的是它只扫描到空格,但我希望它扫描到新行。

我的文件每行有 1 个元素(有些带有空格)

for(int i=0;i!=3;i++){
    switch(i){
      case 0:
        if(fscanf(recordsRegistry,"%[^\n]s", (recordsArray[recordsArrayPosition]).author)!=1){
          i=3;//stop condition
        }
        break;
      case 1:
        if(fscanf(recordsRegistry,"%[^\n]s", (recordsArray[recordsArrayPosition]).title)!=1){
          i=3;//stop condition
        }
        break;
      case 2:
        if(fscanf(recordsRegistry,"%hu", &((recordsArray[recordsArrayPosition]).numberOfSales))!=1){
          i=3;//stop condition
        }
        i=-1;
        recordsArrayPosition++;
        totalRecords++;
        recordsArray=realloc(recordsArray, totalRecords*recordStructSize) ;
        if(recordsArray==NULL){
          fprintf(stderr, "Could not reallocate memory at line %d.\n", __LINE__);
          return 3;
        }  
        break;
    }
  }

正在读取的文件示例:

LEANN RIMES
WHAT A WONDERFUL WORLD
4628
BLUE CHEER
WHAT DOESNT KILL YOU
9664
WITHIN TEMPTATION & KEITH CAPUTO
WHAT HAVE YOU DONE
3226
WITHIN TEMPATION
WHAT HAVE YOU DONE
8093
KOKO TAYLOR
WHAT IT TAKES (THE CHESS YEARS)
7160
DOOBIE BROTHERS
WHAT WERE ONCE VICES ARE NOW HABITS
2972
LIL'ED & THE BLUES IMPERIALS
WHAT YOU SEE IS WHAT YOU GET
9443
VARIOUS ARTISTS
WHAT'S SHAKIN
4473

结构:

typedef struct{
  char author[20], title[50];
  short unsigned int numberOfSales;
} RECORD;

新的循环:

for(int i=0;i!=3;i++){
    switch(i){
      case 0:
        if(fgets(recordsArray[recordsArrayPosition].author, totalRecords, recordsRegistry)==NULL){
          //printf("aa\n");
          i=2;//stop condition
        }
        break;
      case 1:
        if(fgets(recordsArray[recordsArrayPosition].title, totalRecords, recordsRegistry)==NULL){
          //printf("aaa\n");
          i=2;//stop condition
        }
        break;
      case 2:
        if(fscanf(recordsRegistry,"%hu", &((recordsArray[recordsArrayPosition]).numberOfSales))!=1){
          //printf("aaaa\n");
          i=2;//stop condition
        }
        i=-1;
        recordsArrayPosition++;
        totalRecords++;
        recordsArray=realloc(recordsArray, totalRecords*recordStructSize) ;
        if(recordsArray==NULL){
          fprintf(stderr, "Could not reallocate memory at line %d.\n", __LINE__);
          return 3;
        }  
        break;
    }
  }

它打印什么

标签: c

解决方案


...当 fscanf 返回一个不同于 1 的值时会导致无限循环

当您将i设置为 3 时,将不会在i!=3中测试该值,因为在测试之前i++将完成

i设置为 2


只有 %s ...它只扫描到我希望它扫描到新行的空间。

如果您想读取每行使用fgets而不是fscanf,请不要忘记删除可能的换行符

scanf系列中的 's' 匹配一系列 非空白字符,空格是分隔符

man scanf说:

   s      Matches a  sequence  of  non-white-space  characters;  the  next
          pointer  must be a pointer to the initial element of a character
          array that is long enough to hold the  input  sequence  and  the
          terminating null byte ('\0'), which is added automatically.  The
          input string stops at white space or at the maximum field width,
          whichever occurs first.

警告您混合读取行和值,当您读取值时未读取换行符,将“%hu”替换为“%hu\n”或非常安全地读取该行,然后从中提取数字(我在我的提议)


从你的言论

为什么 i++ 会在 i!=3 之前进行测试?

您的 :

for(int i=0;i!=3;i++){
 <body without continue>
}

相当于

{ int i = 0;

  while (i != 3) {
    <body without continue>
    i++;
  }
}

这里有一个建议:

#include <stdio.h>
#include <ctype.h>
#include <string.h>

typedef struct{
  char author[20], title[50];
  short unsigned int numberOfSales;
} RECORD;

#define MAXRECORDS 100

void removeEndSpaces(char * s)
{
  char * p = s + strlen(s);

  while ((s != p) && isspace((unsigned char) *--p))
    *p = 0;
}

int main()
{
  FILE * fp = fopen("f", "r");
  RECORD records[MAXRECORDS];
  int nrecords;
  char line[32];

  if (fp == NULL){
    perror("cannot read f");
    return -1;
  }

  for (nrecords = 0; nrecords != MAXRECORDS; nrecords += 1) {
    if (fgets(records[nrecords].author, sizeof(records[nrecords].author), fp) == NULL)
      break;
    removeEndSpaces(records[nrecords].author);

    if (fgets(records[nrecords].title, sizeof(records[nrecords].title), fp) == NULL) {
      fprintf(stderr, "invalid input file\n");
      break;
    }
    removeEndSpaces(records[nrecords].title);

    /* the more secure way to read the number is first to read the line then read the enumber in that line */
    if ((fgets(line, sizeof(line), fp) == NULL) ||
        (sscanf(line, "%hu", &records[nrecords].numberOfSales) != 1)) {
      fprintf(stderr, "invalid input file\n");
      break;
    }
  }

  /* nrecords values the number of records read without error */
  for (int i = 0; i != nrecords; i += 1)
    printf("%s : %s / %hu\n", 
           records[i].author, records[i].title, records[i].numberOfSales);

  return 0;
}

如你所见,用索引做你的东西是没用的,代码更清晰

假设文件f包含您的输入、编译和执行:

pi@raspberrypi:/tmp $ gcc -Wall -Werror -pedantic a.c -g
pi@raspberrypi:/tmp $ ./a.out
invalid input file
LEANN RIMES : WHAT A WONDERFUL WORLD / 4628
BLUE CHEER : WHAT DOESNT KILL YOU / 9664
pi@raspberrypi:/tmp $ 

如您所见,该文件无效,原因是作者“WITHIN TEMPTATION & KEITH CAPUTO”更多换行符太长,无法保存为 20 个字符,这就是为什么您总是需要检查发生的情况并且永远不要认为一切正常的原因:在您的其他问题的初始代码中fscanf写出具有未定义行为的项目。例如,要使用(f/s)scanf读取最多 20 个字符,包括字符串中的空字符,请使用格式“%20s”

如果我将字段作者调整为 40,一切正常:

pi@raspberrypi:/tmp $ gcc -Wall -Werror -pedantic a.c -g
pi@raspberrypi:/tmp $ ./a.out
LEANN RIMES : WHAT A WONDERFUL WORLD / 4628
BLUE CHEER : WHAT DOESNT KILL YOU / 9664
WITHIN TEMPTATION & KEITH CAPUTO : WHAT HAVE YOU DONE / 3226
WITHIN TEMPATION : WHAT HAVE YOU DONE / 8093
KOKO TAYLOR : WHAT IT TAKES (THE CHESS YEARS) / 7160
DOOBIE BROTHERS : WHAT WERE ONCE VICES ARE NOW HABITS / 2972
LIL'ED & THE BLUES IMPERIALS : WHAT YOU SEE IS WHAT YOU GET / 9443
VARIOUS ARTISTS : WHAT'S SHAKIN / 4473
pi@raspberrypi:/tmp $ 

推荐阅读