*p++和(*p)++的区别是什么

来源:建站作者:阿亮头衔:草根站长
导读:本期聚焦于阿亮创作的《*p++和(*p)++的区别是什么》,敬请观看详情。指针与自增运算符结合时常让初学者困惑,*p++和(*p)++虽然只差一对括号,执行结果却完全不同。要准确理解两者,必须先弄清运算符优先级和副作用发生的时机。*p++中的后缀自增优先级高于解引用,因此编译器先对p做自增,表达式整体返回的是p原来指向的值,随后p才移动到下一个位置;这一写法经常出现在遍历数组或复制字符串的循环里。而(*p)++是先用括号强制先解引用,取得p指向的那个变量,再对这个变量的值执行自增,指针p本身不会改变。简单说,*p++改变的是指针,(*p)++改变的是指针指向的数据。若误用会导致指针越界或修改了错误对象。本文结合优先级规则、内存变化和典型代码示例,把这两个表达式的区别讲清楚,并给出安全使用建议。

在C语言里,*p++ 和 (*p)++ 只差一对括号,但编译器对它们的解释完全不同。前者通常用于遍历数组时一边读取当前元素一边移动指针,后者则是在保持指针不变的情况下修改指向的数据。要彻底区分二者,需要从运算符优先级、后缀自增的返回值以及副作用发生时机三个层面分析。

*p++和(*p)++的区别是什么

一、运算符优先级决定解析方式

理解这两个表达式,第一步要弄清楚C语言中相关运算符的优先级。后缀自增运算符 ++ 的优先级高于解引用运算符 *,而圆括号的优先级最高。因此,*p++ 并不会被解析成先解引用再自增,而是会被解析成 *(p++)。也就是说,后缀自增先作用于指针 p,解引用再作用于 p++ 的返回值。

后缀自增有一个关键特性:表达式的值是变量自增前的旧值。所以 p++ 这个子表达式返回的是 p 原来的地址,而副作用则是把 p 自身加一。等到解引用运算符作用在这个返回值上时,读取到的仍然是原来地址里的数据。最终效果就是:取到当前指向的值,同时指针向后移动一个元素。

相比之下,(*p)++ 通过圆括号强制改变了默认优先级。编译器会先计算 *p,得到指针 p 所指向的那个对象。接着后缀自增直接作用在这个对象上,使其值增加一。表达式的整体值同样是该对象自增前的旧值,但指针 p 本身不会发生任何移动。这个区别可以用一个简单表格概括。

表达式实际解析改变的是谁表达式返回值
*p++*(p++)指针 p 本身原来指向的对象值
(*p)++(*p)++p 指向的对象该对象自增前的值

二、用代码观察两种写法的实际差异

光看规则还不够直观,下面用一段完整的C程序展示 *p++(*p)++ 在内存中的不同表现。代码里定义一个包含三个元素的数组,并让指针 p 初始指向数组首元素。

#include <stdio.h>

int main(void) {
    int arr[] = {10, 20, 30};
    int *p = arr;

    int v1 = *p++;
    printf("v1=%d, *p=%d\n", v1, *p);

    int v2 = (*p)++;
    printf("v2=%d, *p=%d\n", v2, *p);

    return 0;
}

运行这段程序,第一行输出为 v1=10, *p=20。原因在于 p 最初指向 arr[0],它的值是10。*p++ 先返回这个10,然后把 p 移动到 arr[1],因此打印 *p 时得到的是20。可以看出,这一操作同时完成了读取当前值和指针后移两件事。

第二行输出为 v2=20, *p=21。此时 p 已经指向 arr[1],它的值是20。(*p)++ 先解引用得到 arr[1],再把 arr[1] 的值从20增加到21,而 p 仍然指向 arr[1]。所以 v2 得到旧值20,打印 *p 时已经是新的值21。

从内存视角看,*p++ 改变的是指针变量 p 中保存的地址,而 (*p)++ 改变的是该地址上存放的整数内容。如果指针指向的是结构体或数组等复合对象,前者的移动幅度则取决于指针所指向类型的大小,例如 int* 每次移动通常为4字节,char* 每次移动1字节。

三、等价的拆分写法与副作用时机

为了进一步理解,可以把两个表达式拆成更基础的步骤。*p++ 大致等价于以下逻辑:先保存指针旧值,再让指针加一,最后通过旧地址解引用。

int *old = p;
p = p + 1;
int result = *old;

(*p)++ 则等价于:先解引用取得对象,再让这个对象的数值增加一,但表达式的返回值仍然是旧值。

int old_value = *p;
*p = *p + 1;
int result = old_value;

需要注意,后缀自增的副作用并不要求立即完成。C标准只保证副作用会在下一个序列点之前完成。也就是说,在 *p++ 中,p 的加一操作可能在完成解引用之后才真正写回内存,但这不影响最终可观察行为。真正需要警惕的是在同一个表达式里多次修改同一个变量,或者同时修改与读取同一个变量,这会带来未定义行为或未指定行为。

一个常见的错误是在函数参数中连续使用多个 *p++。例如 printf("%d %d\n", *p++, *p++) 这类写法,虽然看上去很紧凑,但函数参数求值顺序在C语言里是不确定的。编译器可能先计算左侧参数,也可能先计算右侧参数,最终结果会因平台和编译优化级别不同而变化。更安全的方式是把每一步拆开,或者先取值再单独移动指针。

四、典型使用场景与避坑建议

*p++ 最常见的场景是遍历数组或者复制字符串。例如实现一个简单的字符串拷贝函数时,可以用 *d++ = *s++ 同时完成复制和两个指针的后移。只要目标缓冲区足够大,这种写法非常紧凑。遍历数组求和时也可以写成 sum += *p++,每次读取当前元素后自动前进到下一个元素。

char *my_strcpy(char *dest, const char *src) {
    char *d = dest;
    while ((*d++ = *src++) != '\0')
        ;
    return dest;
}

这里 *d++ 返回的是 d 原来指向的字符位置,*src++ 返回的是 src 原来指向的字符。赋值完成后,两个指针都会向后移动。当把 src 指向的空字符复制过来后,整个循环结束。由于移动和复制耦合在一起,代码非常简短,但也要求读者对运算符优先级有清晰认识。

(*p)++ 更适合那些需要修改指针所指内容的场景。比如某个函数通过指针参数返回计数器,调用方可以使用 (*counter)++ 来增加计数。又比如在数组中需要把某个元素的值加一时,若不想改变当前指针位置,就应该写 (*p)++ 而不是 *p++。错误地写成 *p++ 会导致指针跑到下一个位置,后续代码可能继续基于错误的地址操作,甚至越界。

实际编程中,如果对优先级没有十足把握,最好的办法是加上括号,或者干脆拆成两行。括号能让意图一目了然:*(p++) 表达的就是先移动指针再取值,(*p)++ 表达的就是先取值再对值自增。利用 const 也可以帮助检查误用,比如 const int *p 指向只读数据时,编译器会禁止 (*p)++,因为不能修改指向的值;而 int * const p 表示指针本身不可修改,此时 *p++ 会触发编译错误。

总结起来,*p++ 改变的是指针本身,(*p)++ 改变的是指针指向的数据。理解后缀自增的旧值返回值特性,以及解引用运算符的优先级,是区分二者的关键。在阅读他人代码时遇到这类表达式,可以先根据优先级补全括号,再判断当前操作到底移动了指针还是修改了内存内容。如果写出的表达式需要读者反复推敲,不如拆成更明确的语句,用少量代码行数换取长期的可维护性。

指针自增运算符优先级解引用修改时间:2026-08-23 18:01:41

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。