4

如何通过获取用户输入来打印字符串数组?我在 和 之间感到str+i困惑str[i]

在我的程序中,不打印字符串。在该程序终止后,它需要 5 个字符串作为输入

#include <stdio.h>
int main()
{

    char *str[5];

    for (int i = 0; i < 5; i++)
    {
        scanf("%s", &str[i]);
    }

    for (int i = 0; i < 5; i++)
    {

        printf("%s\n", str[i]);
    }
}

所以首先,去阅读答案,然后来这里获取完整的代码。通过 2 天的时间,我在以下答案的帮助下找到了两个解决我的问题的方法。

//1.
#include <stdio.h>
#include<stdlib.h>
int main()
{

    char* str[5];

    for (int i = 0; i < 5; i++)
    {
        str[i] = (char *)malloc(40 * sizeof(char));
        scanf("%s", str[i]);
    }

    for (int i = 0; i < 5; i++)
    {

        printf("%s\n", str[i]);
    }

   
}

//2

#include <stdio.h>
#include<stdlib.h>
int main()
{

    char str[5][40];

    for (int i = 0; i < 5; i++)
    {
        
        scanf("%s", &str[i]);
    }

    for (int i = 0; i < 5; i++)
    {

        printf("%s\n", str[i]);
    }

   
}
4

2 回答 2

10

字符串是 c 语言中痛苦的学习经历。它与高级语言完全不同。

首先,要回答您的明确问题,str[i]是数组中第 i 个元素的值。如果str指向字符数组"Hello"str[1]则为值“e”。 str + i另一方面,是指向数组中第 i 个元素的指针。在相同的情况下,str指向字符数组的 where 是指向该数组中"Hello"str + 1e”的指针。 str[i]并且*(str + i)在各方面都是相同的。事实上,规范定义a[b](*(a + b)),以及随之而来的所有行为! (顺便说一句,C 仍然支持一个非常古老的符号i[str],它与str[i]因为指针和整数的加法是可交换的。你永远不应该使用反向形式,但值得注意的是,当我说它们被定义为相同时,我是认真的!)

请注意,我一直非常小心地避免使用“字符串”这个词,而是专注于“字符数组”。C 在技术上没有字符串类型。这在这里很重要,因为您不能做简单的事情std::string str[5](这是创建 5 个字符串的数组的有效 C++ 表示法)来获得可变长度的字符串。你必须确保你有记忆。 char *str[5]创建一个 5 的数组char*,但不创建任何要写入的字符数组。这就是您的代码失败的原因。实际发生的是,每个元素str都是指向未指定地址的指针(创建变量之前存在的垃圾内存),并scanf试图分配给该(不存在的)数组。当您随机写入内存中的某个地方时,就会发生不好的事情!

有两种解决方案。一种是使用使用 malloc 的 Serve Lauijssen 方法。拜托拜托拜托记住使用free()来释放该内存。在几乎任何真正的程序中,您都不想泄漏内存,并且使用free是尽早养成的一个非常重要的习惯。您还应该确保malloc没有返回 null。这是另一种习惯。它实际上从不在桌面上返回 null,但它可以。在嵌入式平台上,它很容易发生。只需检查一下!(而且,从评论中必须提醒我这一点来看,这表明我没有及早养成这个习惯!)

另一种方法是创建一个多维字符数组。您可以使用该语法char str[5][80]创建一个 5x80 的字符数组。

确切的行为有点笨拙,但是您会发现它恰好按照您认为在您的情况下应有的方式行事。您可以只使用上面的语法,然后继续前进。但是,您最终应该回过头来了解它是如何工作的以及下面发生了什么。

C 以从左到右的方式处理对这些多维数组的访问,并将数组“展平”。在 的情况下char str[5][80],这将在内存中创建一个 400 个字符的数组。 str[0]将是一个char [80](一个 80 个字符的数组),它是该内存条中的前 80 个字符。 str[1]将是下一个 80 个字符,依此类推。C 会隐式地将数组衰减为指针,因此当scanf需要 a时char*,它会自动将作为char [80]的值的str[i]转换为char*指向数组第一个字符的 a 。

现在,除了那些明确的“这就是实际发生的事情”之外,您会发现这可以满足您的需求。 char str[5][80]将分配 400 个字符的内存,分为 5 组,每组 80 个。 str[i]将(几乎)总是变成char*指向第 i 组字符开头的指针。然后scanf有一个指向要填充的字符数组的有效指针。因为 C 的“字符串”是以空字符结尾的,这意味着它们在第一个空字符0(又名字符'\0')处结束,而不是在为其分配的内存的末尾,额外的未使用字符数组中的空间根本无关紧要。

再次,对不起它这么久。对于曾经在地球表面增光的每个 C 程序员来说,这基本上是一个困惑的根源。我还没有遇到过最初没有被指针混淆的 C 程序员,更不用说 C 处理数组的方式了。

其他三个细节:

  • 我建议将名称从 更改strstrs。它不会影响代码的运行方式,但是如果将对象视为数组,则使用复数形式往往更具可读性。如果我正在阅读代码,strs[i]看起来像 中的第 i 个字符串strs,而str[i]看起来像字符串中的第 i 个字符。
  • 正如 Bodo 在评论中指出的那样,使用诸如scanf("%79s", str[i])确保您不会阅读太多字符之类的东西是非常非常可取的。稍后,如果您不及早养成这种习惯,您将受到内存损坏的困扰。您在主要系统中读到的绝大多数漏洞利用都是“缓冲区溢出”,即攻击者可以将太多字符写入缓冲区,并在溢出到接下来发生的任何内容时对额外数据进行恶意操作内存空间。我敢肯定,在你的 C 职业生涯的这个阶段,你并不担心攻击者恶意使用你的代码,但以后这将是一件大事。
  • 最终,您将在真正需要的地方编写代码char**,即指向字符指针的指针。多维数组方法在那天实际上不起作用。当我遇到这个时,我必须创建两个数组。第一个是char buffer[400]保存字符的“支持”缓冲区,第二个是char* strs[5]保存我的字符串的缓冲区。然后我必须做strs[0] = buffer + (0 * 80); strs[1] = buffer + (1 * 80);等等。你在这里不需要这个,但我在更高级的代码中需要它。
    • 如果你这样做,你也可以按照评论中的建议来制作一个static char backing[400]. 这会在编译时创建一个可由函数使用的 400 字节块。一般来说,我建议避免这种情况,但为了完整起见,我将其包括在内。由于平台限制,在某些嵌入式软件情况下,您需要使用它。然而,这在多线程情况下被严重破坏,这就是为什么许多依赖静态分配内存的标准 C 函数现在具有_r重入和线程安全的变体结尾。
    • 还有alloca
于 2021-07-13T13:54:58.873 回答
1

既然你已经定义了char *str[5],那么str应该是 type char **。因此,当scanf()expect时char *,给出&str[i]的是 type char **,所以它是不正确的。这就是为什么你的printf("%s\n", str[i])可能不起作用。

( (str+i)which is of type char **and not the same as str[i]) 方法可能适用printf()于上述情况,因为您正在将字符串值读取到&str[i]. 当您输入的字符串很短时,这可能会很好。但是,读取 values to&str[i]并不是你想要的,因为str[i]它已经是 type char *。尝试在上面的代码中输入一个非常长的字符串(如aaaaaaa...),它可能会给你一个Segmentation fault. 所以从技术上讲,该方法被破坏了,您确实需要在读取字符串之前为数组char *中的每个元素分配内存。str例如,你可以做

for (int i=0; i<5; ++i)
  str[i] = (char *) malloc(length_you_wish);

由于 eachstr[i]是 typechar *并且scanf()需要 type 的参数char *,因此使用scanf( "%s", str[i] ),省略&before str[i]

现在,您可以printf()使用 printf( "%s\n", str[i] )or printf( "%s\n", *(str+i) ),其中str[i]or*(str+i)是 类型char *

最后,你需要做

for (int i=0; i<5; ++i)
  free(str[i])

道德: str[i]是指向特定char数组的指针(它保存数组的第一个char元素的地址),但(str+i)指向str[i](它指的是 的地址,str[i]因此应该具有相同的值)。(str+i)&str[i]

于 2021-07-14T12:41:03.880 回答