首先查看C语言运算符优先级表(如下相关部分),可以看到++和指针的*运算符优先级都是二级,结合方向都是“右到左” 。
2 |
- |
负号运算符 |
-表达式 |
右到左 |
单目运算符 |
~ |
按位取反运算符 |
~表达式 |
|||
++ |
自增运算符 |
++变量名/变量名++ |
|||
-- |
自减运算符 |
--变量名/变量名-- |
|||
* |
取值运算符 |
*指针变量 |
|||
& |
取地址运算符 |
&变量名 |
在C或C++中,i++
和 ++i
都是对变量 i
进行自增操作的表达式,但它们之间有一个关键的区别:它们的副作用(side effect)发生的时机不同,以及它们作为表达式时的返回值也不同。
-
后缀自增
i++
:- 首先,返回变量
i
的当前值。 - 然后,将
i
的值增加1。 - 注意:这个操作的结果(即增加后的值)不会立即用于表达式中,而是会在表达式计算完成后才生效。
- 首先,返回变量
-
前缀自增
++i
:- 首先,将变量
i
的值增加1。 - 然后,返回增加后的新值。
- 这个操作的结果会立即用于表达式中。
- 首先,将变量
一、*p++与*(p++),*++p与*(++p)
1. *p++与*(p++)
在C或C++等编程语言中,*p++ 和 *(p++) 虽然在大多数情况下表现出相似的行为,但它们的操作顺序和侧重点有所不同,尤其是在复杂的表达式或需要明确操作顺序的场景中。
1.1 *p++
尽管这种写法在技术上可能合法,但它实际上并不是一个常见的或推荐的写法,因为它可能引起混淆。我们可以这样理解:
首先,p++ 是一个后缀递增操作,意味着它返回p的当前值,然后将p指向下一个位置(即地址加1,假设p是指向整型或类似大小的类型的指针)。
但是,*p++ 的解释并不直接遵循这个简单的规则,因为C/C++的语法并不允许直接这样写来清晰地表示意图。实际上,编译器会将其解析为 *(p++),即先对p进行解引用操作(获取p指向的值),然后p递增。
然而,直接写*p++可能会让一些读者误解为首先递增p然后解引用(这是不正确的)。因此,虽然编译器可能将其视为*(p++),但最好显式地使用*(p++)来避免混淆。
1.2 *(p++)
这个表达式明确地表示了先对p指向的当前位置进行解引用操作(即获取该位置的值),然后将p递增到下一个位置。
这是一个非常常见的用法,特别是在遍历数组或链表时,需要按顺序访问每个元素但又不想在访问后立即丢失当前元素的地址。
示例
假设我们有一个整型数组int arr[] = {1, 2, 3}; 和一个指向该数组的指针int *p = arr;。
#include <>
int main() {
int arr[] = {1, 2, 3};
int *p = arr; // p 指向数组的第一个元素
// 使用 *(p++) 遍历数组
while (p < arr + 3) { // 确保不要越界
printf("%d ", *(p++)); // 打印当前元素,然后递增 p
}
// 注意:此时 p 已经指向 arr[3] 的位置(即数组末尾的下一个位置)
// 如果再次解引用 p,将会导致未定义行为(因为 p 指向了非法的内存位置)
return 0;
}
在这个例子中,*(p++) 被用作循环体内的一个表达式,它首先解引用 p(即获取 p 当前指向的数组元素的值),然后递增 p 以便在下一次迭代中指向数组的下一个元素。循环继续,直到 p 指向数组末尾的下一个位置(在这个例子中是 arr + 3)。
直接写*p++(尽管不常见且可能引起混淆)在大多数情况下也会以相同的方式工作,但最好避免这种写法。
2.*++p与*(++p)
在C或C++中,*++p 和 *(++p) 实际上是相同的,因为 ++p(前缀递增)和 *(解引用)操作符的优先级和结合性决定了它们的执行顺序。
++p 是一个前缀递增操作符,它首先将指针 p 指向的地址增加(通常是增加其指向类型的大小,比如对于 int* 类型的指针,就是增加 sizeof(int)),然后返回递增后的指针的值。
* 是一个解引用操作符,它用于获取指针指向位置的值。
由于 ++ 操作符的优先级高于 * 操作符,并且它们都是右结合的(但在这个上下文中,右结合性并不直接影响,因为只有一个 ++ 和一个 *),所以 *++p 和 *(++p) 都会首先执行 ++p,然后对递增后的指针进行解引用。
这里是一个简单的例子来说明这一点:
#include <>
int main() {
int arr[] = {1, 2, 3};
int *p = arr; // p 指向数组的第一个元素
// 使用 *++p
printf("%d\n", *++p); // 这将打印 2,因为 p 首先递增到指向 arr[1],然后解引用得到 2
// 此时 p 已经指向 arr[1]
// 使用 *(++p)(尽管它与 *++p 等价)
// 但为了说明,我们再次递增 p 并解引用
printf("%d\n", *(++p)); // 这将打印 3,因为 p 再次递增到指向 arr[2],然后解引用得到 3
// 注意:此时 p 已经指向 arr[2] 的下一个位置(即数组末尾的下一个位置)
// 如果再次解引用 p,将会导致未定义行为(因为 p 指向了非法的内存位置)
return 0;
}
在这个例子中,*++p 和 *(++p) 都首先递增指针 p,然后解引用递增后的指针。重要的是要注意,在递增指针之后,指针将不再指向原始位置,因此在递增和解引用之后,你需要小心处理指针的位置,以避免访问无效的内存。
3.总结:
*p++ 和 *(p++) 都返回 p 自增之前的值。
*++p 和 *(++p) 都返回 p 自增之后指向的值。
关键在于理解前缀自增(++p)和后缀自增(p++)之间的区别,以及它们与解引用操作(*)的结合方式。前缀自增先改变指针的值,然后返回新的指针值;后缀自增返回原指针值,然后改变指针的值。解引用操作(*)则用于获取指针当前指向的值。
二、(*p)++ 与 ++(*p)
在C和C++中,(*p)++ 和 ++(*p) 这两个表达式虽然看起来相似,尽管它们都涉及到指针 p
的解引用和递增操作,但递增操作是应用于指针指向的值还是指针本身,这是关键的区别。其中的++本质上是对值操作
1. (*p)++
这个表达式首先通过 *p 解引用指针 p,获取它当前指向的值。
它对这个值执行后缀自增操作(++),即将该值增加1。
由于这是后缀自增,它首先返回原始值,然后才将值增加1。
因此,(*p)++ 的结果是 p 指向的值在自增之前的副本。同时,p 指向的值在表达式执行完毕后确实增加了1,但 p 本身(即指针的地址)没有改变。
2. ++(*p)
这个表达式也是首先通过 *p 解引用指针 p。
与 (*p)++ 不同,它对这个值执行前缀自增操作(++)。
前缀自增先增加值,然后返回新的值。
因此,++(*p) 的结果是 p 指向的值在自增之后的值。同样,p 本身(即指针的地址)没有改变,但 p 指向的值确实增加了1。
下面是一个简单的例子来说明:
#include <>
int main() {
int x = 5;
int *p = &x; // p 指向 x
printf("Before increment: %d\n", *p); // 输出 x 的原始值
// 使用 (*p)++
int temp1 = (*p)++; // temp1 接收递增前的值,x 的值变为 6
printf("After (*p)++: x = %d, temp1 = %d\n", x, temp1); // 输出 x 的新值和 temp1(递增前的值)
// 重置 x 以便下一个测试
x = 5;
p = &x;
printf("Before increment: %d\n", *p); // 再次输出 x 的原始值
// 使用 ++(*p)
int temp2 = ++(*p); // temp2 接收递增后的值,x 的值也变为 6
printf("After ++(*p): x = %d, temp2 = %d\n", x, temp2); // 输出 x 的新值和 temp2(递增后的值)
return 0;
}
输出结果:
Before increment: 5
After (*p)++: x = 6, temp1 = 5
Before increment: 5
After ++(*p): x = 6, temp2 = 6
3. 总结:
(*p)++:先获取 p 指向的值,然后将其增加1(但表达式的结果是增加之前的值)。
++(*p):先将 p 指向的值增加1,然后返回这个新的值。
在这两种情况下,指针 p 本身(即它存储的地址)都没有改变,只是它指向的值被修改了。
三、++*p
在C或C++中,++*p 和 ++(*p) 是完全等价的表达式。它们之间的括号实际上并没有改变表达式的含义或优先级,因为在这个上下文中,* 操作符的优先级已经足够高,以至于括号不是必需的。
++*p 可以分解为两个步骤:
解引用指针:*p 表示取出指针 p 当前指向地址的值。如果 p 是一个指向整数的指针,那么 *p 就是该整数变量的值。
前缀自增:++ 作用于解引用后的值上,意味着首先将 *p 的值增加1,然后返回这个新的值。与后缀自增(*p++)不同,前缀自增是在增加值之后立即返回新的值。
因此,++*p 的作用是先将 p 指向的值增加1,然后返回这个增加后的值。但是,需要注意的是,p 本身(即指针的地址)并没有改变,只是它指向的值被修改了。
举个例子:
int x = 5;
int *p = &x; // p 指向 x
int result = ++*p; // 先将 *p(即 x)的值增加1,然后返回新的值
// 此时,x 的值为 6,result 的值也为 6(因为 *p 已经变成了 6)
// p 本身的值(即它存储的地址)没有改变,它仍然指向 x
在这个例子中,++*p 表达式导致 x 的值从5增加到6,并且这个新的值(6)被赋值给了 result 变量。同时,p 指针仍然指向 x,但 x 的值已经被修改了。