天天看点

(1.1.22)前置++和后置++的区别 一、前置++和后置++的区别 二、Java和C的不同之处 三、VC和TC:后置++什么时候执行+1?四、前置++什么时候+1

对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.,理由是 前置自增 (++i) 通常要比后置自增 (i++) 效率更高。于是我查了查前置++和后置++的区别。

一、前置++和后置++的区别

[cpp]  view plain copy

  1. int a = 0;  
  2. ++ a;   //前置++  
  3. a++;    //后置++  

++a表示取a的地址,增加它的内容,然后把值放在寄存器中;

a++表示取a的地址,把它的值装入寄存器,然后增加内存中的a的值;

[cpp]  view plain copy

  1. //a,++a为地址,一直指向内存存储的值
  2. //a++为临时变量,是常量
  3. //注意在JAVA语言中,无论前置还是后置,返回的不再是地址,而是临时变量,因此会造出不同的输出
  4. int a = 4;  
  5. (a++)++;   //编译错误,a++返回一个临时变量,为常量类型,不能再次赋值修改
  6. ++(a++);   //编译错误
  7. (a++)+=1;  //编译错误
  8. //
  9. a+=a++;    //9。【java 8】(1)a++返回临时变量4,内存中a的值5 (2)a指向内存中值 (3)5+=4  (4)返回9
  10. a+=++a;    //10。【java 9】(1)++a直接对内存值+1,返回内存中a的值5  (2)a指向内存中的值  (3)5+=5  (4)返回10
  11. ++a+=a;    //10。【java 报错】(1)++a直接对内存值+1,返回内存中a的值5   (2)a指向内存中的中  (3)5+=5  (4)返回10
  12. ++a+=a++;  //11。【java 报错】(1)++a直接对内存值+1,返回内存中a的值5  (2)a++返回临时变量5,内存中a的值改为6 (3)左侧++a为地址指向的内存值,此时为6 (4)6+=5 (5)返回11
  13. ++a+=++a;  //12。【java 报错】(1)++a直接对内存值+1,返回内存中a的值5 (2)++a直接对内存值+1,返回内存中a的值6 (3)左右都为内存地址,指向内存中的值  (4)6+=6 (5)返回12
  14. //
  15. a=a+a++;   //9。【java 8】
  16. a=a+++a;   //9。【java 9】
  17. a=a+(++a);   //10。  【java 9】

从下面述代码,我们可以看出前置++和后置++,有3点不同:

  1. 返回类型不同:++a的返回类型是Age&,是被自增的对象本身,左值(地址);a++的返回类型const Age,是一个临时变量。
  2. 形参不同
  3. 代码不同:a++要拷贝
  4. 效率不同:a++产生临时变量

另外,网上找了篇文章,通过从运算符重载的角度来探讨他们的不同,如下:

假设有一个类Age,描述年龄。该类重载了前置++和后置++两个操作符,以实现对年龄的自增。

[cpp]  view plain copy

  1. class Age     
  2. {     
  3. public:     
  4.     Age& operator++() //前置++     
  5.     {     
  6.         ++i;     
  7.         return *this;     
  8.     }     
  9.     const Age operator++(int) //后置++     
  10.    {     
  11.         Age tmp = *this;     
  12.         ++(*this);  //利用前置++     
  13.         return tmp;     
  14.     }     
  15.     Age& operator=(int i) //赋值操作     
  16.     {     
  17.         this->i = i;     
  18.         return *this;     
  19.     }     
  20. private:     
  21.     int i;     
  22. };    

返回值类型的区别

前置++的返回类型是Age&,后置++的返回类型const Age。这意味着,前置++返回的是左值,后置++返回的是右值。(关于左值和右值的讨论很多,见本文下面)

左值和右值,决定了前置++和后置++的用法。

[cpp]  view plain copy

  1. int main()     
  2. {     
  3.     Age a;     
  4.     (a++)++;  //编译错误     
  5.     ++(a++);  //编译错误     
  6.     a++ = 1;   //编译错误     
  7.     (++a)++;  //OK     
  8.     ++(++a);  //OK     
  9.     ++a = 1;   //OK     
  10. }    

a++的类型是const Age,自然不能对它进行前置++、后置++、赋值等操作。

++a的类型是Age&,当然可以对它进行前置++、后置++、赋值等操作

a++的返回类型为什么要是const对象呢?

有两个原因:

  1. 如果不是const对象,a(++)++这样的表达式就可以通过编译。但是,其效果却违反了我们的直觉 。a其实只增加了1,因为第二次自增作用在一个临时对象上。
  2. 另外,对于内置类型,(i++)++这样的表达式是不能通过编译的。自定义类型的操作符重载,应该与内置类型保持行为一致 。

a++的返回类型如果改成非const对象,肯定能通过编译,但是我们最好不要这样做。

++a的返回类型为什么是引用呢?

这样做的原因应该就是:与内置类型的行为保持一致。前置++返回的总是被自增的对象本身。因此,++(++a)的效果就是a被自增两次。

形参的区别

前置++没有形参,而后置++有一个int形参,但是该形参也没有被用到。很奇怪,难道有什么特殊的用意?

其实也没有特殊的用意,只是为了绕过语法的限制。

前置++与后置++的操作符重载函数,函数原型必须不同。否则就违反了“重载函数必须拥有不同的函数原型”的语法规定。

虽然前置++与后置++的返回类型不同,但是返回类型不属于函数原型。为了绕过语法限制,只好给后置++增加了一个int形参。

原因就是这么简单,真的没其他特殊用意。其实,给前置++增加形参也可以;增加一个double形参而不是int形参,也可以。只是,当时就这么决定了。

代码实现的区别

前置++的实现比较简单,自增之后,将*this返回即可。需要注意的是,一定要返回*this。

后置++的实现稍微麻烦一些。因为要返回自增之前的对象,所以先将对象拷贝一份,再进行自增,最后返回那个拷贝。

在Age的代码中,后置++利用了前置++来实现自增。这样做是为了避免“自增的代码”重复。

在本例中,自增的代码很简单,就是一行++i,没有必要这样做。但是在其它自增逻辑复杂的例子中,这么做还是很有必要的。

效率的区别

如果不需要返回自增之前的值,那么前置++和后置++的计算效果都一样。但是,我们仍然应该优先使用前置++,尤其是对于用户自定义类型的自增操作。

前置++的效率更高,理由是:后置++会生成临时对象。

从Age的后置++的代码实现也可以看出这一点。

[cpp]  view plain copy

  1. const Age operator++(int) //后置++     
  2. {     
  3.     Age tmp = *this;     
  4.     ++(*this);  //利用前置++     
  5.     return tmp;     
  6. }    

很明显,tmp是一个临时对象,会造成一次构造函数和一次析构函数的额外开销。虽然,编译器在某些情况下可以优化掉这些开销。但是,我们最好不要依赖编译器的行为。

所以,在非内置类型的时候,尽量使用前置++,因为效率高(后置自增,效率低)

另外,网上找了篇文章,通过从运算符重载的角度来探讨他们的不同,如下:

假设有一个类Age,描述年龄。该类重载了前置++和后置++两个操作符,以实现对年龄的自增。

[cpp]  view plain copy

  1. class Age     
  2. {     
  3. public:     
  4.     Age& operator++() //前置++     
  5.     {     
  6.         ++i;     
  7.         return *this;     
  8.     }     
  9.     const Age operator++(int) //后置++     
  10.    {     
  11.         Age tmp = *this;     
  12.         ++(*this);  //利用前置++     
  13.         return tmp;     
  14.     }     
  15.     Age& operator=(int i) //赋值操作     
  16.     {     
  17.         this->i = i;     
  18.         return *this;     
  19.     }     
  20. private:     
  21.     int i;     
  22. };    

二、Java和C的不同之处

java产生临时变量 c直接对原值修改

public class Test {

 public static void main(String[] args) 
     {
  int a=1,i=1;
  for(i=1;i<10;i++)
      {
   a=a++;
   System.out.println(a);
   }
   
   System.out.println(a);
      }

}
执行结果为:
1
1
1
1
1
1
1
1
1
1
#include"stdio.h"
main()
    {
    int a=1,i=1;
  for(i=1;i<10;i++)
      {
   a=a++;
   printf("%d\n",a); 
   }
            printf("%d\n",a); 
    }
结果为:
2
3
4
5
6
7
8
9
10
10
           
在Java中,
a=a++;   后置++立即+1;
相当于(1)a++返回1  (2)立马内存中+1:a=a+1 (3)覆盖重写a=1    

在VC中,后置++在整个语句结束后才执行。
a=a++;  后置++是在整个赋值语句完成,才进行的      
譬如:(1)a++先返回1 (2)a=1;(3)赋值语句结束,a=a+1=2 (4)输出2      
(2)a++先返回2 (2)a=2 (3)赋值语句结束,a=a+1=3 (4)输出3      

三、VC和TC:后置++什么时候执行+1?

如有int i = 2时,表达式(i++) + (i++) + (i++)的值是多少呢?

//TC中后置++在子表达式结束后理立即执行。

在TC中,第一个子表达式i++求完值后,变量i会立即执行自增操作,因此,第二个子表达式中变量i的值已经是3了。表达式(i++) + (i++) + (i++)的值为9(2+3+4)。

在java中,第一个子表达式i++求完值后,变量i会立即执行自增操作,因此,第二个子表达式中变量i的值已经是3了。表达式(i++) + (i++) + (i++)的值为9(2+3+4)。

//i++返回临时变量,解答下。后置++在整个语句结束后才执行。

在VC6.0中,第一个子表达式i++求完值后,其它子表达式中出现的变量i的值还没有改变,依然是2。表达式(i++) + (i++) + (i++)的值为6(2+2+2),求完值后,变量i会执行自增操作3次,其值会变成5。

四、前置++什么时候+1

//++i返回内存地址解释下原因

VC6.0采用了不同的原则处理前置和后置自增(自减)操作符。前置自增操作符会先执行自增操作,如有int i = 2时,表达式++i + i的值为6(3+3),但是表达式(++i) + (++i)的值却是8(4+4即先执行两次自增操作,第一次使变量i的值变为3,第二次使变量i的值变为4,然后再求值)。

表达式(++i) + (++i) + (++i)的值是多少呢?(4+4+5=13,子表达式(++i) +(++i) 求完值后,原表达式变成了8+(++i)。)

int a=1,i=2;
	 if((++a!=1)&&(++a==3))
		   printf("%d",a);//3