同不少高級編程語言同樣,Java語言的運算符系統當中也有自增(++)和自減(--)這兩個運算符。不少小夥伴對這兩個運算符都深感頭疼,而且不少公司在面試的時候也常常會問到與之相關的問題,今天咱們就經過一篇文章來深度解析一下這兩個運算符,相信在看過這篇文章以後,你不再會被自增(++)和自減(--)運算符難住。因爲自增和自減運算符的原理徹底相同,因此咱們在講解的時候僅以自增運算符舉例。(預警:本文舉例較多,篇幅較長,請耐心看完)java
自增(++)和自減(--)運算符是對變量在原始值的基礎上進行加1或減1的操做。它們都有前綴和後綴兩種形式。前綴形式的運算規則能夠歸納爲:」先自增(減),後引用」,然後綴形式的運算規則能夠歸納爲:」先引用,後自增(減)」。這裏所說的」引用」,指的是使用變量的值。另外,咱們還要強調一個細節:不管是前綴形式仍是後綴形式,自增自減運算符的優先級要高於賦值運算符。你們要記清楚這個細節,後文還會針對這個細節進行論述。下面咱們就分爲幾種狀況來研究++和--在不一樣場合下的運算效果。面試
請看代碼:編程
咱們能夠看到,這段代碼中總共有3條語句,其中第2條語句中僅有一個後綴形式++操做,程序的輸出結果是3。那麼咱們再來看另一段代碼:編程語言
這段代碼與以前的那段代碼基本同樣,只是第2條語句中,後綴形式的++操做被換成了前綴形式,程序的輸出結果仍是3。這說明:當一條語句中僅有++或--操做時,前綴形式與後綴形式的運算符沒有任何區別。請注意:這句話的前半句是一個很重要的前提,那就是」 一條語句中僅有++或--操做」,若是脫離了這個前提,後半句所說的結論並不成立。ide
可能有些小夥伴沒看明白這個標題是什麼意思,咱們來看一段代碼:學習
這段代碼的第2條語句對變量a進行了自增操做,而且把這個操做結果賦值給另外一個變量b。語句中的變量b就是標題中所說的」其餘變量」,是指沒有進行自增自減操做的其餘變量。爲何要強調」賦值給其餘變量」這個前提呢?就是由於若是把運算結果賦值給變量a自身,又會產生不一樣的效果,咱們後面再去講解賦值給自身的狀況。如今先來分析程序,重點看第2條語句:變量a所進行的是前綴形式的自增操做,那麼按照」先自增後引用」的運算規則,a的值首先變成3,而後賦值b。所以,給變量b賦值的是3,那麼輸出結果固然就是3和3。
若是代碼變成下面的樣子:3d
這一次,代碼的第2行發生了變化,a的自增操做變成了後綴形式。此時的程序輸出結果是3和2。爲何會是這樣的運行結果呢?網上有不少資料對此的解釋是:由於表達式中出現的是後綴形式的自增操做,所以,運算的規則就變成了」先引用後自增」。計算機會先使用a的值給b賦值,a的值是2,因此b被賦值爲2,a在完成給b賦值的操做以後,纔會完成自增變爲3,因此程序的輸出結果爲3和2。這種解釋看似很是合理,但實際上是錯誤的!視頻
按照這種解釋,後綴形式的自增是在賦值以後才完成的,由此能夠推出後綴形式的自增自減運算的優先級比賦值運算的優先級更低。而咱們以前已經特地強調過:不管是前綴形式仍是後綴形式,自增自減運算符的優先級都比賦值運算符要高。接下來問題來了:既然++和--的運算優先級高於賦值運算符,那麼爲何賦值以前a的值沒有自增爲3呢?blog
其實這是個錯覺!a在賦值給變量b以前,就已經完成了自增!爲了講解清楚真實狀況,咱們必須科普一個小常識,那就是:當程序中,若是變量參與了算術運算、或者以變量的值進行賦值,或者打印了某個變量的值,總之只要程序中用到這個變量的值,都會先把這個變量存入一個臨時的空間,專業上把這個臨時的空間稱之爲」操做數棧」。咱們以前所說的」先引用後自增」中所說的這個」引用」操做,其實就是指」把變量的值存入操做數棧」這個動做。當程序中須要用到變量的值,計算機是從」操做數棧」中取出值進行運算,並非咱們想象的直接從變量所在的內存單元中取出數值。可是,若是語句中僅有++或--,並不會把變量的值存入操做數棧,而是直接對變量進行自增或自減的操做,這也是爲何咱們把語句中僅有++或--單獨做爲一種狀況講解的緣由。內存
科普完這個小常識以後,咱們來解釋剛纔的代碼爲何會輸出3和2。代碼中出現用a的值給變量b賦值的語句,而且a的後面出現了++,說明要對a進行後綴形式的自增操做。按照咱們剛纔科普的小常識,a參與了賦值運算,那麼就會把a的值存入操做數棧。由於a的自增是後綴形式的,因此要遵循」先引用後自增」的運算規則,所以,計算機會首先取出a的值2存入操做數棧,而後再把a的值增長到3。作完自增操做以後,接下來會對變量b進行賦值操做。那麼,是用哪一個值給變量b賦值呢?就是用剛纔存到操做數棧中的那個2對變量b進行賦值,因此b最終獲得的值是2,所以輸出結果是3和2。
在這裏,請你們注意一個細節,那就是:代碼中的自增操做雖然是後綴形式的,但這個自增動做倒是在賦值以前完成的,這也解釋了後綴形式的自增運算優先級高於賦值運算,而網上不少資料中所說的」先完成賦值再去作自增操做」是徹底錯誤的。
那麼,以前例題中第2條語句是」b=++a;」,會不會也把a的值存入操做數棧呢?答案是確定的,只要是變量參了算術運算、賦值、被打印這些操做,都會取出變量的值存入操做數棧。由於語句中出現的是前綴形式的自增,因此在把值存入操做數棧以前就已經完成了自增操做。
好,如今咱們來加大一點難度,請看代碼:
這段代碼的第2條語句,出現了2次++a,那麼運算結果會是多少呢?」=」右邊的表達式爲」++a + ++a」,按照運算優先級,計算機首先對a進行自增操做,通過這一步操做以後,變量a的值變成了3,緊接着,計算機會把這個3存入操做數棧中,爲了方便講述,咱們把這個操做數棧叫作」棧1」,接下來,表達式中又出現了++a,此時計算機再次對a進行自增,a的值變成了4,爲了完成加法操做,計算機又把這個4存入到另外一個操做數棧中。咱們把這個操做數棧稱爲」棧2」。按照運算優先級,接下來要作的事情就是完成加法運算,計算機把」棧1」中的3和」棧2」中的4分別取出而且進行相加,獲得的結果是7,最後把這個7賦值給」=」左邊的變量b。因此上面代碼運行所輸出的結果分別是4和7。
下面咱們再來研究一個關於運算順序問題,請看下面的代碼:
咱們仍是重點來分析第2行代碼。」=」右邊首先是++a,因此對先a進行自增操做,此時a變成了3,緊接着把自增以後的值存入」棧1」,接下來遇到了a++,由於是後綴形式的自增操做,遵循」先引用後自增」的運算規則,計算機先取出a的值3,而且存入」棧2」,而後對a進行自增的操做,a的值變成了4。如今,」棧1」和」棧2」中都已經存入了值,那麼,此時計算機是先把」棧1」和」棧2」中的值作個相加操做呢,仍是先去作變量a的第3次自增操做呢?不少同窗確定都會認爲計算機確定是先去完成a的第3次自增操做,理由是自增自減運算優先級高於加法運算。其實否則,當運算進行到這一步的時候,計算機會先把」棧1」和」棧2」中的兩個值相加,而後纔去完成a的第3次自增操做。不少人都不理解爲何會這樣,難道不是++a的優先級更高嗎?
這裏須要澄清一個你們對」優先級」概念的誤解。當計算機有AB兩個操做能夠作的時候,若是選擇先完成A獲得的結果是X,而選擇先完成B獲得結果是Y,此時計算機必須按照運算的優先級作出選擇。而若是先完成A和先完成B對運算結果沒有影響,那麼計算機就會先完成左邊(先出現的)的操做。此時,若是把」棧1」和」棧2」中的值相加,並不會影響到最終的運算結果,而且這個操做是先於」a的第3次自增」出現的,因此計算機會先把」棧1」和」棧2」中的值加起來,而後才完成」a的第3次自增」。
不少小夥伴可能不理解,那麼自增操做優先級高於加法操做,是如何體現出來的呢?你們仔細看,在代碼的第2條語句中,a的自增出現了3次,加法運算出現了2次。」a的第2次自增」後於」第1次加法運算」出現,但卻先於」第1次加法運算」執行,同理,」a的第3次自增」後於」第2次加法運算」出現,卻先於」第2次加法運算」執行。這些都體現出自增運算的優先級是高於加法運算的。
講清楚」優先級」這個問題以後,咱們再回到例題自己。第1次加法運算是3和3相加,結果是6,計算機會把這個6存入」棧3」。緊接着計算機看到表達式中第2次出現了++a,因而再次對a進行自增操做,a的值變成了5,而且存入」棧4」。以後就是完成」棧3」和」棧4」中數值的相加操做,也就是把6和5相加,最終獲得的結果是11。所以程序最終的輸出結果是5和11。
標題中所說的」自身」,就是指進行了自增或自減運算操做的變量。咱們來看下面的代碼:
以上代碼的第2條語句,對a進行了前綴形式的自增,而後又賦值給a自身,那麼a的值是多少呢?由於a進行的是前綴形式的自增,因此運算規則是」先自增後引用」,自增以後a的值變成了3,把3存入操做數棧,以後以3賦值給a,因此a的值仍是3。
可是,若是咱們把代碼變成以下形式:
這一次,咱們把第2條語句中的++a改爲了a++。這種狀況下,程序輸出a的值爲居然爲2,而不是3。說的直白一點,a並無按咱們的想象實現自增。這是爲何呢?咱們來分析一下整個運算的過程:計算機看到」=」右邊是後綴形式的自增,所以以」先引用後自增」的規則進行運算,先把a的值存入操做數棧,緊接着對a進行自增操做,a的值變成了3,最後又用操做數棧中的那個2對a進行賦值,a的值又變成了2。這樣給咱們形成了一種」a沒有進行自增」的錯覺。以前說過,網上不少資料都誤傳「後綴形式自增操做優先級低於賦值運算」。若是按照這種錯誤說法,沒法解釋以上代碼最終的輸出結果爲2的緣由。這也是爲何本文一開始就強調」不管是前綴形式仍是後綴形式,自增自減運算符的優先級要高於賦值運算符」的緣由,就是由於這個細節是破解此類問題的關鍵點。相似的狀況還能夠衍生出不少版本,例如如下這段代碼::
這段代碼中,第2條語句對a進行了兩次自增操做,最終輸出a的值是6。而若是按照」後綴形式的自增自減優先級低於賦值運算」的錯誤說法,則會認爲最終輸出a的值是7,理由是」=」右邊的運算結果是6,賦值給」=」左邊的a以後,又進行一次自增,最終的結果是7,固然,事實能夠證實這種理解是錯誤的。
接下來,咱們再來研究一種更特殊的狀況,請看如下代碼:
這一次,語句中出現了複合賦值運算符。若是程序運行,輸出a的值會是多少呢?咱們首先能夠推導出」+=」右邊的運算結果是7(具體推導過程再也不贅述)。咱們還知道,複合賦值運算符在完成運算的時候,要把右邊看成總體。那麼如今關鍵的問題就只剩一個了,那就是:」+=」左邊的a究竟是多少?不少人認爲」+=」左邊a的值應該是4,緣由是++的運算優先級高於+=,因此要先完成2次自增,完成了2次自增之後,a的值已經變成了4,由此推得」+=」左邊a的值應該是4,而最終的運算結果是11(4+7的和)。但實際運行程序的話,能夠看到輸出a的值爲9而非11。這是爲何呢?就是由於+=的優先級雖然低於++,可是計算機在實際完成+=運算的時候會分爲好幾個步驟進行。咱們能夠大體把+=運算分解爲四大步驟:
A、把+=左邊的變量值存入操做數棧1
B、計算+=右邊的表達式,並把計算結果存入操做數棧2(此步驟實際上是由多個具體步驟組成的)
C、把操做數棧1和操做數棧2中的數值相加獲得運算結果
D、把運算結果存入變量a當中
如今最關鍵的問題是步驟A和B哪個先被執行。若是先執行步驟A,那麼存入操做數棧1的是變量a自增以前的值,也就是2;反之,若是先執行B,那麼存入操做數棧1的是變量a自增以後的值,也就是4。真實的狀況是先執行步驟A,也就是把變量a自增以前的值存入操做數棧1。這是一個廣泛適用的規律,因此你們必定要記住:當語句中以複合賦值運算符給變量賦值的時候,計算機會先把複合賦值運算符左邊變量的值存入操做數棧。所以,這段程序運行的結果是9。
一、以上,咱們把自增自減運算符的題目總結爲3大類,不管是企業的面試題,仍是學校的考試題,關於自增自減運算符的題目基本均可以歸結到這3大類中。因此,仔細研究這3大類題目的規律,基本能夠保證相關題目不會難住你。
二、筆者爲確保對文中所提到的代碼都能作到正確解釋,對文中全部代碼均用javap命令查看了編譯後的字節碼。
三、文中例題中的代碼若是放到C語言環境下,執行結果會有不一樣,緣由是C語言對源碼的編譯和解析規則與Java語言略有不一樣,所以,千萬不要把本文的結論套用到C語言中。
四、雖然文章詳細分析了自增自減運算的各類狀況,但筆者自己很是反對代碼中出現相似於」 a += ++a + ++a;」這樣的代碼,由於這樣的代碼可讀性很是差,很容易形成誤解,因此,各位小夥伴在真實編程的時候,必定別怕麻煩,哪怕多寫幾行代碼,也要把代碼寫的可讀性強一些,儘可能不要讓人產生誤解和歧義。
五、爲方便初學者理解,文中並無出現編譯器、解析器之類的名詞。而文中所提到的」棧1」、 」棧2」、 」棧3」、 」棧4」這些操做數棧的名稱,也只是爲了方便讀者理解而起的名稱,並不是對應系統真實對各個操做數棧的命名。
但願本文可以幫助初學者深刻理解Java語言自增自減運算符。
如想系統學習Java編程,歡迎觀看我在本站的視頻課程。