C语言中strcmp函数对于相等的字符串未返回0

9

我已经尝试过广泛搜索解决此问题,但只能找到其中一个字符串缺少换行符或空字节的帖子。我相当确定这里不是这种情况。

我正在使用以下函数将一个单词与包含每行一个单词的单词列表文件(函数中的字典)进行比较。这是代码:

int isWord(char * word,char * dictionary){
  FILE *fp;
  fp = fopen(dictionary,"r");
  if(fp == NULL){
    printf("error: dictionary cannot be opened\n");
    return 0;
  }
  if(strlen(word)>17){
    printf("error: word cannot be >16 characters\n");
    return 0;
  }
  char longWord[18];
  strcpy(longWord,word);
  strcat(longWord,"\n");
  char readValue[50] = "a\n";
  while (fgets(readValue,50,fp) != NULL && strcmp(readValue,longWord) != 0){
    printf("r:%sw:%s%d\n",readValue,longWord,strcmp(longWord,readValue));//this line is in for debugging
  }
  if(strcmp(readValue,longWord) == 0){
    return 1;
  }
  else{
    return 0;
  }
}

代码编译无误,函数可以正确读取字典文件并打印出其中的单词列表。然而,我的问题在于,即使两个字符串完全相同,strcmp 也不会返回0,因此该函数对任何输入都会返回false。
例如,我得到的结果是:
r:zymoscope
w:zymoscope
-3

有什么想法吗?我觉得我一定是漏看了什么显而易见的东西,但在搜索中找不到任何信息。


9
我猜你正在使用Windows电脑,你已经阅读了带有CRLF行结束符的文件,并且你没有正确地去除这些行结束符。在许多(或者说大部分)字符集中,'\r'和'\n'之间的值有3个差异。有趣的是你只有一个printf()函数打印所有数据,但在格式字符串中没有包含\n。你依赖于数据中的换行符,这似乎有些可疑。(编写一个函数以十六进制形式打印字符串中的字节;在每个字符串上调用它;发现区别所在。) - Jonathan Leffler
3
另外,你的文件从未被关闭。在打开文件之前尽可能进行所有检查。完成后(或出现错误时)立即关闭文件。 - user2371524
4
建议在使用fgets(readValue,50,fp)后立即使用readValue[strcspn(readValue, "\r\n")] = 0;来消除行尾字符。 - chux - Reinstate Monica
1
好的;在某些方面,这种情况(Linux读取可能 - 很可能 - 在Windows上创建的文件)更有意义。提供我建议的字符串转储函数并使用它。您可以使用:static void dump_string(const char *tag, const char *string) { size_t len = strlen(string); printf("%s (%zu):", tag, len); size_t i; for (i = 0; i < len; i++) { printf(" %.2X", (unsigned char)string[i]); if (i % 16 == 15) putchar('\n'); } if (i % 16 != 0) putchar('\n'); } 并调用它:dump_string("r", readValue); dump_string("w", longWord); 或类似的方式。 - Jonathan Leffler
2
未来参考:1)在打印有问题的字符串时,使用类似于printf("'%s'\n", bad_string);的方式来帮助识别前导和尾随的空格、换行符等。2)与其认为strcmp()是错误的,不如问问为什么这些字符串不相等。 - chux - Reinstate Monica
显示剩余13条评论
2个回答

5

我看到你正在将newline添加到测试字符串中,以尝试解决fgets()保留行结尾的问题。更好的方法是在源头解决这个问题。你可以像这样删除所有尾随内容,在从文件读取后立即进行处理。

readValue [ strcspn(readValue, "\r\n") ] = '\0';   // remove trailing newline etc

2
我想补充一点,对于以 '\0' 结尾的字符串来说,这是完全安全的。如果没有任何类型的行尾符,strcspn() 函数会返回字符串长度,因此不会对已经存在的 '\0' 造成任何伤害。 - Weather Vane

4

你正在读取的字符串包含尾随字符,因此它与您要比较的字符串不同。

删除尾随的换行符(如果有的话也删除回车符); 然后,在进行字符串比较时,您就不需要添加任何换行符或回车符了:

int isWord(char *word, char *dictionary){
  FILE *fp;
  fp = fopen(dictionary, "r");
  if (fp == NULL){
    fprintf(stderr, "error: dictionary cannot be opened\n");
    return 0;
  }
  if (strlen(word) > 16){
    fprintf(stderr, "error: word cannot be >16 characters\n");
    return 0;
  }
  char readValue[50];
  while (fgets(readValue, 50, fp) != NULL){
    char *ep = &readValue[strlen(readValue)-1];

    while (*ep == '\n' || *ep == '\r'){
      *ep-- = '\0';
    }
    if (strcmp(readValue, word) == 0){
      return 1;
    }
  }
  return 0;
}

1
注意:在读取的第一个字符是空字符时,readValue[strlen(readValue)-1]可能会导致未定义的行为/错误。检查字符串长度大于0是解决这个问题的一种方法。 - chux - Reinstate Monica
1
readValue"\n""\r\n"时,while (*ep == '\n' || *ep == '\r'){ *ep-- = '\0'; } *ep-- = '\0';会出现问题。 - chux - Reinstate Monica
虽然他太有礼貌了,不好意思说出来,但是@chux的答案确实避免了这两个问题(尽管可能会在输入行中间停止,与CRLF或LF行结尾分开,这可能需要进一步讨论)。而且,使用POSIX getline()函数而不是fgets()需要进行单独的strlen()操作的原因是它返回读取的数据长度,这使您可以读取前导空字节(如果您认为这是必要的)。 - Jonathan Leffler
是的,我能看出这两个都有问题,最初我选择了这个答案,因为它似乎更通用,可以处理不同可能的行尾。@chux,现在看来最好的方法是使用您的解决方案,并检查每个可能的行尾。 - Xephz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接