这里有一个详细的解释,希望能对您有所帮助。让我们从您的程序开始,因为它是最容易解释的。
int main()
{
char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
第一条语句:
char* p = "Hello";
声明p
为指向char
的指针。当我们说“指向char
的指针”时,这是什么意思?这意味着p
的值是char
的地址;p
告诉我们在内存中有一些空间来存储char
。
该语句还将p
初始化为指向字符串字面量"Hello"
中的第一个字符。为了这个练习的目的,重要的是要理解p
指向的只是整个字符串中的第一个字符'H'
,而不是整个字符串。毕竟,p
是指向一个char
,而不是整个字符串。p
的值是"Hello"
中'H'
的地址。
然后您设置了一个循环:
while (*p++)
循环条件*p++
是什么意思?这里有三个因素使它变得复杂(至少在熟悉之前):
- 后缀
++
和间接*
运算符的优先级
- 后缀递增表达式的值
- 后缀递增表达式的副作用
1. 优先级。快速查看运算符优先级表将告诉您,后缀递增具有比解除引用/间接更高的优先级。这意味着复杂表达式*p++
将被分组为:*(p++)
。也就是说,*
部分将应用于p++
部分的值。所以我们先来看p++
部分。
2. 后缀表达式的值。 p++
的值是递增之前的p
的值。如果您有:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
输出将为:
7
8
因为i++
在增量之前会先计算出i
的值。同样地,p++
也会计算出当前p
的值。我们知道,p
的当前值是'H'
的地址。
所以现在*p++
中的p++
部分已经被计算出来了;它是p
的当前值。然后进行*
部分。*(p的当前值)
的意思是:访问p
所持有的地址上的值。我们知道该地址上的值是'H'
。因此,表达式*p++
的结果是'H'
。
现在等一下,你说什么?如果*p++
的结果是'H'
,那么为什么上面的代码没有打印出那个'H'
呢?这就是副作用的作用。
3. 后缀表达式的副作用。后缀++
具有当前操作数的值,但它具有增加该操作数的副作用。什么?再看一下那个int
代码:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
如前所述,输出将为:
7
8
当在第一个printf()
中评估i++
时,它的值为7。但是C标准保证,在第二个printf()
开始执行之前的某个时间点,++
运算符的副作用将已经发生。也就是说,在第二个printf()
发生之前,i
将已经因第一个printf()
中的++
运算符而被增加。顺便说一下,这是标准关于副作用时间的少数保证之一。
因此,在您的代码中,当评估表达式*p++
时,它的值为'H'
。但是当您到达以下内容时:
printf ("%c", *p)
发生了讨厌的副作用。 p
已经被增加了。哇!它不再指向 'H'
,而是指向 'H'
后面的一个字符:也就是 'e'
。这解释了你的口音输出:
ello
因此,在其他答案中出现了一系列有用(且准确)的建议合唱:要打印标准英语发音的
"Hello"
而不是其伦敦俚语版本,您需要类似以下的代码:
while (*p)
printf ("%c", *p++);
说了这么多,那其他的呢?你问这些的意思:
*ptr++
*++ptr
++*ptr
我们刚刚讨论了第一个,现在来看看第二个:*++ptr
。
我们在之前的解释中看到后缀递增p++
有一定的优先级、值和副作用。前缀递增++p
与其后缀对应物具有相同的副作用:它将其操作数增加1。然而,它具有不同的优先级和不同的值。
前缀递增的优先级低于后缀递增;它的优先级为15。换句话说,它具有与解引用/间接运算符*
相同的优先级。在像下面这样的表达式中:
*++ptr
重要的不是优先级:这两个运算符在优先级上是相同的。所以关联性发挥作用。前缀递增和间接操作符具有右-左关联性。由于这种关联性,操作数ptr
将与最右边的运算符++
分组,然后才是更靠左的运算符*
。换句话说,表达式将被分组为*(++ptr)
。因此,就像*ptr++
一样,但出于不同的原因,这里也会将*
部分应用于++ptr
部分的值。
那么这个值是什么呢?前缀递增表达式的值是操作数在递增之后的值。这使它与后缀递增运算符非常不同。假设你有:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
输出结果将是:
8
8
...跟后缀操作符看到的不一样。同样地,如果您有:
char* p = "Hello";
printf ("%c ", *p); // note space in format string
printf ("%c ", *++p); // value of ++p is p after the increment
printf ("%c ", *p++); // value of p++ is p before the increment
printf ("%c ", *p); // value of p has been incremented as a side effect of p++
输出结果将是:
H e e l // good dog
你明白为什么吗?
现在我们来看你问过的第三个表达式,++*ptr
。实际上,这是最棘手的一个。两个运算符具有相同的优先级和右-左结合性。这意味着表达式将被分组为 ++(*ptr)
。 ++
部分将应用于 *ptr
部分的值。
所以如果我们有:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
出乎意料的自大输出将会是:
I
什么?好的,所以
*p
部分将被评估为
'H'
。然后
++
开始发挥作用,此时它将应用于
'H'
,而不是指针!当您将1添加到
'H'
时会发生什么?您将得到1加上
'H'
的ASCII值,即72;您将得到73。将其表示为
char
,您将获得ASCII值为73的
char
:
'I'
。
这解决了您在问题中提到的三个表达式。这里还有另一个表达式,在您的问题的第一条评论中提到:
(*ptr)++
这个也很有趣。如果你有:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
它将会给你这样一个充满热情的输出:
HI
发生了什么?再次,这是关于优先级,表达式值和副作用的问题。由于括号,*p
部分被视为主要表达式。主要表达式胜过其他任何内容;它们首先得到计算。而且,如您所知,*p
求值为'H'
。表达式的其余部分,++
部分应用于该值。因此,在这种情况下,(*p)++
变为'H'++
。
'H'++
的值是多少?如果您说'I'
,那么您已经忘记了(已经!)我们对后缀递增与值vs.副作用的讨论。请记住,'H'++
求值为'H'
的当前值。因此,第一个printf()
将打印'H'
。然后,作为副作用,该'H'
将递增为'I'
。第二个printf()
打印出那个'I'
。因此,您有了欢快的问候。
好的,但在这最后两种情况下,我为什么需要呢?
char q[] = "Hello";
char* p = q;
为什么我不能只有像这样的东西
char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
因为"Hello"
是一个字符串字面量。如果你尝试++*p
,你就试图将字符串中的'H'
改变成'I'
,使整个字符串变成"Iello"
。在C语言中,字符串字面量是只读的;试图修改它们会引发未定义的行为。"Iello"
在英语中也是未定义的,但这只是巧合。
相反,你也不能有
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
为什么不行呢?因为在这个实例中,p
是一个数组。数组不是可修改的左值;你不能通过前缀或后缀递增或递减来改变p
指向的位置,因为数组的名称就像一个常量指针一样工作。(这并不是它实际上的含义;这只是一种方便的看法。)
总之,这里是你所问的三件事:
*ptr++ // effectively dereferences the pointer, then increments the pointer
*++ptr // effectively increments the pointer, then dereferences the pointer
++*ptr // effectively dereferences the pointer, then increments dereferenced value
这里还有第四个,和前三个一样有趣:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
如果ptr
实际上是一个数组标识符,第一和第二个将会崩溃。如果ptr
指向字符串字面值,第三和第四个将会崩溃。
以上就是全部内容。希望现在一切都清晰了。感谢您的聆听,我将在这里待上整整一周。
(*ptr)++
(需要加括号以消除与*ptr++
的歧义)。 - user4815162342char* p
,指向一个由唯一字符组成的、以结束符结尾的字符串。然后定义一个函数fn(char ch)
,该函数打印出ch
参数和当前指向的字符。现在调用fn(*p++)
。问:fn
函数会打印出相同的字符两次吗?你会惊讶地发现,有多少教授会回答错误。 - WhozCraigconst char* p = "Hello";
。 - hetepeperfan