在字符指针数组str中str+i和str[i]的含义是什么
如何通过获取用户输入来打印字符串数组?我在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("%sn", 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("%sn", 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("%sn", str[i]);
}
}
回答
字符串是 c 中痛苦的学习经历。它与高级语言完全不同。
首先,要回答您的明确问题,str[i]是数组中第 i 个元素的值。如果str指向字符数组"Hello",则str[1]是值“e”。 str + i另一方面,是指向数组中第 i 个元素的指针。在同样的情况,其中str点到字符数组"Hello",str + 1是一个指针,指向该数组中的“e”。 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又名字符' ')而不是为其分配的内存的末尾结束,额外的未使用字符数组中的空间根本无关紧要。
再次,抱歉这么久。这基本上是每个曾经在地球表面上过的 C 程序员的困惑之源。我还没有遇到过一个最初没有被指针混淆的 C 程序员,更不用说 C 如何处理数组了。
其他三个细节:
- 我建议将名称从 更改
str为strs。它不会影响代码的运行方式,但如果您将对象视为数组,则使用复数形式往往会更易读。如果我正在阅读代码,则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。
- 如果你这样做,你也可以按照评论中的建议制作一个