只是为了确认我所想的事情是否真的发生了。当我将字母表(26 个字母)作为输入时,以下代码会打印出 25,这是因为 fgets 总是自动将 n 个元素数组中的第 n 个元素设置为 ‘\n’ 吗?

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

int main(void)
{
    char str[26];

    printf("String: ");
    fgets(str, 26, stdin);

    printf("%lu\n", strlen(str));
}

这样,当我尝试打印字母表的 strlen 时,它会在 ‘\n’ 之前停止并返回 25?

2

  • 4
    fgets()不会“自动将第 n 个元素设置为”。它\n从输入中读取所有字符,直到给定的缓冲区大小或直到(包括)换行符为止。同样,fgets()不会“添加换行符”。它只是读取输入。如果输入不包含换行符,则缓冲区也不包含换行符。


    – 


  • 2
    问题的根源在于您认为可以将一个包含 26 个字母的字符串存储在一个大小为 26 的数组中。但这并不是 C 语言中字符串的工作方式,fgets或者说根本不是fgets


    – 



最佳答案
3

如果整行适合缓冲区,根据您的大小参数(26在您的情况下),其中包括字符串空终止符,fgets则会添加换行符。

如果缓冲区无法容纳整行,则fgets不会添加换行符,只会添加缓冲区可以容纳的内容(包括空终止符)。

因此,如果您输入超过 200 个25字符(换行符除外),那么长度将始终为 200 25


另外,您应该始终检查返回的内容。

该函数返回正确格式为类型的值size_t%zu

如果可能的话,sizeof在使用时使用fgets。如fgets(str, sizeof str, stdin)

2

  • +1 表示换行符。你没有忘记,终端的 26 个符号条目也包含换行符,因此我们得到 26+1 或 2+1 = 29 个符号数组


    – 

  • 4
    “然后 fgets 将添加换行符。” –> 更像是“然后 fgets 将读取并保存'z'换行符。”


    – 

OP 输入了:"abcdefghijklmnopqrstuvwxyz" Enter。共有 27 个字符:'a'到 的字符'z'以及换行符'\n'

fgets(str, 26, stdin);读取最多25 个字符stdin,然后附加一个空字符, '\0'

因此strlen(str)是 25 并"z\n"留在stdin稍后阅读。


而是读入更大的缓冲区并砍掉潜在的尾随'\n'

// Large enough for 26 letters, \n, appended \0
char str[           26          +1       +1];

printf("String: ");
if (fgets(str, sizeof str, stdin)) {
  printf("%zu\n", strlen(str));   // Expect 27. Note specifier changed to %zu.
  str[strcspn(str, "\n")] = '\0'; // Lop off potential trailing '\n'.
  printf("%zu\n", strlen(str));   // Expect 26
}

你正在朝着正确的方向思考。

您可以查看文档(大多数情况下使用 cpp 文档是可以的):

它指出,签名是:

char* fgets(char* str, int count, FILE* stream);

文档说:

从给定的文件流中读取最多 count – 1 个字符并将它们存储在 str 指向的字符数组中。

因此,您给它一个 26 个元素的数组,它会用最多 25 个元素填充它并附加终止符。在 C 中,终止符是一个零字节 ( '\0')。

strlen实际上,它会在给定地址后搜索下一个终止​​符。它会找到该终止符str[25] == '\0'并返回 25 作为结果。

在某些应用程序中,该位置存在很大的安全问题 –如果您计划将其用作以空字符结尾的 C 字符串,则应始终char*在字符数组中为终止符添加一个额外的项。否则,所有函数都不会在字符串末尾停止处理 – 它们仅对指针进行操作,并且len大多数情况下没有给出参数。

总是这样吗?

在某些情况下,空字符不用作终止符,甚至可以作为数据的一部分,但在这种情况下,您不应将此数组称为“字符串”。在某些协议中,您会在其他地方列出字符数组的长度。例如,请参阅 BER-TLV。它包含连接的标签号、长度和数组本身。以下是标签 0x55 中包含的“123”的示例:

55 03 31 32 33

31 32 33– ASCII 中的 3 个字节,代表“123”。请注意 – 没有空终止符!

虽然这种格式在通信中被广泛使用,但是您不能在strlen或中使用指向此类数组的指针作为参数printf

感谢@some-programmer-dude。我必须提一下:

当您在终端中输入某个字符串时,您会用回车符(换行符)终止该输入。在 Linux 中它只是"\n"(1 个字节),而在 Windows 中它是"\r\n"(2 个字节)。

假设您输入John并按下 Enter。流将包含:
"John\n"– 即 5 个字节。但要存储此文本并将其用于printf%s替换或例如 ,strlen您还应该存储一个浮动空终止符。因此您可能需要分配至少 6 个字节:

str[6] = {'J', 'o', 'h', 'n', '\n', '\0'};

当您调用 strlen 时,它将返回自给定指针以来非零元素的数量 – 即 5。如果您随后重用该数组并存储,例如仅存储“?”,那么您将获得:

str[6] = {'?', '\n', '\0', 'n', '\n', '\0'};

注意,strlen将返回 2,而在 之后数组不会被清除\0。所有函数都将忽略第一个检测到的字符之后的数据'\0',而 printf(例如)将仅“看到” "?\n"