++i和i++的区别

本文详细解析了C语言中自增运算符`++`和`i++`的区别,通过实例展示了它们在操作变量时的顺序与结果,强调了副作用的概念和运算符的优先级、结合性的重要性。同时,对比了间接运算符`*p++`与`*++p`的使用方式,深入理解C语言中的自增运算符应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

For i++。因此i++属于右值(最终表达式返回的是临时变量)

int a, i=1;
a = i++;
//for i++
int temp = i; //temp is 1
i = i+1;      //2
return temp;  //return value is 1
so
a = temp;     //a is 1
For ++i

int a, i=1;
a = ++i;
//for ++i
i = i+1;     //i is 2
return i;    //return is 2
so 
a = i        //a is 2

可看出i++因为要生成临时对象,效率较低。

接下来,通过示例彻底理解自增运算符的两种用法(自减的用法与之类似,只不过是加1变成了减1)。

    1、++i和i++的区别

    如清单1(注意代码中的注释): 

  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     int a, b, i = 7;  
  6.   
  7.     i++; //等价于i = i + 1;   
  8.     ++i; //等价于i = i + 1;   
  9.   
  10.     a = i++; //等价于a = i; i = i + 1;   
  11.     b = ++i; //等价于i = i + 1; b = i;  
  12.       
  13.     printf("a = %d, b = %d\n", a, b);  
  14.       
  15.     return 0;  
  16. }  
#include <stdio.h>

int main(void)
{
    int a, b, i = 7;

    i++; //等价于i = i + 1;
    ++i; //等价于i = i + 1;

    a = i++; //等价于a = i; i = i + 1;
    b = ++i; //等价于i = i + 1; b = i;
    
    printf("a = %d, b = %d\n", a, b);
    
    return 0;
}

    例子输出结果: 

  1. a = 9, b = 11  
a = 9, b = 11

    在例子中,第7和第8行的作用一样,仅仅是为变量i加1,这时i的值已经增加为9,接下来第10行变量a先获得i的值(即9),然后i加1,第11行变量i先再加1,然后把得到的值赋给b,所以b的值为11。

    稍微复杂的例子,如清单2: 

  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     int a = 5;  
  6.   
  7.     int *p = &a;  
  8.   
  9.     int b = (*p)++; //等价于b = a++; 即b = a; a = a + 1;  
  10.   
  11.     int c = ++(*p); //等价于c = ++a; 即a = a + 1; c = a;   
  12.       
  13.     printf("b = %d, c = %d\n", b, c);  
  14.   
  15.     printf("(*p)++ = %d, ++(*p) = %d\n", (*p)++, ++(*p));  
  16.   
  17.     return 0;  
  18. }  
#include <stdio.h>

int main(void)
{
    int a = 5;

    int *p = &a;

    int b = (*p)++; //等价于b = a++; 即b = a; a = a + 1;

    int c = ++(*p); //等价于c = ++a; 即a = a + 1; c = a; 
    
    printf("b = %d, c = %d\n", b, c);

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

    return 0;
}

    例子输出结果: 

  1. b = 5, c = 7  
  2. (*p)++ = 8, ++(*p) = 8  
b = 5, c = 7
(*p)++ = 8, ++(*p) = 8

    在这个例子中,只不过是通过*p来间接地操作a,其他关于自增运算符的用法与清单1类似。第9行的*p一定要用小括号括起来,否则含义就不一样了。而第11行的++(*p)也可以写成++*p(用GCC验证过),那是因为对操作数p来说它只有一个运算符*在计算它,所以无关乎运算符优先级和结合性的问题。

    值得注意的是,由于C语言没有指定函数各参数的求值顺序,所以第15行的代码是不可移植的,用不同的编译器可能会产生不同的结果(对于这个例子,GCC是先计算++(*p),后计算(*p)++,所以两者都等于8)。

    知识点:

    (1)、副作用

    在对表达式求值的同时,修改了某些变量的值,其中修改值的行为在C语言中被叫作副作用,那是因为对C语言而言,计算的目的就是对表达式求值,如语句int a = 5,它的含义是先求值得到5,然后把5赋值给变量a,后一步的赋值就是此表达式的副作用。自增和自减运算符就是因为副作用而被使用,除了加1或减1之外,还给自身赋值。

    (2)、运算符的优先级

    在C语言中,把运算符的优先级分为15级,如下表,从上到下,依次为从最高优先级到最低优先级(为了方便记忆,将15级分成11类,并对每类进行了命名)。

初等运算符

包括小括号 ()、中括号 [] 、成员访问运算符 . 和 -> 。

一元运算符

包括自增++和自减--、正负号+ 和-、间接运算*和取址运算& 、类型转换(type)、 sizeof 、逻辑反! 、位取反~等。

算术运算符

包括两级,先乘除(*、/、%)后加减(+、-)。

位移运算符

包括左移 << 和右移 >> 。

关系运算符

包括小于 < 、小于等于 <= 、大于 > 、大于等于 >= 。

判等运算符

包括相等 == 和不相等 != 。

位逻辑运算符

分三级,依次为位与 &、位异或 ^ 和位或 | 。

逻辑运算符

分两级,依次为逻辑与 && 和逻辑或 || 。

条件运算符

? :

赋值运算符

包括= 、+= 、-=、 *=、 /=、 %= 、&= 、^=、 |= 、<<= 、>>= 。

逗号运算符

    (3)、结合性

    对于同一操作数,在具有两个相同优先级的操作符时决定先执行哪个操作符的问题就是由结合性决定的。

    相同优先级的操作符具有同样的结合性。右结合性就是说表达式中最右边的操作最先执行,然后从右到左依次执行。在C语言中,具有右结合性的操作符只有相应的三类,分别为一元运算符、条件运算符和赋值运算符。

    注意:C语言中的优先级和结合性都是针对同一操作数而言的。如表达式24/8*2,对于操作数8而言,/ 和*的优先级相同,所以再根据它们的左结合性可知,表达式是先计算24/8得到3,然后计算3*2得到6。

    C语言并没有规定同一运算符相关的多个操作数的计算顺序(&&、|| 、? : 和 , 运算符除外),如式子a = 8 * 9 + 20 * 4,对操作数9和20而言,根据优先级就可判断先乘后加,但表达式中的两个*并不共享同一操作数,所以从左到右的结合性并不适用它,8 * 9 和20 * 4的计算顺序是不定的,到底先计算8 * 9还是20 * 4由编译器决定。

    在上面例子中,8 * 9和20 * 4谁先执行都不影响最后结果的一致,但有些情况下就未必了,如“ b = 3; a = (b++) * (b++); ”这样的例子,对于不同的编译器最后a的值可能等于9,也可能等于12,甚至可能等于16。因此,在实际应用中不能出现这样的未确定性,根据自己的需要,可以把它改成类似“b = 3; c = b++; a = c * c;”这样的形式。

    2、*p++和*++p的区别

    举例,如清单3: 

  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     int arr[] = {1, 2, 3, 4};  
  6.     int *p = arr;  
  7.   
  8.     int a = *p++; //等价于a = *(p++); 即a = *p; p = p + 1;  
  9.       
  10.     int b = *++p; //等价于b = *(++p); 即p = p + 1; b = *p;  
  11.       
  12.     printf("a = %d, b = %d\n", a, b);  
  13.   
  14.     return 0;  
  15. }  
#include <stdio.h>

int main(void)
{
    int arr[] = {1, 2, 3, 4};
    int *p = arr;

    int a = *p++; //等价于a = *(p++); 即a = *p; p = p + 1;
    
    int b = *++p; //等价于b = *(++p); 即p = p + 1; b = *p;
    
    printf("a = %d, b = %d\n", a, b);

    return 0;
}

    例子输出结果: 

  1. a = 1, b = 3  
a = 1, b = 3

    对于第8行的操作数p而言,*和++的优先级相同,但根据它们的右结合性可知,在这个表达式里可认为++的优先级高于*,即*p++等价于*(p++)。

    而对于第10行的操作数p而言,它只有一个运算符++,所以先计算++p得出结果,然后间接运算。

### 前置自增 `++i` 后置自增 `i++` 的区别及用法 #### 一、基本概念 前置自增 (`++i`) 后置自增 (`i++`) 是 C/C++ 中用于变量值增加的操作符。它们的主要功能都是使变量的值加 1,但在具体的行为上有显著差异。 - **前置自增 (`++i`)** 在表达式中立即对变量进行自增操作,并返回自增后的值[^4]。 - **后置自增 (`i++`)** 首先返回当前变量的原始值,在整个表达式计算完成后才对该变量进行自增操作[^3]。 --- #### 二、语法实现细节 对于内置数据类型(如 `int`, `float`),编译器会对这两种操作符进行优化处理,通常性能差距不明显。但对于用户定义的数据类型(如类对象),两者的实现方式存在本质上的不同: - **前置自增** 实现时一般通过引用返回自身,避免额外的对象拷贝开销。例如: ```cpp Age& operator++() { ++this->value; return *this; // 返回自增后的对象本身 } ``` - **后置自增** 需要创建一个临时对象来保存原值,以便在后续使用时不被修改影响其他部分逻辑。例如: ```cpp const Age operator++(int) { Age temp = *this; // 创建副本存储旧状态 ++(*this); // 调用前置++ return temp; // 返回未改变前的状态 } ``` 因此,当涉及复杂类型的运算时,后置版本可能带来更高的内存消耗运行时间成本[^5]。 --- #### 三、实际应用案例分析 考虑如下代码片段及其行为解释: ```cpp #include <iostream> using namespace std; class Counter { public: int value; Counter(int v): value(v) {} // 前置++ Counter& operator++() { cout << "Pre-increment called\n"; ++value; return *this; } // 后置++ Counter operator++(int) { cout << "Post-increment called\n"; Counter old = *this; ++(*this); return old; } }; void testIncrementOperators() { Counter c1(5), c2(7); cout << "c1 before increment: " << c1.value << endl; cout << "Result of pre-increment on c1: " << (++c1).value << endl; cout << "c1 after pre-increment: " << c1.value << endl; cout << "\nc2 before increment: " << c2.value << endl; cout << "Result of post-increment on c2: " << (c2++).value << endl; cout << "c2 after post-increment: " << c2.value << endl; } int main(){ testIncrementOperators(); } ``` 此程序展示了如何分别调用了前置与后置两种形式下的方法并打印相应结果[^5]。 --- #### 四、注意事项 尽管现代编译技术能够有效减少简单场景下二者之间的效率差别,但仍需注意以下几点: - 如果仅为了更新某个数值而无需关心其原有值,则优先选用前置形式以提高可读性潜在性能优势; - 对于大型结构体或者含有深层嵌套成员属性的情况尤其如此——因为每次复制都意味着更多资源占用[^3]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值