在C语言中,如何正确地比较字符串?

251

我正在尝试编写一个程序,让用户输入一个单词或字符,存储它,然后打印出来,直到用户再次输入相同的内容,退出程序。我的代码如下:

#include <stdio.h>

int main()
{
    char input[40];
    char check[40];
    int i = 0;
    printf("Hello!\nPlease enter a word or character:\n");
    gets(input);   /* obsolete function: do not use!! */
    printf("I will now repeat this until you type it back to me.\n");

    while (check != input)
    {
        printf("%s\n", input);
        gets(check);   /* obsolete function: do not use!! */
    }

    printf("Good bye!");
    
    return 0;
}

问题是,即使用户输入(check)与原始输入(input)匹配,我仍然不断打印输入字符串。我是否比较这两个值的方式有误?

17
gets( )已经从标准中移除,请改用fgets( )。新函数可以实现同样的功能,但更安全可靠。 - lost_in_the_source
1
请注意,这个答案解释了如何比较字符串的相等性、不相等性、小于、大于、小于或等于以及大于或等于。并非所有的字符串比较都是为了相等性。大小写敏感的比较又是另一回事;其他特殊比较(例如字典顺序)需要更专业的比较器,而复杂的正则表达式则可以用于更复杂的比较。 - Jonathan Leffler
还要注意,此外还有一个基本上重复的问题如何检查值是否与字符串匹配,这个问题是在几年前提出的。 - Jonathan Leffler
这个回答解决了你的问题吗?如何检查一个值是否匹配一个字符串 - Andreas
3
这个问题很好,但使用gets()是不可取的。自C11标准以来,它也已被删除 -> 请阅读为什么gets()函数如此危险,不应该使用? - RobertS supports Monica Cellio
11个回答

367

你不能使用!===来比较字符串(有用地),你需要使用strcmp

while (strcmp(check,input) != 0)
这是因为!===只会比较这些字符串的基地址,而不是它们本身的内容。

12
在Java中相同的内容可能只会与地址进行比较。 - Telerik
43
使用while (strcmp(check, input))是足够的并且被认为是良好的编程实践。 - Shravan
10
使用strncmp更安全!不想发生缓冲区溢出! - Floam
1
@Floam 如果你实际上没有字符串,而是已知长度的零填充非零字符序列,那么这将是正确的咒语。但这完全是另一回事! - Deduplicator
不幸的是,虽然 gets 函数会在用户按下回车键时删除输入中的换行符,但 fgets 函数却不会。为了解决这个问题,可以使用 buffer[strlen(buffer) - 1] = '\0'; 代码来删除换行符。 - ADBeveridge
1
@ADBeveridge buffer[strlen(buffer) - 1] = '\0'; 并不总是有效的,实际上是危险的。fgets() 不总是返回以换行符结尾的字符串,因此您的代码可能会删除有效数据;事实上,fgets() 可能会返回一个零长度的字符串,这意味着 buffer[strlen(buffer) - 1] = '\0'; 将写在数组的边界之外并调用未定义的行为。请参见 Removing trailing newline character from fgets() input - Andrew Henle

44

有几件事需要注意:gets是不安全的,应该使用fgets(input, sizeof(input), stdin)来替换,以避免缓冲区溢出。

其次,在比较字符串时,必须使用strcmp,其中返回值为0表示两个字符串匹配。使用等号运算符(例如 !=)比较两个字符串的地址,而不是它们内部的单个char

还要注意的是,尽管在这个示例中不会引起问题,但fgets也会将换行符'\n'存储在缓冲区中;gets()则不会。如果你将来自fgets()的用户输入与字符串字面量(如"abc")进行比较,它永远不会匹配(除非缓冲区太小,无法容纳'\n')。


你能否请澄清一下"\n"和字符串字面值的关系或问题?我在将文件中的字符串(行)与整个文件进行比较时,得到了不等的结果。 - incompetent
1
如果你使用 fgets() 从文件中读取一行,那么字符串可能是 "abc\n",因为 fgets() 会保留换行符。如果你将其与 "abc" 进行比较,由于在读取数据中的换行符和以空字节终止的 "abc" 之间存在差异,所以你会得到 '不相等' 的结果。因此,你需要去掉换行符。可靠的一行代码是 buffer[strcspn(buffer, "\n")] = '\0';,它的优点是无论缓冲区中是否有任何数据,或者该数据是否以换行符结尾,都可以正确地工作。其他去除换行符的方法容易崩溃。 - Jonathan Leffler
这个答案准确地解决了代码的问题,而得到最多投票和被采纳的答案只涉及回答问题标题。特别要提到的是最后一段非常好。+1 - RobertS supports Monica Cellio

17

使用 strcmp

这个函数在头文件string.h中,非常常用。如果两个字符串相等,strcmp会返回0。查看此处以获取更好的解释。

基本上,你需要执行以下操作:

while (strcmp(check,input) != 0)
或者
while (!strcmp(check,input))
或者
while (strcmp(check,input))

你可以查看这个,一个关于strcmp的教程。


11

你不能像这样直接比较数组

array1==array2

你应该逐个字符地比较它们;为此,你可以使用一个函数并返回布尔值(True:1,False:0)。然后你可以在 while 循环的测试条件中使用它。

试试这个:

#include <stdio.h>
int checker(char input[],char check[]);
int main()
{
    char input[40];
    char check[40];
    int i=0;
    printf("Hello!\nPlease enter a word or character:\n");
    scanf("%s",input);
    printf("I will now repeat this until you type it back to me.\n");
    scanf("%s",check);

    while (!checker(input,check))
    {
        printf("%s\n", input);
        scanf("%s",check);
    }

    printf("Good bye!");

    return 0;
}

int checker(char input[],char check[])
{
    int i,result=1;
    for(i=0; input[i]!='\0' || check[i]!='\0'; i++) {
        if(input[i] != check[i]) {
            result=0;
            break;
        }
    }
    return result;
}

1
请问您能否提供更多关于您的解决方案的细节? - abarisone
是的,这是strcmp函数的替代方案,并且不使用string.h头文件的解决方案。@Jongware - mugetsu
3
这种方法行不通。当checker在一个字符串中找到'\0'时,它不会检查另一个字符串是否存在'\0'。即使一个字符串仅是另一个字符串的前缀(例如,"foo"和"foobar"),该函数也会返回1(表示相等)。请修正此问题。 - lukasrozs
2
我会使用||而不是&& - lukasrozs

9
欢迎来到“指针”的概念。许多初学者发现这个概念很难理解,但如果你想成为一名合格的程序员,你必须掌握这个概念。而且,你已经开始问正确的问题了,这很好。
你清楚地址是什么吗?看看这张图:
----------     ----------
| 0x4000 |     | 0x4004 |
|    1   |     |    7   |
----------     ----------

在这张图中,整数1被存储在地址0x4000的内存中。为什么要有地址呢?因为内存很大,可以存储很多整数,就像城市很大,可以容纳很多家庭一样。每个整数都存储在一个内存位置上,就像每个家庭都住在一个房子里。每个内存位置都由一个地址标识,就像每个房子都有一个地址。
图中的两个方框代表着两个不同的内存位置。你可以把它们看作是房子。整数1位于地址0x4000(类比“Elm St. 4000”),整数7位于地址0x4004(类比“Elm St. 4004”)的内存位置。
你以为你的程序是在比较1和7,但实际上它比较的是0x4000和0x4004。那么当你遇到这种情况时会发生什么呢?
----------     ----------
| 0x4000 |     | 0x4004 |
|    1   |     |    1   |
----------     ----------

两个整数值相同,但地址不同。你的程序比较的是地址。

5

在比较字符串时,应该按照每个字符进行比较。为此,您可以使用内置的字符串函数strcmp(input1,input2);并且您需要使用名为#include<string.h>的头文件。

试试这段代码:

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

int main() 
{ 
    char s[]="STACKOVERFLOW";
    char s1[200];
    printf("Enter the string to be checked\n");//enter the input string
    scanf("%s",s1);
    if(strcmp(s,s1)==0)//compare both the strings  
    {
        printf("Both the Strings match\n"); 
    } 
    else
    {
        printf("Entered String does not match\n");  
    } 
    system("pause");  
} 

3

您需要使用strcmp()函数,并且需要#include <string.h>头文件。

!===操作符仅比较这些字符串的基地址,而不是字符串的内容。

while (strcmp(check, input))

示例代码:

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

int main()
{
    char input[40];
    char check[40] = "end\n"; //dont forget to check for \n

    while ( strcmp(check, input) ) //strcmp returns 0 if equal
    {
        printf("Please enter a name: \n");
        fgets(input, sizeof(input), stdin);
        printf("My name is: %s\n", input);
    }

    printf("Good bye!");
    return 0;
}

注意1: gets() 不安全。请使用 fgets()
注意2:当使用 fgets() 时,您还需要检查 '\n' 换行符。

3
你可以:
使用 strcmp() 函数来自 string.h,这是更简单的版本
或者如果你想自己编写,可以使用类似下面的代码:
int strcmp(const char *s1, const char *s2)
{
    for (i = 0; s1[i] != '\0' || s2[i] != '\0'; i++)
    {
        if (s1[i] != s2[i])
        {
            return (unsigned char)s1[i] < (unsigned char)s2[i] ? -1 : 1;
        }
    }
    return 0;
}

我会这样使用strcmp()函数:
while (strcmp(check, input))
{
    // code here
}

你可能想在strcmp函数的结尾处加上return 0; - Harrison
不是必须的,但是这是一个好习惯。 - Anic17
我冒昧地修复了你的strcmp函数的原型和实现。它必须根据不同字符串参数的字典顺序返回负值或正值,并且你忘记将i初始化为0:使用for循环比使用while循环更少出错。此外,你的代码在任一字符串是另一个字符串的前缀时返回0。最后的return语句是必需的,否则对于相同的字符串,该函数将具有未定义的行为。 - chqrlie

2
如何正确比较字符串?
char input[40];
char check[40];
strcpy(input, "Hello"); // input assigned somehow
strcpy(check, "Hello"); // check assigned somehow

// insufficient
while (check != input)

// good
while (strcmp(check, input) != 0)
// or 
while (strcmp(check, input))

让我们深入了解为什么check!=input不足够
在C中,字符串是标准库规范。

字符串是以包括第一个空字符在内的连续字符序列。
C11 §7.1.1 1

上面的input不是字符串inputchar类型的40个数组input的内容可以成为一个字符串
在大多数情况下,当数组用于表达式中时,它会转换为其第一个元素的地址。
以下代码将checkinput转换为它们各自的第一个元素的地址,然后比较这些地址。
check != input   // Compare addresses, not the contents of what addresses reference

为了比较字符串,我们需要使用这些地址,然后查看它们指向的数据。
strcmp()可以胜任此工作。 §7.23.4.2

int strcmp(const char *s1, const char *s2);

strcmp函数将指向s1的字符串与指向s2的字符串进行比较。

strcmp函数返回一个整数,如果s1指向的字符串大于、等于或小于s2指向的字符串,则相应地大于、等于或小于零。

代码不仅可以找到字符串是否具有相同的数据,而且在它们不同时还可以找出哪个更大/更小。

当字符串不同时,以下内容是正确的。
strcmp(check, input) != 0

如需了解更多信息,请参见创建自己的strcmp()函数


-2
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char s1[50],s2[50];
        printf("Enter the character of strings: ");
        gets(s1);
        printf("\nEnter different character of string to repeat: \n");
        while(strcmp(s1,s2))
        {
            printf("%s\n",s1);
            gets(s2);
        }
        return 0;
    }

这是一个非常简单的解决方案,可以让您按照所需的方式获得输出。


3
gets(); 自 C11 起不再是标准 C 的一部分。 - chux - Reinstate Monica
3
strcmp(s1,s2)是未定义行为,因为s2的内容未在最初指定。 - chux - Reinstate Monica
如果您能以某种形式提供此片段的输出,那将非常好。 - not2qubit

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