【问题标题】:Can a pointer to a string be used in a printf?可以在 printf 中使用指向字符串的指针吗?
【发布时间】:2011-10-11 14:01:45
【问题描述】:

我在想这样的事情:

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

int main(void) {
    //test pointer to string
    char s[50];
    char *ptr=s;
    printf("\nEnter string (s): ");
    fgets(s, 50, stdin);
    printf("S: %s\nPTR: %s\n", s, *ptr);

    system("PAUSE");
    return 0;
}

或者我应该使用带有 *(s+i) 和格式说明符 %c 的 for 循环? 这是通过指针和简单的 printf 打印字符串的唯一可能方法吗?

更新:printf 使用数组的第一个元素的地址进行操作,因此当我使用 *ptr 时,我实际上使用的是第一个元素而不是它的地址。谢谢。

【问题讨论】:

  • 请不要使用gets。 “永远不要使用gets()。因为事先不知道数据是不可能知道gets()会读取多少个字符的,而且因为gets()会继续存储超过缓冲区末尾的字符,所以非常危险使用。它已被用来破坏计算机安全性。请改用 fgets()。” (fgets(3))。
  • 如果您不介意,我调整了代码以最大限度地减少 cmets 对您所写内容的可能性,这些内容对问题并不重要。即使用gets()main()的返回类型。您应该不惜一切代价避免使用gets(),并使用其他方法,例如fgets()(正如@Bertrand 解释的那样)。 main() 也应该总是返回int

标签: c arrays string pointers printf


【解决方案1】:

printf"%s" 格式说明符总是需要一个 char* 参数。

给定:

char s[] = "hello";
char *p = "world";
printf("%s, %s\n", s, p);

看起来就像您为第一个 %s 传递了一个数组,为第二个传递了一个指针,但实际上您(正确地)为两者传递了指针。

在 C 中,任何数组类型的表达式都会隐式转换为指向数组第一个元素的指针除非它位于以下三个上下文之一中:

  • 它是一元“&”(地址)运算符的参数
  • 它是一元“sizeof”运算符的参数
  • 它是用于初始化数组对象的初始化程序中的字符串文字。

(我认为C++还有一两个例外。)

printf()的实现看到"%s",假设对应的参数是一个指向char的指针,并使用该指针遍历字符串并打印出来。

comp.lang.c FAQ 的第 6 节对此进行了很好的讨论。

【讨论】:

  • 关于 any expression of array type is implicitly converted to a pointer to the array's first element - 虽然这是真的,但内存明智的程序将为 p 变量分配 4 或 8 个字节(取决于平台)来存储 w 字符的地址,但情况并非如此s 变量。 s 之所以能够表现得像指针,仅仅是因为编译器的技巧,因为编译器知道数组总是存在于连续的内存块中。存储h 字符地址的s 变量不会发生单独的内存分配。如果我有任何错误,请纠正我。
  • 我不会说s“能够充当[a]指针”。 s 是一个数组对象。在大多数情况下,表达式s 是一个指针表达式。对于char *p = "world";,为p分配sizeof (char*)字节,为字符串字面量对应的数组对象分配6个字节。没错,s 没有单独分配内存;声明 char s[] = "hello"; 不创建指针对象。
  • 我之所以说“s 能够充当 [a] 指针”是因为 The "%s" format specifier for printf always expects a char* arguments 是一个数组对象仍然足以满足参数的期望:)。但我明白你的意思。当s 实际上是一个数组对象时,我们不应该将它称为指针。
  • @RBT:将“数组”和“指针”视为形容词,而不是名词。数组expression 可以变成指针expression(“转换”,可能应该称为“调整”,发生在编译时)。数组object永远不会变成指针object
【解决方案2】:
printf("%s\n", ptr);

这是你想要的吗?

顺便说一下,来自printf(3),这里是s 转换说明符(即%s)的文档:

如果不存在 l 修饰符:const char * 参数应该 是指向字符类型数组的指针(指向字符串的指针)。 数组中的字符最多写入(但不包括)a 终止空字节('\0');如果指定了精度,则不再 比指定的数量被写入。如果给出精度,则不 需要存在空字节;如果未指定精度,或者是 大于数组的大小,数组必须包含一个 终止空字节。

【讨论】:

  • @AndrewGH:Betrand Marron 是正确的。 *ptr 具有 char 类型,但 %s 需要 char* 类型,因此 printf("%s\n", ptr); 是正确的。
  • @Andrew G.H., No. ptr 是指向 char 的指针。 *ptr 是一个字符。
  • @Andrew G.H.,是的printf("%s\n", ptr)。如果你不明白,我强烈建议你阅读一本关于指针的书并学习使用它们。
  • 既然知道了,为什么不能通过 *ptr 访问字符串呢?因为 *ptr 等价于 array[0] ...所以我不明白为什么只使用 ptr?
  • @Andrew: *ptr 只是字符串的第一个字符,不是整个字符串。大多数作用于字符串的操作都是通过指向字符串第一个字符的指针间接执行的。如果将*ptr 传递给函数,函数接收的唯一信息就是第一个字符的;它使函数无法访问字符串的其余部分。传递*ptr 让函数通过取消引用指针来访问第一个字符,通过递增指针然后取消引用来访问第二个字符,依此类推。
【解决方案3】:

你应该这样做 "printf("S: %s\nPTR: %s\n", s, ptr); " 而不是 printf("S: %s\nPTR: %s\n", s, *ptr);

ptr 和 *ptr 之间的区别是:ptr 为您提供了您所指向的变量在内存中的 地址,而 *ptr 则提供了 值指向变量的 在这种情况下是 *ptr = ptr[0]

这段代码将说明我的意思:

printf("\tS: %s\n\tPTR: %s\n\tAddress of the pointed Value: %x\n\tValue of the whole String: %s\n\tValue of the first character of the String: %c\n", s, ptr,ptr,ptr,*ptr);

【讨论】:

  • 是的,当然,但是,当使用printf("%s", array_name) 时,您基本上可以访问&array_name[0],不是吗?...哦,没关系,我明白了。 printf 使用数组的第一个元素的地址进行操作,因此当我使用 *ptr 时,我实际上使用的是第一个元素而不是它的地址。谢谢。
  • @Andrew G.H: printf("%s", array_name) 不允许您访问 &array_name[0] 而是 &array_name。 &array_name 是特定类型变量的地址。处理每个变量的方式因类型而异。对于字符串(char *), %array_name 但要像你说的第一个字符,但由于 "%s" , printf 必须读取 array_name 直到 '\0' 被发现
  • 数组的标识符等于数组第一个元素的地址:array_identifier == &amp;array_identifier[0]cplusplus.com/doc/tutorial/pointers
  • 当然是的,但是您似乎认为 printf("%s", array_name) 将访问第一个元素...我只是想告诉您,因为“%s” printf 将打印指针变量的所有内容,直到在该指针变量中找到'\0'....
  • 它不是使用第一个元素的地址来做到这一点吗?我的意思是我听说过的每个函数都使用这个第一个地址来确定其余的,因为下一个元素是从这一点开始连续的。如果没有找到 nul 字节,任何使用这个系统的函数都容易继续计算元素,这就是缓冲区溢出的原因。这就是为什么gets很危险并且可以创建B.O.
【解决方案4】:

根据我的经验,当您尝试将 %s 指令与 *p 一起使用时,您应该会遇到分段错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-26
    • 2020-10-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多