【问题标题】:About string length, terminating NUL, etc关于字符串长度、终止NUL等
【发布时间】:2012-12-31 21:23:41
【问题描述】:

我目前正在学习 C,我对 char 数组和字符串之间的差异以及它们的工作方式感到困惑。

问题 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:

字符串变量与 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?

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

问题 3:

终止的 NUL 是否只跟随字符串而不跟随字符数组?那么字符串“Hi”的实际值为[H][i][\0],而char数组“Hi”的实际值为[H][i]?

问题 4:

假设 c[2] 将存储 "Hi" 后跟 \0(不确定这是如何完成的,使用 gets(c) 可能吗?)。那么 \0 存储在哪里?它是存储在 c[2] 之后的“某处”变成 [H][i]\0 还是 c[2] 附加一个 \0 成为 c[3] 即 [H][i][\0 ]?

有时字符串/字符数组后面有一个 \0 并且当我通过 if (c1==c2) 比较两个变量时会导致麻烦,因为它很可能返回 FALSE (0),这很令人困惑。

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

【问题讨论】:

  • +1:很好的问题,很好的表述!

标签: c string arrays


【解决方案1】:

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

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

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

答案3:其中一个元素为NUL的char数组是一个字符串;没有元素为 NUL 的 char 数组不是字符串。

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

【讨论】:

  • 感谢您的回复。哦,我明白了,所以字符串必须是 char 数组,但 char 数组可能不是字符串,并且 char 数组/字符串的内容/元素可能会有所不同,具体取决于为处理 char 数组/字符串而执行的操作,对?所以如果进行字符串操作,而char数组已满且不包含\0,那么最后一个字符将不会被处理(字符串操作过程中省略/放弃)。
  • 如果NUL(或任何其他值)没有空间,您将获得未定义行为。处理字符串时,请始终确保有足够的空间存放所有内容,包括终止的 NUL
  • 好的。谢谢你的建议!
【解决方案2】:

问题一:

为什么 source code 1 和 source 的结果会有差异 代码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" //what is the size? Is there a \0?

这绝对没有大小,除非您在某处使用名称。 #defines 不是编译器看到的内容的一部分——如果你在任何地方使用name,预处理器会将name 替换为"Mick"——希望这是编译器可以理解的地方。然后适用与上一个答案相同的规则 - 这取决于您要如何使用字符数组。要正确操作 strlenstrpy 和几乎所有其他 str... 函数,最后需要一个零。

问题 3:

终止的 null 是否只跟在字符串后面而不跟在 char 数组后面?所以 字符串“Hi”的实际值为 [H][i][\0] 和实际值 char 数组 "Hi" 是 [H][i]?

是的,不,也许。这完全取决于您如何使用"Hi" 字符串文字(这是“双引号内的东西”的技术名称)。如果编译器是“允许的”,它会在最后放一个零。但是,如果您将数组初始化为给定的大小,它将把字节填充在那里,如果没有零空间,那是您的问题,而不是编译器的问题。

问题 4:

假设 c[2] 将存储 "Hi" 后跟 \0(不确定如何 这完成了,也许使用gets(c)?)。那么 \0 存储在哪里?是吗 存储在 c[2] 之后的“某处”成为 [H][i]\0 或 c[2] 将 附加一个 \0 成为 c[3] 即 [H][i][\0]?

在 c[2] 中,除了 'H'、'i' 之外,不知道存储了什么 [从技术上讲,它很可能是“地球的尽头”——用计算机术语来说,这是“可以'不被读取 - 在这种情况下strlen 会崩溃你的程序,因为 strlen 读取超出地球的尽头]。但如果也可以是零,一,字母 'a',数字 42,或任何其他 8 位 [1] 值。

有时会在 字符串/字符数组,当我比较两个变量时会引起麻烦 if (c1==c2) 因为它很可能返回 FALSE (0)。

如果 c1 和 c2 是 char 数组,那总是错误的,因为 c1 和 c2 永远不会有相同的地址,并且当以这种方式在 C 中使用数组时,它变成“第一个在内存中的地址数组中的元素”。所以无论 c1 和 c2 的内容是什么,它们的地址永远不会相同[因为它们是两个不同的变量,并且两个变量在内存中不能有相同的位置 - 这就像试图将两辆车停在一个停车位大到只能容纳一辆车——不,在我们的思想实验中不允许压碎任何一辆汽车]。

[1] 不保证字符为 8 位。但是,让我们暂时先讨论一下。

【讨论】:

    【解决方案3】:

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

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

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

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

    请注意,您不能用 == 运算符比较 char 数组。你必须使用

    if (strcmp(c1, c2) == 0) {
       /* Equal. */
    } else {
       /* Not equal. */
    }
    

    【讨论】:

    • 感谢您的回复。如果我不打算在声明后初始化 char 数组怎么办?假设我需要读取长度未知或长度已知范围的输入?假设最大长度为 5,可以存储单词“Hello”。我应该使用char c[6]; 作为声明吗? “!”会发生什么?如果用户输入“你好!”?
    • 如果您使用fgets(c, sizeof c, stdin) 读取输入,则'!'(和'\n')会卡在缓冲区中。 c 的内容将是 {'H', 'e', 'l', 'l', 'o', '\0'}。如果您使用scanf("%s", c); 读取输入,则会调用未定义行为来尝试在数组外部写入。使用scanf("%5s", c); 读取输入会将'!' 留在输入缓冲区中。
    • @pmg 好的,我现在明白了。顺便说一句,我再也找不到 KingsIndian 的答案,但我仍然想知道为什么 "zero\0one\0" 的大小是 10,它读作 [z][e][r][o][\0][ o][n][e][\0][\0] (双 \0 结尾)?
    • @blackr1234:是的,字符串文字 "zero\0one\0" 在末尾包含双 '\0'字符串文字总是包含一个“额外的”'\0',即使是多余的(字符串文字在源代码中由引号分隔)。
    • @pmg 但是为什么它的大小不是 5? sizeof 不计算最左边的 \0 吗?
    【解决方案4】:

    strlen() 适用于\0 终止字符,在C 中,所有字符串都应以\0 终止。因此,当您只为 2 个字符 Hi 提供 2 个空格但没有空间用于 \0 时。因此,您在strlen() 中得到Undefined Behavior。 如果是char c[3] = "Hi";,则在第三位有\0,strlen() 将计算实际长度。

    How to declare them with the minimum required index numbers allowing \0 to be stored if any ?

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

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

    注意:

    编辑:在上述没有明确提及尺寸的情况下,不要将sizeofstrlen()混淆。

    strlen() 仅返回字符数 sizeof 给出字符数加一(对于\0 字符)。

    所以 sizeof 总是比 strlen() 返回的数字多 1。

    【讨论】:

    • @pmg :明确提到,如果您不提供大小,则表示空括号 [],例如 char c[] = "Hi";
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-22
    • 2013-05-05
    相关资源
    最近更新 更多