字符串长度、终止符NUL等问题

3
我是一名有用的助手,可以为您翻译文本。

我目前正在学习C语言,对字符数组和字符串之间的区别以及它们的工作原理感到困惑。

问题1:

为什么源代码1和源代码2的结果不同?

源代码1:

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

int main(void)
{
    char c[2]="Hi";
    printf("%d\n", strlen(c));   //returns 3 (not 2!?)
    return 0;
}

源代码2:

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

int main(void)
{
    char c[3]="Hi";
    printf("%d\n", strlen(c));   //returns 2 (not 3!?)
    return 0;
}

问题2:

字符串变量和字符数组有何不同?如何声明它们以允许存储\0,并具有最小所需的索引号(请阅读下面的代码)?

char name[index] = "Mick";   //should index be 4 or 5?

char name[index] = {'M', 'i', 'c', 'k'};   //should index be 4 or 5?

#define name "Mick"   //what is the size? Is there a \0?

问题3:

终止的NUL只跟随字符串而不是字符数组吗?所以字符串“Hi”的实际值是[H][i][\0],而字符数组“Hi”的实际值是[H][i]?

问题4:

假设c[2]将存储带有\0的“Hi”(不确定如何完成此操作,可能使用gets(c)?)。那么\0存储在哪里?它存储在c[2]之后的“某个地方”,变成[H][i]\0,还是c[2]会被追加一个\0,变成c[3],即[H][i][\0]?

有时候字符串/字符数组后面会跟着\0,这让我很困惑,当我使用if (c1==c2)比较两个变量时,它很可能返回FALSE(0)。

非常感谢详细的答案。但是保持简洁的回答有助于我的理解 :) 提前感谢您!


+1:非常好的问题,表述得很清楚! - pmg
4个回答

3

问题 1:

源代码 1 和源代码 2 的结果有何区别?

源代码 1:

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

int main()
{
    char c[2]="Hi";
    printf("%d", strlen(c));   //returns 3 (not 2!?)
    getchar();
}

源代码2:

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

int main()
{
    char c[3]="Hi";
    printf("%d", strlen(c));   //returns 2 (not 3!?)
    getchar();
}
答案: 由于在第一种情况下,c []仅保存“ Hi”。strlen查找结尾处的零,并根据c []后面确切的内容找到一个或更多个,或崩溃。我们无法确定c []数组后面的内存中确切包含什么。

问题2:

字符串变量与char数组有何不同?如何在允许存储\0的最小必需索引号下声明它们(请阅读以下代码)?

char name[index] = "Mick";   //should index be 4 or 5?

char name[index] = {'M', 'i', 'c', 'k'};   //should index be 4 or 5?

答案 这取决于你想做什么。如果你想将内容作为字符串使用,则可能需要5个字符。但是并没有说你不能将"Mick"存储在一个4个字符的数组中 - 只是你不能使用strlen来查找它的长度,因为strlen将继续查找到5,很可能(更)进一步查找长度,如果下几个内存位置中没有零,则可能导致崩溃,因为最终,将没有有效的内存地址可以读取。

#define name "Mick" //大小是多少?有\0吗?

这根本没有大小,直到你在某个地方使用name。#define不是编译器看到的内容-预处理器将在任何地方使用name时将其替换为"Mick" - 希望这是一个编译器可以理解的地方。然后与先前的答案相同-取决于您如何使用字符数组。对于正确操作strlenstrpy和几乎所有其他str ...函数,您需要在结尾处有一个零。

问题3:

终止的null只跟随字符串而不跟随字符数组吗?因此字符串“Hi”的实际值是[H] [i] [\ 0],字符数组“Hi”的实际值是[H] [i]?

是的,不是,可能。这取决于您如何使用"Hi"字符串文字(这是“双引号内的内容”的技术名称)。如果编译器“允许”,它将在结尾处放置一个零。但是,如果将数组初始化为给定大小,则会将字节塞入其中,如果没有空间放置零,则这是您的问题,而不是编译器的问题。

问题4:

假设c [2]将存储由\0后跟的“Hi”(不确定如何完成此操作,可能使用gets(c)?)。那么\0存储在哪里?是在c [2]之后的“某个地方”存储以成为[H] [i] \ 0还是将c [2]附加到\0以成为c [3],即[H] [i] [\0]?

在c [2]中,“H”,“i”之外,无法确定存储了什么[从技术上讲,它很可能是“地球的尽头”-在计算机术语中,这是“无法读取的内存” - 在这种情况下,对其进行strlen WILL崩溃你的程序,因为strlen读取超出地球的尽头]。但是如果可以是零,一,字母'a',数字42或任何其他8位[1]值。

有时在字符串/字符数组后会出现\0,这很令人困惑,当我用“if(c1 == c2)”比较两个变量时会导致问题,因为它很可能返回FALSE(0)。
如果c1和c2是char数组,那么它们永远不会相同,因为c1和c2永远不会有相同的地址。在C中使用数组的方式是将其作为“数组中第一个元素在内存中的地址”。所以无论c1和c2的内容是什么,它们的地址都不能相同[因为它们是两个不同的变量,而且两个变量在内存中不可能有相同的位置——就像试图将两辆车停在只能容纳一辆车的停车位上一样——在我们的思想实验中,不能压碎任何一辆车]。
[1]Char并不保证为8位,但让我们暂且忽略这一点。

3

答案1: 在代码1中,你有一个字符数组,它不是一个字符串;在代码2中,你也有一个字符数组,它是一个字符串。

答案2: 字符串是一个字符数组,在其中(至少)一个元素的值为0;如果你将大小部分留空,编译器将自动填充最小可能值。

char astring[] = "foobar"; /* compiler automagically uses 7 for size */
printf("%d\n", (int)sizeof astring);

答案3: 一个字符数组中如果有一个元素是NUL,那么它就是一个字符串;如果没有任何元素是NUL,那么它不是一个字符串。

答案4: 一个定义了两个元素的数组(char c [2];)无法容纳三个元素。如果它将成为一个字符串,它只能是空字符串或者只有一个字符的字符串。


谢谢你的回复。我明白了,所以一个字符串必须是一个字符数组,但一个字符数组可能不是一个字符串,而一个字符数组/字符串的内容/元素可能会因为处理字符数组/字符串的操作而有所不同,对吧?因此,如果进行字符串操作且字符数组已满且不包含 \0,则最后一个字符将不会被处理(在字符串操作期间被省略/丢弃)。 - blackr1234
如果没有足够的空间来存储NUL(或任何其他值),则会出现未定义行为。在处理字符串时,一定要确保有足够的空间来存储所有内容,包括终止符NUL - pmg

1

运行源代码一是未定义的行为,因为strlen()需要一个以NUL结尾的字符串,而c[2] = "Hi"; /* = { 'H', 'i' } */不是。字符串与字符数组的区别在于字符串是具有至少一个NUL字节的字符数组。

其余的答案应该很容易从这个事实中得出。

要自动调整字符数组的大小以匹配初始化时的字符串文字大小,只需指定没有数组大小:

char c[] = "This will automatically size the c array (including the NUL).";

请注意,您不能使用“==”运算符比较字符数组。您必须使用
if (strcmp(c1, c2) == 0) {
   /* Equal. */
} else {
   /* Not equal. */
}

谢谢您的回复。如果我在声明后不打算初始化字符数组怎么办?假设我需要读取一个未知长度或已知长度范围的输入?比如说最大长度为5,可以存储单词“Hello”。我应该使用char c[6];作为声明吗?如果用户输入“Hello!”,'!'会发生什么? - blackr1234
如果你使用fgets(c, sizeof c, stdin)读取输入,那么'!'(和\n)会被卡在缓冲区中。c的内容将是{'H', 'e', 'l', 'l', 'o', '\0'}。如果你使用scanf("%s", c);读取输入,那么你会尝试在数组外部写入,这将导致未定义行为。使用scanf("%5s", c);读取输入会将'!'留在输入缓冲区中。 - pmg
@pmg 好的,我现在明白了。顺便说一下,我找不到KingsIndian的答案了,但我仍然想知道为什么“zero\0one\0”的大小是10,它是否被读作[z][e][r][o][\0][o][n][e][\0][\0](末尾有双\0)? - blackr1234
@blackr1234:是的,字符串字面值"zero\0one\0"在结尾处包含双重的'\0'字符串字面值始终包含一个“额外”的'\0',即使多余的(字符串字面值在源代码中由引号限定)。 - pmg
不,那是 strlen()sizeof 返回对象(或类型)占用的字节数。字符串字面值 "boo" 的类型是 char [4];而 "zero\0\0\0" 的类型是 char [8] ... - pmg
显示剩余2条评论

1

strlen() 适用于以 \0 结尾的字符,在 C 中,所有字符串都应该以 \0 结尾。因此,当你只为两个字符 Hi 分配了2个空间,但没有留出空间给 \0。因此,在 strlen() 中会出现 未定义行为

char c[3] = "Hi"; 的情况下,第三个位置有一个 \0strlen() 将计算实际长度。

如何使用最小的索引号声明它们,以允许存储任何 \0

当你不确定 char 数组的大小时,可以这样做:

char c1[] = "Mike"; // strlen = 4 
char c2[] = "Omkant" // strlen = 6

注意:

编辑:在上述情况中,如果没有明确指明大小,请不要将 sizeofstrlen() 混淆。

strlen() 只返回字符数。sizeof 返回的是字符数加上一个(用于 \0 字符)。

因此,sizeof 总是比 strlen() 返回的数字多 1。


@pmg:明确指出,如果您不提供大小,那么意味着空括号[],例如char c[] = "Hi"; - Omkant

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