指针表达式:*ptr++、*++ptr 和 ++*ptr
Posted
技术标签:
【中文标题】指针表达式:*ptr++、*++ptr 和 ++*ptr【英文标题】:Pointer expressions: *ptr++, *++ptr and ++*ptr 【发布时间】:2013-08-31 03:54:42 【问题描述】:最近遇到这个问题,我自己也搞不懂。
这三个表达式REALLY是什么意思?
*ptr++
*++ptr
++*ptr
我试过里奇。但遗憾的是无法按照他所说的这 3 个操作。
我知道它们都是为了增加指针/指向的值而执行的。我也可以猜测可能有很多关于优先级和评估顺序的事情。就像一个先增加指针然后获取该指针的内容,一个简单地获取内容然后增加指针等等等等。如您所见,我对他们的 actual 没有清楚的了解操作,我想尽快清除。但是当我有机会将它们应用到程序中时,我真的迷失了。例如:
int main()
char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
给我这个输出:
ello
但我的期望是它会打印 Hello
。
最后一个请求——请举例说明每个表达式在给定代码 sn-p 中的工作方式。大多数时候,我脑海中只有一段理论。
【问题讨论】:
你错过了第四个:(*ptr)++
(括号需要消除*ptr++
的歧义)
因为您在打印之前增加了指针。你想要 while(*p) 和 printf("%c", *p++);
面试的好问题。实际用途有限。我希望 C 没有那些指针 :)
@Himanshu 如果这能烤到你的受访者的面,试试这个:有一个 global 指针,char* p
,指向一个有效终止的唯一字符字符串。然后有一个函数fn(char ch)
打印both ch
参数和 p
指向的当前字符。现在调用fn(*p++);
问:fn
是否打印相同的字符 twice ?你会惊讶于有多少教授把这个问题弄错了。
因为 p 指向一个字符串文字,你应该写 const char* p = "Hello";
【参考方案1】:
这是一个详细的解释,希望对您有所帮助。让我们从您的程序开始,因为它是最容易解释的。
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.优先级。快速浏览运算符的优先级表会告诉您后缀增量的优先级 (16) 高于取消引用/间接 (15)。这意味着复杂表达式*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
的当前值。然后*
部分发生。 *(current value of p)
表示:访问p
持有的地址处的值。我们知道那个地址的值是'H'
。所以表达式*p++
的计算结果为'H'
。
现在等一下,你是说。如果*p++
的计算结果为'H'
,为什么上面的代码中没有'H'
打印?这就是副作用发挥作用的地方。
3.后缀表达式副作用。后缀++
具有当前操作数的值,但它具有增加该操作数的副作用。嗯?再看看int
代码:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
如前所述,输出将是:
7
8
当i++
在第一个printf()
中求值时,它的求值为7。但C 标准保证在第二个printf()
开始执行之前的某个时间点,副作用 ++
运算符将发生。也就是说,在第二个printf()
发生之前,i
将由于第一个printf()
中的++
运算符而增加。顺便说一句,这是该标准对副作用时间的少数保证之一。
然后,在您的代码中,当计算表达式 *p++
时,它的计算结果为 'H'
。但是当你得到这个时:
printf ("%c", *p)
出现了讨厌的副作用。 p
已增加。哇!它不再指向'H'
,而是指向'H'
之后的一个字符:即'e'
。这解释了你的 cockneyfied 输出:
ello
因此,其他答案中的有用(和准确)建议的合唱:要打印收到的发音"Hello"
而不是它的伦敦对应物,您需要类似
while (*p)
printf ("%c", *p++);
就这么多。其余的呢?你问这些的含义:
*ptr++
*++ptr
++*ptr
我们刚刚讲了第一个,让我们看看第二个:*++ptr
。
我们在前面的解释中看到后缀增量p++
具有一定的优先级、值和副作用。前缀增量++p
与它的后缀对应物具有相同的副作用:它将其操作数增加1。但是,它具有不同的优先级和不同的价值。
前缀增量的优先级低于后缀;它的优先级为 15。换句话说,它与取消引用/间接操作符*
具有相同的优先级。像
*++ptr
重要的不是优先级:两个运算符的优先级相同。所以 associativity 开始了。前缀增量和间接运算符具有左右关联性。由于这种关联性,操作数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'
,那么您已经(已经!)忘记了我们关于后缀增量的价值与副作用的讨论。请记住,'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
指向字符串文字,第三个和第四个将崩溃。
你有它。我希望现在一切都是水晶。你是一个很棒的观众,我整个星期都会在这里。
【讨论】:
在来这个论坛之前,我已经搜索了3本我拥有的“C”类书籍。我还尝试了一些著名的在线教程。但是它们都没有接近您的解释(尤其是您将它们放在一起的方式)。您不仅回答了我提出的问题,而且还从基层层面讨论了很多事情。其实今天你教会了我很多基本的东西,这是我以前缺乏的。我忍不住切换了我接受的答案。 :) 再次感谢。 +1 我认为这是我在 SO 上读到的最佳长答案。我想每个人都可以从这个答案中学到很多东西。 @verbose 欢迎您!我建议避免将重要文本用作评论和水平滚动条。 你先生,应该写一本关于 C 的书。 @verbose 你先生,辜负了你的名字.. :)【参考方案2】:假设ptr
指向数组arr
的第i个元素。
*ptr++
计算结果为 arr[i]
并将 ptr
设置为指向 arr
的第 (i+1) 个元素。相当于*(ptr++)
。
*++ptr
将ptr
设置为指向arr
的第(i+1) 个元素并计算为arr[i+1]
。相当于*(++ptr)
。
++*ptr
将arr[i]
增加一并计算其增加的值;指针ptr
保持不变。相当于++(*ptr)
。
还有一个,但你需要括号来写它:
(*ptr)++
将arr[i]
增加一并在增加之前评估其值;指针ptr
再次保持不变。
其余的你自己想办法; @Jaguar 也回答了这个问题。
【讨论】:
【参考方案3】:*ptr++ : post increment a pointer ptr
*++ptr : Pre Increment a pointer ptr
++*ptr : preincrement the value at ptr location
阅读here 了解前置增量和后置增量运算符
这将给出Hello
作为输出
int main()
const char *p = "Hello";
while(*p)
printf("%c",*p++);//Increment the pointer here
return 0;
【讨论】:
@Nik-Lz 是的,输出将是Hello
【参考方案4】:
循环中的条件不好:
while(*p++)
printf("%c",*p);
和
一样while(*p)
p++;
printf("%c",*p);
这是错误的,应该是:
while(*p)
printf("%c",*p);
p++;
*ptr++
与*(ptr++)
相同,即:
const char *ptr = "example";
char value;
value = *ptr;
++ptr;
printf("%c", value); // will print 'e'
*++ptr
与*(++ptr)
相同,即:
const char *ptr = "example";
char value;
++ptr;
value = *ptr;
printf("%c", value); // will print 'x'
++*ptr
与++(*ptr)
相同,即:
const char *ptr = "example";
char value;
value = *ptr;
++value;
printf("%c", value); // will print 'f' ('e' + 1)
【讨论】:
我绝对同意答案的第一部分。在第二部分中,使用整数初始化指针(指向整数!)对于那些难以理解指针用法的人来说是令人困惑的。【参考方案5】:你说得对,注意*
优先于前缀增量,但不超过后缀增量。以下是这些细分的方式:
*ptr++
- 从左到右,取消引用指针,然后增加指针值(不是它指向的值,因为后缀优先于取消引用)
*++ptr
- 增加指针然后取消引用它,这是因为前缀和取消引用具有相同的优先级,因此它们按从右到左的顺序计算
++*ptr
- 在优先级方面与上述类似,再次从右到左依次取消引用指针,然后递增指针指向的内容。请注意,在您的情况下,这将导致未定义的行为,因为您正在尝试修改只读变量 (char* p = "Hello";
)。
【讨论】:
【参考方案6】:我将添加我的观点,因为虽然其他答案是正确的,但我认为他们遗漏了一些东西。
v = *ptr++
意思
temp = ptr;
ptr = ptr + 1
v = *temp;
在哪里
v = *++ptr
意思
ptr = ptr + 1
v = *ptr
了解后增量(和后减量)的含义很重要
temp = ptr // Temp created here!!!
ptr = ptr + 1 // or - 1 if decrement)
v = *temp // Temp destroyed here!!!
为什么重要?好吧,在 C 中,这并不是那么重要。尽管在 C++ 中 ptr
可能是一个复杂的类型,比如迭代器。例如
for (std::set<int>::iterator it = someSet.begin(); it != someSet.end(); it++)
在这种情况下,因为it
是一个复杂类型it++
可能会因为temp
创建而产生副作用。当然,如果你很幸运,编译器会尝试丢弃不需要的代码,但如果迭代器的构造函数或析构函数做了任何事情,那么 it++
将在创建 temp
时显示这些效果。
我想说的是写下你的意思。如果你的意思是increment ptr,那么写++ptr
而不是ptr++
。如果你的意思是temp = ptr, ptr += 1, temp
,那么写ptr++
【讨论】:
【参考方案7】:*ptr++ // 1
这与:
tmp = *ptr;
ptr++;
所以ptr
指向的对象的值被检索出来,然后ptr
递增。
*++ptr // 2
这与:
++ptr;
tmp = *ptr;
所以指针ptr
递增,然后读取ptr
指向的对象。
++*ptr // 3
这与:
++(*ptr);
所以ptr
指向的对象自增; ptr
本身没有改变。
【讨论】:
【参考方案8】:指针表达式:*ptr++、*++ptr 和 ++*ptr:
注意:指针必须初始化并且必须有有效的地址。因为在 RAM 中,除了我们的程序(a.out)之外,还有更多程序同时运行,即如果您尝试访问一些未为您保留的内存,操作系统将通过分段错误。
在解释之前让我们先看一个简单的例子?
#include<stdio.h>
int main()
int num = 300;
int *ptr;//uninitialized pointer.. must be initialized
ptr = #
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
*ptr = *ptr + 1;//*ptr means value/data on the address.. so here value gets incremented
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
/** observe here that "num" got changed but manually we didn't change, it got modified by pointer **/
ptr = ptr + 1;//ptr means address.. so here address got incremented
/** char pointer gets incremented by 1 bytes
Integer pointer gets incremented by 4 bytes
**/
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
分析上面代码的输出,希望你得到上面代码的输出。从上面的代码中可以清楚地看出,指针名称 (ptr) 意味着我们正在谈论 address 而 *ptr 意味着我们正在谈论 abbout 价值/数据。
案例 1:*ptr++、*++ptr、*(ptr++) 和 *(++ptr):
上面提到的所有 4 种语法都是相似的,在所有 address gets incremented
中,但是地址如何增加是不同的。
注意:求解任意一个表达式,求表达式中有多少个运算符,然后求出运算符的优先级。我有多个具有相同优先级的运算符,然后检查可能从右(R)到左(L)或从左到右的进化顺序或关联性。
*ptr++ :这里有两个运算符,即取消引用(*)和++(增量)。两者都具有相同的优先级,然后检查 R 到 L 的关联性。所以从右到左开始求解,无论运算符先到。
*ptr++ : 第一个 ++ 是在从 R 到 L 求解时出现的,所以地址会递增,但它的后递增。
*++ptr :与第一个相同,地址也增加,但它的预增加。
*(ptr++) : 这里有 3 个操作符,其中 grouping () 具有最高优先级,所以首先 ptr++ 解决了,即地址增加但发布。
*(++ptr) :与上面的情况相同,这里的地址也增加但预增加。
案例 2 : ++*ptr, ++(*ptr), (*ptr)++ :
上面提到的所有 4 种语法都是相似的,在 所有值/数据都会递增,但值如何改变是不同的。
++*ptr :第一个 * 在从 R 到 L 求解时出现,因此值发生了变化,但它的预增量。
++(*ptr) : 同上例,修改值。
(*ptr)++ : 这里有3个操作符,其中grouping () 优先级最高,Inside () *ptr 在那里,所以首先解决*ptr 即值递增但是发帖。
注意 : ++*ptr 和 *ptr = *ptr + 1 都是相同的,在这两种情况下值都会改变。 ++*ptr : 仅使用 1 条指令 (INC),直接单次更改值。 *ptr = *ptr + 1 :这里第一个值递增(INC),然后赋值(MOV)。
要理解上述所有不同的指针增量语法,让我们考虑简单的代码:
#include<stdio.h>
int main()
int num = 300;
int *ptr;
ptr = #
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
*ptr++;//address changed(post increment), value remains un-changed
// *++ptr;//address changed(post increment), value remains un-changed
// *(ptr)++;//address changed(post increment), value remains un-changed
// *(++ptr);//address changed(post increment), value remains un-changed
// ++*ptr;//value changed(pre increment), address remains un-changed
// (*ptr)++;//value changed(pre increment), address remains un-changed
// ++(*ptr);//value changed(post increment), address remains un-changed
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
在上面的代码中,尝试注释/取消注释 cmets 并分析输出。
Pointers as Constant : 没有任何方法可以将指针设为常量,我在此仅提及。
1)const int *p OR int const *p :这里value
是常量,地址不是常量 即 p 指向哪里?某个地址?在那个地址上的价值是什么?一些价值吧?该值是恒定的,您不能修改该值,但指针指向哪里?一些地址吧?它也可以指向其他地址。
要理解这一点,让我们考虑下面的代码:
#include<stdio.h>
int main()
int num = 300;
const int *ptr;//constant value, address is modifible
ptr = #
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
*ptr++;//
// *++ptr;//possible bcz you are trying to change address which is possible
// *(ptr)++;//possible
// *(++ptr);//possible
// ++*ptr;//not possible bcz you trying to change value which is not allowed
// (*ptr)++;//not possible
// ++(*ptr);//not possible
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
尝试分析上述代码的输出
2)int const *p:它被称为'**constant pointe**r
',即address is constant but value is not constant
。此处不允许更改地址,但可以修改值。
注意:常量指针(上例)必须在声明自身时初始化。
为了理解这一点,让我们检查一下简单的代码。
#include<stdio.h>
int main()
int x = 300;
int* const p;
p = &x;
printf("x = %d p =%p and *p = %d\n",num,p,*p);
在上面的代码中,如果你观察到没有 ++*p 或 *p++ 所以你可能认为这是简单的情况,因为我们没有改变地址或值,但它会产生错误。为什么 ?我在 cmets 中提到的原因。
#include<stdio.h>
int main()
int x = 300;
/** constant pointer must initialize while decaring itself **/
int* const p;//constant pointer i.e its pointing to some address(here its pointing to garbage), it should point to same address(i.e garbage ad
dress only
p = &x;// but here what we are doing ? we are changing address. we are making p to point to address of x instead of garbage address.
printf("x = %d p =%p and *p = %d\n",num,p,*p);
那么这个问题的解决方案是什么?
int* const p = &x;
有关此案例的更多信息,请考虑以下示例。
#include<stdio.h>
int main()
int num = 300;
int *const ptr = #//constant value, address is modifible
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
*ptr++;//not possible
// *++ptr;//not possible bcz you are trying to change address which is not possible
// *(ptr)++;//not possible
// *(++ptr);//not possible
// ++*ptr;// possible bcz you trying to change value which is allowed
// (*ptr)++;// possible
// ++(*ptr);// possible
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
3)const int* const p:这里地址和值都是常数。
要了解这一点,请查看以下代码
#include<stdio.h>
int main()
int num = 300;
const int* const ptr = #//constant value,constant address
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
*ptr++;//not possible
++*ptr;//not possible
printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
【讨论】:
【参考方案9】: 后缀++
的优先级高于一元 *
。
前缀 ++ 和一元 *
具有相同的优先级,两者的表达式具有从右到左的运算符关联性,这意味着右侧的操作数在左侧之前绑定到操作数(s)。
因此:
*ptr++
将指针递增 1 项,然后取消引用它在递增之前的内存位置。
*++ptr
将指针增加 1 项,然后取消引用它现在指向的内存位置。
++*ptr
取消引用内存位置,然后将 contents(值)增加 1。
【讨论】:
【参考方案10】:后缀和前缀的优先级高于取消引用所以
*ptr++ 在这里发布增量 ptr 然后指向新的 ptr 值
*++ptr here Pre Increment 拳头然后指向新的ptr值
++*ptr这里先获取ptr指向的值,然后递增那个值
【讨论】:
这是不正确的。后缀具有更高的优先级,但前缀与取消引用具有相同的优先级。【参考方案11】:const char *p = "Hello";
*p means "Hello"
^
|
p
*p++ means "Hello"
^
|
p
*++p means "Hello"
^
| (WHILE THE STATEMENT IS EXECUTED)
p
*++p means "Hello"
^
| (AFTER THE STATEMENT IS EXECUTED)
p
++*p
表示您正在尝试增加 *p
的 ASCII 值
is "Hello"
^
|
p
你不能增加这个值,因为它是一个常数,所以你会得到一个错误
对于您的 while 循环,循环将一直运行,直到 *p++
到达字符串的末尾,那里有一个 '\0'
(NULL) 字符。
现在,由于 *p++
跳过了第一个字符,因此您只能从第二个字符开始获得输出。
下面的代码不会输出任何东西,因为while循环有'\0'
const char *p = "Hello";
while('\0')
printf("%c",*p);
以下代码将为您提供与下一个代码相同的输出,即 ello 。
const char *p = "Hello";
while(*++p)
printf("%c",*p);
.......................
const char *p = "Hello";
while(*p++)
printf("%c",*p);
【讨论】:
以上是关于指针表达式:*ptr++、*++ptr 和 ++*ptr的主要内容,如果未能解决你的问题,请参考以下文章