天天看點

(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