在解除引用的指针后进行增量?
试着理解C语言中指针的行为,我对下面的例子代码感到有些惊讶:
#include <stdio.h> void add_one_v1(int *our_var_ptr) { *our_var_ptr = *our_var_ptr +1; } void add_one_v2(int *our_var_ptr) { *our_var_ptr++; } int main() { int testvar; testvar = 63; add_one_v1(&(testvar)); /* Try first version of the function */ printf("%d\n", testvar); /* Prints out 64 */ printf("@ %p\n\n", &(testvar)); testvar = 63; add_one_v2(&(testvar)); /* Try first version of the function */ printf("%d\n", testvar); /* Prints 63 ? */ printf("@ %p\n", &(testvar)); /* Address remains identical */ }
输出:
64 @ 0xbf84c6b0 63 @ 0xbf84c6b0
第二个函数( add_one_v2
)中的*our_var_ptr++
语句究竟做了什么,因为它显然与*our_var_ptr = *our_var_ptr +1
不一样?
由于运算符优先规则和++
是后缀运算符的事实, add_one_v2()
引用指针,但实际上++
正在应用于指针本身 。 但是,请记住,C始终使用按值传递: add_one_v2()
正在递增其指针的本地副本 ,这对存储在该地址的值没有任何影响。
作为一个testing,用这些代码replaceadd_one_v2()
,看看输出是如何受到影响的:
void add_one_v2(int *our_var_ptr) { (*our_var_ptr)++; // Now stores 64 } void add_one_v2(int *our_var_ptr) { *(our_var_ptr++); // Increments the pointer, but this is a local // copy of the pointer, so it doesn't do anything. }
这是使C和C ++非常有趣的那些小问题之一。 如果你想弯曲你的大脑,找出这个:
while (*dst++ = *src++) ;
这是一个string副本。 指针不断增加,直到一个值为零的字符被复制。 一旦你知道为什么这个技巧会起作用,你将永远不会忘记++是如何在指针上工作的。
PS您始终可以用圆括号覆盖操作员的顺序。 以下将增加指向的值,而不是指针本身:
(*our_var_ptr)++;
好,
*our_var_ptr++;
它是这样工作的:
- 取消引用首先发生,给你
our_var_ptr
指示的内存位置(包含63)。 - 然后评估expression式,63的结果仍然是63。
- 结果被扔掉(你没有做任何事情)。
- 然后
our_var_ptr
在评估之后递增。 它正在改变指针指向的位置,而不是它指向的位置。
实际上这样做是一样的:
*our_var_ptr; our_var_ptr = our_var_ptr + 1;
合理? Mark Ransom的答案就是一个很好的例子,除了他实际使用结果。
这里有很多的困惑,所以这里是一个修改过的testing程序,以使发生的事情变得清晰(或至less清楚)。
#include <stdio.h> void add_one_v1(int *p){ printf("v1: pre: p = %p\n",p); printf("v1: pre: *p = %d\n",*p); *p = *p + 1; printf("v1: post: p = %p\n",p); printf("v1: post: *p = %d\n",*p); } void add_one_v2(int *p) { printf("v2: pre: p = %p\n",p); printf("v2: pre: *p = %d\n",*p); int q = *p++; printf("v2: post: p = %p\n",p); printf("v2: post: *p = %d\n",*p); printf("v2: post: q = %d\n",q); } int main() { int ary[2] = {63, -63}; int *ptr = ary; add_one_v1(ptr); printf("@ %p\n", ptr); printf("%d\n", *(ptr)); printf("%d\n\n", *(ptr+1)); add_one_v2(ptr); printf("@ %p\n", ptr); printf("%d\n", *ptr); printf("%d\n", *(ptr+1)); }
与结果输出:
v1: pre: p = 0xbfffecb4 v1: pre: *p = 63 v1: post: p = 0xbfffecb4 v1: post: *p = 64 @ 0xbfffecb4 64 -63 v2: pre: p = 0xbfffecb4 v2: pre: *p = 64 v2: post: p = 0xbfffecb8 v2: post: *p = -63 v2: post: q = 64 @ 0xbfffecb4 64 -63
四件事要注意:
- 指针的本地副本的更改不会反映在调用指针中。
- 改变本地指针的目标确实会影响调用指针的目标(至less在目标指针更新之前)
-
add_one_v2
指向的值不会递增,也不是下一个值,但是指针是 -
add_one_v2
指针的增量在解引用之后发生
为什么?
- 由于
++
绑定比*
更为紧密,因此add_one_v2
的增量应用于指针,而不是指向的指针。 - 后期增量发生在术语评估之后 ,所以解引用获得数组中的第一个值(元素0)。
正如其他人指出的那样,运算符优先级导致v2函数中的expression式被视为*(our_var_ptr++)
。
但是,由于这是一个后增加运算符,所以说增加指针然后解除引用是不正确的。 如果这是真的,我不认为你会得到63作为你的输出,因为它会返回下一个内存位置的值。 其实我相信操作的顺序是:
- 保存指针的当前值
- 增加指针
- 取消引用步骤1中保存的指针值
正如所解释的,你没有看到指针的值的变化,因为它被值传递给函数。
our_var_ptr是一个指向内存的指针。 即它是存储数据的存储单元。 (在这种情况下,int的二进制格式是4个字节)。
* our_var_ptr是取消引用的指针 – 它指向指向“指向”的位置。
++增加一个值。
所以。 *our_var_ptr = *our_var_ptr+1
取消引用指针并在该位置添加一个值。
现在,添加运算符优先级 – 将其读作(*our_var_ptr) = (*our_var_ptr)+1
,您会发现取消引用首先发生,因此您需要取值并将其提高。
在你的另一个例子中,++运算符的优先级比*低,所以它接受你传入的指针,向它添加一个(所以现在指向垃圾),然后返回。 (记住值始终是通过C中的值传递的,所以当函数返回原始的testvar指针保持不变时,只会改变函数中的指针)。
我的build议,当使用解引用(或其他)使用括号来明确你的决定。 不要试图记住优先规则,因为你最终只会使用另一种语言,而这些语言稍有不同,你会感到困惑。 或者老,最后忘记有更高的优先级(就像我用*和 – >做的)。
'++'运算符的优先级高于'*'运算符,这意味着指针地址在被解除引用之前将被递增。
然而,“+”运算符的优先级低于“*”。
我将尝试从一个不同的angular度回答这个问题…第1步让我们看看运算符和操作数:在这种情况下,它是操作数,并且你有两个运算符,在这种情况下*用于解引用和++增量。 步骤2具有较高优先级++的优先级高于*步骤3其中是++,它是在右边,表示POST递增在这种情况下,编译器在完成后执行“心理注释”与所有其他操作符一样…注意,如果它是* ++ p,那么它将在之前执行它,因此在这种情况下,它相当于取两个处理器的寄存器,一个将保存解除引用的值* p另一个将保持递增的p ++的值,在这种情况下有两个是POST活动的原因…在这种情况下,这是棘手的,看起来像是一个矛盾。 我们可以期望++优先于*,它只是表示它将在所有其他操作数之后应用,在下一个'之前'被应用。 令牌…
从K&R,第105页:“* t ++的值是在t增加之前指向的字符”。
如果不使用括号来指定操作顺序,则增量优先于取消引用。 但请记住,后缀和前缀增量是两个不同的操作。
在++ x中,运算符会引用您的variables并添加一个。 它的行为有点像这样:
void operator++(double &x) //prefix { x = x + 1; }
在x ++中,运算符递增您的variables并返回其旧值。 喜欢这个:
double operator++(double &x) //postfix { double temp = x; ++x; return temp; }
这里有一个副本,使其效率较低(这就是为什么你应该把++ i放在++循环中的原因)。
但请注意,增量总是先处理。 如果你写:
char *x = {'a', 'c'}; char y = *x++; char z = *x;
指针x将在解引用之前递增,但解除引用将发生在旧的x值(由后缀增量返回)上。 在这种情况下,y将用'a'和z用'c'初始化。
如果使用如下括号,则没有什么不同:
char *x = {'a', 'c'}; char y = *(x++);
但是,如果你这样做:
char *x = {'a', 'c'}; char y = (*x)++;
这里,x将被解引用,并且它所指向的值('a')将被递增(到'b')。 但后缀增量返回旧值。 因此,即使* x现在具有值“b”,y也将被初始化为“a”。
在strcpy函数(在其他答案中提到),增量也是先完成的:
char *strcpy(char *dst, char *src) { char *aux = dst; while(*dst++ = *src++); return aux; }
在每次迭代中,首先处理dst ++,作为后缀增量,返回dst的旧值。 然后,dst(这是一个指针)的旧值被解引用来接收一些右值。 src然后递增,并且旧的值被解除引用到dst。 这就是为什么dst [0] = src [0],dst [1] = src [1],直到* dst等于0并中止循环。
由于指针是通过值传递的,只有本地副本才会增加。 如果你真的想增加指针,你必须像这样通过引用传递它:
void inc_value_and_ptr(int **ptr) { (**ptr)++; (*ptr)++; }