难以理解 CS50 中的 char* 和字符串

Trouble understanding char* and string in CS50

提问人:King Brain 提问时间:7/12/2023 最后编辑:Vlad from MoscowKing Brain 更新时间:7/16/2023 访问量:123

问:

所以我知道字符串只是连续存储在计算机内存中的字符数组。

我也知道,为了找出字符串的位置,你只需要转到第一个字符的位置,因为它是连续的,当程序或函数遇到 \0 字符时,字符串结束。

但我不明白的是:

  1. char* s = "HI!";
    

它是否创建一个包含 4 个字符的数组?或者它只是指向起始角色位置的指针?还是两者兼而有之?

2.

    char* name = "BEN";
    printf("%c %c\n", *(name + 1), *name + 1);

为什么它们都给出两个不同的输出(E 和 C),而不是都给出 E?

c cs50 隐式转换 字符串文本指 针算术

评论

1赞 chux - Reinstate Monica 7/12/2023
“它是否创建了一个包含 4 个字符的数组?” --> 编译器“It”与其中的字符串形成一个。 获取第一个元素的地址。需要更多吗?char[4]"HI!"s
3赞 UnholySheep 7/12/2023
数字 2 是运算符优先级的经典案例
1赞 HolyBlackCat 7/12/2023
"HI!"它本身就是一个由 4 个字符组成的数组。 然后指向它的第一个元素。char* s
5赞 Jonathan Leffler 7/12/2023
请注意,标题包括 — 一个有争议的决定。不过,你的问题并没有真正问到这一点。对“字符串”的引用似乎是针对 C 标准 §7.1.1 术语定义 ¶1 中定义的通用概念,而不是标头中的 typedef。cs50.htypedef char *string;cs50.h
3赞 Weather Vane 7/12/2023
@JonathanLeffler 我怀疑 OP 现在从他们之前的问题中了解到这是 cs50 的发明(所谓的方便)。致 King Brain:我没有做 cs50,但起初我确实发现它对指针的更复杂的语法很有帮助。后来我明白了为什么它没有帮助,请参阅typedef指针是个好主意吗?stringtypedef

答:

1赞 Ted Lyngmo 7/12/2023 #1
  1. in then 是字符串文字。 指向 .字符串文字长度为 4(包括终止)。另一种(非惯用的)看待它的方式:......这里是指向 .char* s = "HI!";"HI!"sHchar\0
    char(*s)[4] = &"HI!";
    
    schar[4]
  2. 在您的第二个示例中,它是关于您执行操作的顺序,即运算符优先级。
    char* name = "BEN";
    printf("%c %c\n", *(name + 1), *name + 1);
    
    • (name + 1)添加到 中,因此您会得到一个指向 .之后,用 和 get 取消引用指针。1char*nameE*E
    • *name取消引用指针,指针指向它,然后指向它,使其成为 .B+ 1C

这与这样做相同:

printf("%c %c\n", name[1], name[0] + 1);
3赞 Vlad from Moscow 7/12/2023 #2

在本声明中

char* s = "HI!";

将创建两个实体。

第一个是字符串文字,它具有静态存储持续时间和数组类型。(在C++中,它具有常量字符数组类型,与C相反。"HI!"char[4]const char[4]

您可以使用 printf 进行检查

printf( "sizeof( \"HI!\" ) = %zu\n", sizeof( "HI!" ) );

这里字符数组用作指针的初始值设定项。在这种情况下,它被隐式转换为指针的第一个元素,并且指针指向数组的第一个元素的地址。ss

至于这个代码片段

char* name = "BEN";
printf("%c %c\n", *(name + 1), *name + 1);

由于指针算术,表达式具有类型并指向字符串文字的第二个字符 (thus )。取消引用指针表达式,就像获取表达式指向的字符串文本的符号一样。实际上,表达式与 .:) 相同name + 1char *"BEN"'E'*(name + 1)*(name + 1)name[1]1[name]

至于这个表达式,取消引用指针,你会得到字符串文字的第一个符号。然后,将 1 添加到符号 'B''C'( *name + 1 )name[0] + 1' 的内部代码中。*namename'B'( *name + 1 ), so the expression takes the value of the next symbol after , which is . The expression is equivalent to the expression

使用下标运算符 like 和 使表达式更清晰。name[1]name[0] + 1

我认为您知道可以仅使用原始字符串文字重写调用会很有趣。一些例子:printf

printf("%c %c\n", *( "BEN" + 1), *"BEN" + 1);

printf("%c %c\n", "BEN"[1], "BEN"[0] + 1);

甚至

printf("%c %c\n", 1["BEN"], 0["BEN"] + 1);
2赞 selbie 7/12/2023 #3

但我不明白的是:

char* s = "HI!";

它是否创建一个包含 4 个字符的数组?或者它只是指向起始角色位置的指针?还是两者兼而有之?

赋值的右侧提供了一个常量字符数组(4 个,包括 null 字符)。为这 4 个字符分配内存的位置/方式不是问题。编译器可以自由选择生存位置和生存时间。= "HI!";"HI!"

赋值的左侧:在堆栈上声明一个本地指针,以指向数组的第一个字符(char *s = 'H')

char* name = "BEN";

printf("%c %c\n", *(name + 1), *name + 1);

为什么它们都给出两个不同的输出(E 和 C),而不是都给出 E?

  • name是一个指针。它指向该字符串中字母的地址。B
  • name+1是指向该字符串中过去一个字符的指针,在本例中为 .BE
  • *name是在 所指向的地址处保存的值。换句话说,字符 .name'B'
  • *name + 1与 或 或 相同。您在此表达式中将 1 添加到值中,而不是地址。(*name) + 1'B' + 1'C'
  • *(name + 1),计算结果为 引用的字符串中的第二个字符。或者正如你所期望的那样name'E'