溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C++程序入門之——賦值操作符

發(fā)布時(shí)間:2020-08-11 07:24:17 來(lái)源:ITPUB博客 閱讀:233 作者:千鋒Python唐小強(qiáng) 欄目:編程語(yǔ)言

賦值語(yǔ)句 
  前面已經(jīng)說(shuō)明,要訪問(wèn)內(nèi)存,就需要相應(yīng)的地址以表明訪問(wèn)哪塊內(nèi)存,而變量是一個(gè)映射,因此變量名就相當(dāng)于一個(gè)地址。對(duì)于內(nèi)存的操作,在一般情況下就只有讀取內(nèi)存中的數(shù)值和將數(shù)值寫入內(nèi)存(不考慮分配和釋放內(nèi)存),在C++中,為了將一數(shù)值寫入某變量對(duì)應(yīng)的地址所標(biāo)識(shí)的內(nèi)存中(出于簡(jiǎn)便,以后稱變量a對(duì)應(yīng)的地址為變量a的地址,而直接稱變量a的地址所標(biāo)識(shí)的內(nèi)存為變量a),只需先書寫變量名,后接“=”,再接欲寫入的數(shù)字以及分號(hào)。如下:

a = 10.0f; b = 34;


  由于接的是數(shù)字,因此就可以接表達(dá)式并由編譯器生成計(jì)算相應(yīng)表達(dá)式所需的代碼,也就可如下:

c = a / b * 120.4f;


  上句編譯器將會(huì)生成進(jìn)行除法和乘法計(jì)算的CPU指令,在計(jì)算完畢后(也就是求得表達(dá)式a / b * 120.4f的值了后),也會(huì)同時(shí)生成將計(jì)算結(jié)果放到變量c中去的CPU指令,這就是語(yǔ)句的基本作用(對(duì)于語(yǔ)句,在《C++從零開(kāi)始(六)》中會(huì)詳細(xì)說(shuō)明)。
  上面在書寫賦值語(yǔ)句時(shí),應(yīng)該確保此語(yǔ)句之前已經(jīng)將使用到的變量定義過(guò),這樣編譯器才能在生成賦值用的CPU指令時(shí)查找到相應(yīng)變量的地址,進(jìn)而完成CPU指令的生成。如上面的a和b,就需要在書寫上面語(yǔ)句前先書寫類似下面的變量定義:

float a; long b;


  直接書寫變量名也是一條語(yǔ)句,其導(dǎo)致編譯器生成一條讀取相應(yīng)變量的內(nèi)容的語(yǔ)句。 即可以如下書寫:a;
  上面將生成一條讀取內(nèi)存的語(yǔ)句,即使從內(nèi)存中讀出來(lái)的數(shù)字沒(méi)有任何應(yīng)用(當(dāng)然,如果編譯器開(kāi)了優(yōu)化選項(xiàng),則上面的語(yǔ)句將不會(huì)生成任何代碼)。從這一點(diǎn)以及上面的c = a / b * 120.4f;語(yǔ)句中,都可以看出一點(diǎn)——變量是可以返回?cái)?shù)字的。而變量返回的數(shù)字就是按照變量的類型來(lái)解釋變量對(duì)應(yīng)內(nèi)存中的內(nèi)容所得到的數(shù)字。這句話也許不是那么容易理解,在看過(guò)后面的類型轉(zhuǎn)換一節(jié)后應(yīng)該就可以理解了。


因此為了將數(shù)據(jù)寫入一塊內(nèi)存,使用賦值語(yǔ)句(即等號(hào));要讀取一塊內(nèi)存,書寫標(biāo)識(shí)內(nèi)存的變量名。所以就可以這樣書寫: a = a + 3;


假設(shè)a原來(lái)的值為1,則上面的賦值語(yǔ)句將a的值取出來(lái),加上3,得到結(jié)果4,將4再寫入a中去。由于C++使用“=”來(lái)代表賦值語(yǔ)句,很容易使人和數(shù)學(xué)中的等號(hào)混淆起來(lái),這點(diǎn)應(yīng)注意。
  而如上的float a;語(yǔ)句,當(dāng)還未對(duì)變量進(jìn)行任何賦值操作時(shí),a的值是什么?上帝才知道。當(dāng)時(shí)的a的內(nèi)容是什么(對(duì)于VC編譯器,在開(kāi)啟了調(diào)試選項(xiàng)時(shí),將會(huì)用0xCCCCCCCC填充這些未初始化內(nèi)存),就用IEEE的real*4格式來(lái)解釋它并得到相應(yīng)的一個(gè)數(shù)字,也就是a的值。因此應(yīng)在變量定義的時(shí)候就進(jìn)行賦值(但是會(huì)有性能上的影響,不過(guò)很?。猿跏蓟兞慷乐钩霈F(xiàn)莫名其妙的值,如:float a = 0.0f;。

C++程序入門之——賦值操作符

賦值操作符
  上面的a = a + 3;的意思就是讓a的值增加3。在C++中,對(duì)于這種情況給出了一種簡(jiǎn)寫方案,即前面的語(yǔ)句可以寫成:a += 3;。應(yīng)當(dāng)注意這兩條語(yǔ)句從邏輯上講都是使變量a的值增3,但是它們實(shí)際是有區(qū)別的,后者可以被編譯成優(yōu)化的代碼,因?yàn)槠湟馑际鞘鼓骋粔K內(nèi)存的值增加一定數(shù)量,而前者是將一個(gè)數(shù)字寫入到某塊內(nèi)存中。所以如果可能,應(yīng)盡量使用后者,即a += 3;。這種語(yǔ)句可以讓編譯器進(jìn)行一定的優(yōu)化(但由于現(xiàn)在的編譯器都非常智能,能夠發(fā)現(xiàn)a = a + 3;是對(duì)一塊內(nèi)存的增值操作而不是一塊內(nèi)存的賦值操作,因此上面兩條語(yǔ)句實(shí)際上可以認(rèn)為完全相同,僅僅只具有簡(jiǎn)寫的功能了)。
  對(duì)于上面的情況,也可以應(yīng)用在減法、乘法等二元非邏輯操作符(不是邏輯值操作符,即不能a &&= 3;)上,如:a *= 3; a -= 4; a |= 34; a >>= 3;等。
  除了上面的簡(jiǎn)寫外,C++還提供了一種簡(jiǎn)寫方式,即a++;,其邏輯上等同于a += 1;。同上,在電腦編程中,加一和減一是經(jīng)常用到的,因此CPU專門提供了兩條指令來(lái)進(jìn)行加一和減一操作(轉(zhuǎn)成匯編語(yǔ)言就是Inc和Dec),但速度比直接通過(guò)加法或減法指令來(lái)執(zhí)行要快得多。為此C++中也就提供了“++”和“—”操作符來(lái)對(duì)應(yīng)Inc和Dec。所以a++;雖然邏輯上和a = a + 1;等效,實(shí)際由于編譯器可能做出的優(yōu)化處理而不同,但還是如上,由于編譯器的智能化,其是有可能看出a = a + 1;可以編譯成Inc指令進(jìn)而即使沒(méi)有使用a++;卻也依然可以得到優(yōu)化的代碼,這樣a++;將只剩下簡(jiǎn)寫的意義而已。

應(yīng)當(dāng)注意一點(diǎn),a = 3;這句語(yǔ)句也將返回一個(gè)數(shù)字,也就是在a被賦完值后a的值。由于其可以返回?cái)?shù)字,按照《C++從零開(kāi)始(二)》中所說(shuō),“=”就屬于操作符,也就可以如下書寫:

c = 4 + ( a = 3 );


  之所以打括號(hào)是因?yàn)椤?”的優(yōu)先級(jí)較“+”低,而更常見(jiàn)和正常的應(yīng)用是:c = a = 3;
  應(yīng)該注意上面并不是將c和a賦值為3,而是在a被賦值為3后再將a賦值給c,雖然最后結(jié)果和c、a都賦值為3是一樣的,但不應(yīng)該這樣理解。由于a++;表示的就是a += 1;就是a = a + 1;,因此a++;也將返回一個(gè)數(shù)字。也由于這個(gè)原因,C++又提供了另一個(gè)簡(jiǎn)寫方式,++a;。
假設(shè)a為1,則a++;將先返回a的值,1,然后再將a的值加一;而++a;先將a的值加一,再返回a的值,2。而a—和—a也是如此,只不過(guò)是減一罷了。
  上面的變量a按照最上面的變量定義,是float類型的變量,對(duì)它使用++操作符并不能得到預(yù)想的優(yōu)化,因?yàn)閒loat類型是浮點(diǎn)類型,其是使用IEEE的real*4格式來(lái)表示數(shù)字的,而不是二進(jìn)制原碼或補(bǔ)碼,而前面提到的Inc和Dec指令都是出于二進(jìn)制的表示優(yōu)點(diǎn)來(lái)進(jìn)行快速增一和減一,所以如果對(duì)浮點(diǎn)類型的變量運(yùn)用“++”操作符,將完全只是簡(jiǎn)寫,沒(méi)有任何的優(yōu)化效果(當(dāng)然,如果CPU提供了新的指令集,如MMX等,以對(duì)real*4格式進(jìn)行快速增一和減一操作,且編譯器支持相應(yīng)指令集,則還是可以產(chǎn)生優(yōu)化效果的)。
賦值操作符的返回值
  在進(jìn)一步了解++a和a++的區(qū)別前,先來(lái)了解何謂操作符的計(jì)算(Evaluate)。操作符就是將給定的數(shù)字做一些處理,然后返回一個(gè)數(shù)字。而操作符的計(jì)算也就是執(zhí)行操作符的處理,并返回值。前面已經(jīng)知道,操作符是個(gè)符號(hào),其一側(cè)或兩側(cè)都可以接數(shù)字,也就是再接其他操作符,而又由于賦值操作符也屬于一種操作符,因此操作符的執(zhí)行順序變得相當(dāng)重要。
  對(duì)于a + b + c,將先執(zhí)行a + b,再執(zhí)行( a + b ) + c的操作。你可能覺(jué)得沒(méi)什么,那么如下,假設(shè)a之前為1:

c = ( a *= 2 ) + ( a += 3 );


  上句執(zhí)行后a為5。而c = ( a += 3 ) + ( a *= 2 );執(zhí)行后,a就是8了。那么c呢?結(jié)果可能會(huì)大大的出乎你的意料。前者的c為10,而后者的c為16。
  上面其實(shí)是一個(gè)障眼法,其中的“+”沒(méi)有任何意義,即之所以會(huì)從左向右執(zhí)行并不是因?yàn)椤?”的緣故,而是因?yàn)? a *= 2 )和( a += 3 )的優(yōu)先級(jí)相同,而按照“()”的計(jì)算順序,是從左向右來(lái)計(jì)算的。但為什么c的值不是預(yù)想的2 + 5和4 + 8呢?因?yàn)橘x值操作符的返回值的關(guān)系。
  賦值操作符返回的數(shù)字不是變量的值,而是變量對(duì)應(yīng)的地址。這很重要。前面說(shuō)過(guò),光寫一個(gè)變量名就會(huì)返回相應(yīng)變量的值,那是因?yàn)樽兞渴且粋€(gè)映射,變量名就等同于一個(gè)地址。C++中將數(shù)字看作一個(gè)很特殊的操作符,即任何一個(gè)數(shù)字都是一個(gè)操作符。而地址就和長(zhǎng)整型、單精度浮點(diǎn)數(shù)這類一樣,是數(shù)字的一種類型。當(dāng)一個(gè)數(shù)字是地址類型時(shí),作為操作符,其沒(méi)有要操作的數(shù)字,僅僅返回將此數(shù)字看作地址而標(biāo)識(shí)的內(nèi)存中的內(nèi)容(用這個(gè)地址的類型來(lái)解釋)。地址可以通過(guò)多種途徑得到,如上面光寫一個(gè)變量名就可以得到其對(duì)應(yīng)的地址,而得到的地址的類型也就是相應(yīng)的變量的類型。如果這句話不能理解,在看過(guò)下面的類型轉(zhuǎn)換一節(jié)后應(yīng)該就能了解了。
  所以前面的c = ( a += 3 ) + ( a *= 2 );,由于“()”的參與改變了優(yōu)先級(jí)而先執(zhí)行了兩個(gè)賦值操作符,然后兩個(gè)賦值操作符都返回a的地址,然后計(jì)算“+”的值,分別計(jì)算兩邊的數(shù)字——a的地址(a的地址也是一個(gè)操作符),也就是已經(jīng)執(zhí)行過(guò)兩次賦值操作的a的值,得8,故最后的c為16。而另一個(gè)也由于同樣的原因使得c為10。
   現(xiàn)在考慮操作符的計(jì)算順序。當(dāng)同時(shí)出現(xiàn)了幾個(gè)優(yōu)先級(jí)相同的操作符時(shí),不同的操作符具有不同的計(jì)算順序。前面的“()”以及“-”、“*”等這類二元操作符的計(jì)算順序都是從左向右計(jì)算,而“!”、負(fù)號(hào)“-”等前面介紹過(guò)的一元操作符都是從右向左計(jì)算的,如:!-!!a;,假設(shè)a為3。先計(jì)算從左朝右數(shù)第三個(gè)“!”的值,導(dǎo)致計(jì)算a的地址的值,得3;然后邏輯取反得0,接著再計(jì)算第二個(gè)“!”的值,邏輯取反后得1,再計(jì)算負(fù)號(hào)“-”的值,得-1,最后計(jì)算第一個(gè)“!”的值,得0。

賦值操作符都是從右向左計(jì)算的,除了后綴“++”和后綴“—”(即上面的a++和a--)。因此上面的c = a = 3;,因?yàn)閮蓚€(gè)“=”優(yōu)先級(jí)相同,從右向左計(jì)算,先計(jì)算a = 3的值,返回a對(duì)應(yīng)的地址,然后計(jì)算返回的地址而得到值3,再計(jì)算c = ( a = 3 ),將3寫入c。而不是從左向右計(jì)算,即先計(jì)算c = a,返回c的地址,然后再計(jì)算第二個(gè)“=”,將3寫入c,這樣a就沒(méi)有被賦值而出現(xiàn)問(wèn)題。又:

a = 1; c = 2; c *= a += 4;


  由于“*=”和“+=”的優(yōu)先級(jí)相同,從右向左計(jì)算先計(jì)算a += 4,得a為5,然后返回a的地址,再計(jì)算a的地址得a的值5,計(jì)算“*=”以使得c的值為10。
  因此按照前面所說(shuō),++a將返回a的地址,而a++也因?yàn)槭琴x值操作符而必須返回一個(gè)地址,但很明顯地不能是a的地址了,因此編譯器將編寫代碼以從棧中分配一塊和a同樣大小的內(nèi)存,并將a的值復(fù)制到這塊臨時(shí)內(nèi)存中,然后返回這塊臨時(shí)內(nèi)存的地址。由于這塊臨時(shí)內(nèi)存是因?yàn)榫幾g器的需要而分配的,與程序員完全沒(méi)有關(guān)系,因此程序員是不應(yīng)該也不能寫這塊臨時(shí)內(nèi)存的(因?yàn)榫幾g器負(fù)責(zé)編譯代碼,如果程序員欲訪問(wèn)這塊內(nèi)存,編譯器將報(bào)錯(cuò)),但可以讀取它的值,這也是返回地址的主要目的。 所以如下的語(yǔ)句沒(méi)有問(wèn)題:

( ++a ) = a += 34;


  但( a++ ) = a += 34;就會(huì)在編譯時(shí)報(bào)錯(cuò),因?yàn)閍++返回的地址所標(biāo)識(shí)的內(nèi)存只能由編譯器負(fù)責(zé)處理,程序員只能獲得其值而已。
a++的意思是先返回a的值,也就是上面說(shuō)的臨時(shí)內(nèi)存的地址,然后再將變量的值加一。如果同時(shí)出現(xiàn)多個(gè)a++,那么每個(gè)a++都需要分配一塊臨時(shí)內(nèi)存(注意前面c = ( a += 3 ) + ( a *= 2 );的說(shuō)明),那么將有點(diǎn)糟糕,而且a++的意思是先返回a的值,那么到底是什么時(shí)候的a的值呢?在VC中,當(dāng)表達(dá)式中出現(xiàn)后綴“++”或后綴“—”時(shí),只分配一塊臨時(shí)內(nèi)存,然后所有的后綴“++”或后綴“—”都返回這個(gè)臨時(shí)內(nèi)存的地址,然后在所有的可以計(jì)算的其他操作符的值計(jì)算完畢后,再將對(duì)應(yīng)變量的值寫入到臨時(shí)內(nèi)存中,計(jì)算表達(dá)式的值,最后將對(duì)應(yīng)變量的值加一或減一。
因此:a = 1; c = ( a++ ) + ( a++ );執(zhí)行后,c的值為2,而a的值為3。而如下:

a = 1; b = 1; c = ( ++a ) + ( a++ ) + ( b *= a++ ) + ( a *= 2 ) + ( a *= a++ );


  執(zhí)行時(shí),先分配臨時(shí)內(nèi)存,然后由于5個(gè)“()”,其計(jì)算順序是從左向右,
  計(jì)算++a的值,返回增一后的a的地址,a的值為2
  計(jì)算a++的值,返回臨時(shí)內(nèi)存的地址,a的值仍為2
  計(jì)算b *= a++中的a++,返回臨時(shí)內(nèi)存的地址,a的值仍為2
  計(jì)算b *= a++中的“*=”,將a的值寫入臨時(shí)內(nèi)存,計(jì)算得b的值為2,返回b的地址
  計(jì)算a *= 2的值,返回a的地址,a的值為4
  計(jì)算a *= a++中的a++,返回臨時(shí)內(nèi)存的地址,a的值仍為4
  計(jì)算a *= a++中的“*=”,將a的值寫入臨時(shí)內(nèi)存,返回a的地址,a的值為16
  計(jì)算剩下的“+”,為了進(jìn)行計(jì)算,將a的值寫入臨時(shí)內(nèi)存,得值16 + 16 + 2 + 16 + 16為66,寫入c中
  計(jì)算三個(gè)a++欠下的加一,a最后變?yōu)?9。
  上面說(shuō)了那么多,無(wú)非只是想告誡你——在表達(dá)式中運(yùn)用賦值操作符是不被推崇的。因?yàn)槠洳环掀匠5臄?shù)學(xué)表達(dá)式的習(xí)慣,且計(jì)算順序很容易搞混。如果有多個(gè)“++”操作符,最好還是將表達(dá)式分開(kāi),否則很容易導(dǎo)致錯(cuò)誤的計(jì)算順序而計(jì)算錯(cuò)誤。并且導(dǎo)致計(jì)算順序混亂的還不止上面的a++就完了,為了讓你更加地重視前面的紅字,下面將介紹更令人火大的東西,如果你已經(jīng)同意上面的紅字,則下面這一節(jié)完全可以跳過(guò),其對(duì)編程來(lái)講可以認(rèn)為根本沒(méi)有任何意義(要不是為了寫這篇文章,我都不知道它的存在)。
序列點(diǎn)(Sequence Point)和附加效果(Side Effect)
  在計(jì)算c = a++時(shí),當(dāng)c的值計(jì)算(Evaluate)出來(lái)時(shí),a的值也增加了一,a的值加一就是計(jì)算前面表達(dá)式的附加效果。有什么問(wèn)題?它可能影響表達(dá)式的計(jì)算結(jié)果。

對(duì)于a = 0; b = 1; ( a *= 2 ) && ( b += 2 );,由于兩個(gè)“()”優(yōu)先級(jí)相同,從左向右計(jì)算,計(jì)算“*=”而返回a的地址,再計(jì)算“+=”而返回b的地址,最后由于a的值為0而返回邏輯假。很正常,但效率低了點(diǎn)。
  如果“&&”左邊的數(shù)字已經(jīng)是0了,則不再需要計(jì)算右邊的式子。同樣,如果“||”左邊的數(shù)字已經(jīng)非零了,也不需要再計(jì)算右邊的數(shù)字。因?yàn)椤?amp;&”和“||”都是數(shù)學(xué)上的,數(shù)學(xué)上不管先計(jì)算加號(hào)左邊的值還是右邊的值,結(jié)果都不會(huì)改變,因此“&&”和“||”才會(huì)做剛才的解釋。這也是C++保證的,既滿足數(shù)學(xué)的定義,又能提供優(yōu)化的途徑(“&&”和“||”右邊的數(shù)字不用計(jì)算了)。
  因此上面的式子就會(huì)被解釋成——如果a在自乘了2后的值為0,則b就不用再自增2了。這很明顯地違背了我們的初衷,認(rèn)為b無(wú)論如何都會(huì)被自增2的。但是C++卻這樣保證,不僅僅是因?yàn)閿?shù)學(xué)的定義,還由于代碼生成的優(yōu)化。但是按照操作符的優(yōu)先級(jí)進(jìn)行計(jì)算,上面的b += 2依舊會(huì)被執(zhí)行的(這也正是我們會(huì)書寫上面代碼的原因)。為了實(shí)現(xiàn)當(dāng)a為0時(shí)b += 2不會(huì)被計(jì)算,C++提出了序列點(diǎn)的概念。
  序列點(diǎn)是一些特殊位置,由C++強(qiáng)行定義(C++并未給出序列點(diǎn)的定義,因此不同的編譯器可能給出不同的序列點(diǎn)定義,VC是按照C語(yǔ)言定義的序列點(diǎn))。當(dāng)在進(jìn)行操作符的計(jì)算時(shí),如果遇到序列點(diǎn),則序列點(diǎn)處的值必須被優(yōu)先計(jì)算,以保證一些特殊用途,如上面的保證當(dāng)a為0時(shí)不計(jì)算b += 2,并且序列點(diǎn)相關(guān)的操作符(如前面的“&&”和“||”)也將被計(jì)算完畢,然后才恢復(fù)正常的計(jì)算。
  “&&”的左邊數(shù)字的計(jì)算就是一個(gè)序列點(diǎn),而“||”的左邊數(shù)字的計(jì)算也是。C++定義了多個(gè)序列點(diǎn),包括條件語(yǔ)句、函數(shù)參數(shù)等條件下的表達(dá)式計(jì)算,在此,不需要具體了解有哪些序列點(diǎn),只需要知道由于序列點(diǎn)的存在而可能導(dǎo)致賦值操作符的計(jì)算出乎意料。下面就來(lái)分析一個(gè)例子:

a = 0; b = 1; ( a *= 2 ) && ( b += ++a );


  按照優(yōu)先級(jí)的順序,編譯器發(fā)現(xiàn)要先計(jì)算a *= 2,再計(jì)算++a,接著“+=”,最后計(jì)算“&&”。然后編譯器發(fā)現(xiàn)這個(gè)計(jì)算過(guò)程中,出現(xiàn)了“&&”左邊的數(shù)字這個(gè)序列點(diǎn),其要保證被優(yōu)先計(jì)算,這樣就有可能不用計(jì)算b += ++a了。所以編譯器先計(jì)算“&&”的數(shù)字,通過(guò)上面的計(jì)算過(guò)程,編譯器發(fā)現(xiàn)就要計(jì)算a *= 2才能得到“&&”左邊的數(shù)字,因此將先計(jì)算a *= 2,返回a的地址,然后計(jì)算“&&”左邊的數(shù)字,得a的值為0,因此就不計(jì)算b += ++a了。而不是最開(kāi)始想象的由于優(yōu)先級(jí)的關(guān)系先將a加一后再進(jìn)行a的計(jì)算,以返回1。所以上面計(jì)算完畢后,a為0,b為1,返回0,表示邏輯假。
  因此序列點(diǎn)的出現(xiàn)是為了保證一些特殊規(guī)則的出現(xiàn),如上面的“&&”和“||”。再考慮“,”操作符,其操作是計(jì)算兩邊的值,然后返回右邊的數(shù)字,即:a, b + 3將返回b + 3的值,但是a依舊會(huì)被計(jì)算。由于“,”的優(yōu)先級(jí)是最低的(但高于前面提到的“數(shù)字”操作符),因此如果a = 3, 4;,那么a將為3而不是4,因?yàn)橄扔?jì)算“=”,返回a的地址后再計(jì)算“,”。又:

a = 1; b = 0; b = ( a += 2 ) + ( ( a *= 2, b = a - 1 ) && ( c = a ) );


  由于“&&”左邊數(shù)字是一個(gè)序列點(diǎn),因此先計(jì)算a *= 2, b的值,但根據(jù)“,”的返回值定義,其只返回右邊的數(shù)字,因此不計(jì)算a *= 2而直接計(jì)算b = a – 1得0,“&&”就返回了,但是a *= 2就沒(méi)有被計(jì)算而導(dǎo)致a的值依舊為1,這違背了“,”的定義。為了消除這一點(diǎn)(當(dāng)然可能還有其他應(yīng)用“,”的情況),C++也將“,”的左邊數(shù)字定為了序列點(diǎn),即一定會(huì)優(yōu)先執(zhí)行“,”左邊的數(shù)字以保證“,”的定義——計(jì)算兩邊的數(shù)字。所以上面就由于“,”左邊數(shù)字這個(gè)序列點(diǎn)而導(dǎo)致a *= 2被優(yōu)先執(zhí)行,并導(dǎo)致b為1,因此由于“&&”是序列點(diǎn)且其左邊數(shù)字非零而必須計(jì)算完右邊數(shù)字后才恢復(fù)正常優(yōu)先級(jí),而計(jì)算c = a,得2,最后才恢復(fù)正常優(yōu)先級(jí)順序,執(zhí)行a += 2和“+”。結(jié)果就a為4,c為2,b為5。

所以前面的a = 3, 4;其實(shí)就應(yīng)該是編譯器先發(fā)現(xiàn)“,”這個(gè)序列點(diǎn),而發(fā)現(xiàn)要計(jì)算“,”左邊的值,必須先計(jì)算出a = 3,因此才先計(jì)算a = 3以至于感覺(jué)序列點(diǎn)好像沒(méi)有發(fā)生作用。下面的式子請(qǐng)自行分析,執(zhí)行后a為4,但如果將其中的“,”換成“&&”,a為2。

a = 1; b = ( a *= 2 ) + ( ( a *= 3 ), ( a -= 2 ) );


  如果上面你看得很暈,沒(méi)關(guān)系,因?yàn)樯厦娴膬?nèi)容根本可以認(rèn)為毫無(wú)意義,寫在這里也只是為了進(jìn)一步向你證明,在表達(dá)式中運(yùn)用賦值運(yùn)算符是不好的,即使它可能讓你寫出看起來(lái)簡(jiǎn)練的語(yǔ)句,但它也使代碼的可維護(hù)性降低。 
   類型轉(zhuǎn)換
  數(shù)字可以是浮點(diǎn)數(shù)或是整型數(shù)或其他,也就是說(shuō)數(shù)字是具有類型的。注意《C++從零開(kāi)始(三)》中對(duì)類型的解釋,類型只是說(shuō)明如何解釋狀態(tài),而在前面已經(jīng)說(shuō)過(guò),出于方便,使用二進(jìn)制數(shù)來(lái)表示狀態(tài),因此可以說(shuō)類型是用于告訴編譯器如何解釋二進(jìn)制數(shù)的。
  所以,一個(gè)長(zhǎng)整型數(shù)字是告訴編譯器將得到的二進(jìn)制數(shù)表示的狀態(tài)按照二進(jìn)制補(bǔ)碼的格式來(lái)解釋以得到一個(gè)數(shù)值,而一個(gè)單精度浮點(diǎn)數(shù)就是告訴編譯器將得到的二進(jìn)制數(shù)表示的狀態(tài)按照IEEE的real*4的格式來(lái)解釋以得到一個(gè)是小數(shù)的數(shù)值。很明顯,同樣的二進(jìn)制數(shù)表示的狀態(tài),按照不同的類型進(jìn)行解釋將得到不同的數(shù)值,那么編譯器如何知道應(yīng)該使用什么類型來(lái)進(jìn)行二進(jìn)制數(shù)的解釋?
  前面已經(jīng)說(shuō)過(guò),數(shù)字是一種很特殊的操作符,其沒(méi)有操作數(shù),僅僅返回由其類型而定的二進(jìn)制數(shù)表示的狀態(tài)(以后為了方便,將“二進(jìn)制數(shù)表示的狀態(tài)”稱作“二進(jìn)制數(shù)”)。而操作符就是執(zhí)行指令并返回?cái)?shù)字,因此所有的操作符到最后一定執(zhí)行的是返回一個(gè)二進(jìn)制數(shù)。這點(diǎn)很重要,對(duì)于后面指針的理解有著重要的意義。 
  先看15;,這是一條語(yǔ)句,因?yàn)?5是一個(gè)數(shù)字。所以15被認(rèn)為是char類型的數(shù)字(因?yàn)槠湫∮?28,沒(méi)超出char的表示范圍),將返回一個(gè)8位長(zhǎng)的二進(jìn)制數(shù),此二進(jìn)制數(shù)按照補(bǔ)碼格式編寫,為00001111。
  再看15.0f,同上,其由于接了“f”這個(gè)后綴而被認(rèn)為是float類型的數(shù)字,將返回一個(gè)32位長(zhǎng)的二進(jìn)制數(shù),此二進(jìn)制數(shù)按照IEEE的real*4格式編寫,為1000001011100000000000000000000。
  雖然上面15和15.0f的數(shù)值相等,但由于是不同的類型導(dǎo)致了使用不同的格式來(lái)表示,甚至連表示用的二進(jìn)制數(shù)的長(zhǎng)度都不相同。因此如果書寫15.0f == 15;將返回0,表示邏輯假。但實(shí)際卻返回1,為什么?
  上面既然15和15.0f被表示成完全不同的兩個(gè)二進(jìn)制數(shù),但我們又認(rèn)為15和15.0f是相等的,但它們的二進(jìn)制表示不同,怎么辦?將表示15.0f的二進(jìn)制數(shù)用IEEE的real*4格式解釋出15這個(gè)數(shù)值,然后再將其按8位二進(jìn)制補(bǔ)碼格式編寫出二進(jìn)制數(shù),再與原來(lái)的表示15的二進(jìn)制數(shù)比較。
  為了實(shí)現(xiàn)上面的操作,C++提供了類型轉(zhuǎn)換操作符——“()”。其看起來(lái)和括號(hào)操作符一樣,但是格式不同:(<類型名>)<數(shù)字>或<類型名>(<數(shù)字>)。
  上面類型轉(zhuǎn)換操作符的<類型名>不是數(shù)字,因此其將不會(huì)被操作,而是作為一個(gè)參數(shù)來(lái)控制其如何操作后面的<數(shù)字>。<類型名>是一個(gè)標(biāo)識(shí)符,其唯一標(biāo)識(shí)一個(gè)類型,如char、float等。類型轉(zhuǎn)換操作符的返回值就如其名字所示,將<數(shù)字>按照<類型名>標(biāo)識(shí)的類型來(lái)解釋,返回類型是<類型名>的數(shù)字。因此,上面的例子我們就需要如下編寫:15 == ( char )15.0f;,現(xiàn)在其就可以返回1,表示邏輯真了。但是即使不寫( char ),前面的語(yǔ)句也返回1。這是編譯器出于方便的緣故而幫我們?cè)?5前添加了( float ),所以依然返回1。這被稱作隱式類型轉(zhuǎn)換,在后面說(shuō)明類的時(shí)候,還將提到它。
  某個(gè)類型可以完全代替另一個(gè)類型時(shí),編譯器就會(huì)進(jìn)行上面的隱式類型轉(zhuǎn)換,自動(dòng)添加類型轉(zhuǎn)換操作符。如:char只能表示-128到127的整數(shù),而float很明顯地能夠表示這些數(shù)字,因此編譯器進(jìn)行了隱式類型轉(zhuǎn)換。應(yīng)當(dāng)注意,這個(gè)隱式轉(zhuǎn)換是由操作符要求的,即前面的“==”要求兩面的數(shù)字類型一致,結(jié)果發(fā)現(xiàn)兩邊不同,結(jié)果編譯器將char轉(zhuǎn)成float,然后再執(zhí)行“==”的操作。注意:在這種情況下,編譯器總是將較差的類型(如前面的char)轉(zhuǎn)成較好的類型(如前面的float),以保證不會(huì)發(fā)生數(shù)值截?cái)鄦?wèn)題。如:-41 == 3543;,左邊是char,右邊是short,由于short相對(duì)于char來(lái)顯得更優(yōu)(short能完全替代char),故實(shí)際為:( short )-41 == 3543;,返回0。而如果是-41 == ( char )3543;,由于char不能表示3543,則3543以補(bǔ)碼轉(zhuǎn)成二進(jìn)制數(shù)0000110111010111,然后取其低8位,而導(dǎo)致高8位的00001101被丟棄,此被稱為截?cái)唷=Y(jié)果( char )3543的返回值就是類型為char的二進(jìn)制數(shù)11010111,為-41,結(jié)果-41 == ( char )3543;的返回值將為1,表示邏輯真,很明顯地錯(cuò)誤。因此前面的15 == 15.0f;實(shí)際將為( float )15 == 15.0f;。

注意前面之所以會(huì)朝好的方向發(fā)展(即char轉(zhuǎn)成float),完全是因?yàn)椤?=”的緣故,其要求這么做。下面考慮“=”:short b = 3543; char a = b;。因?yàn)閎的值是short類型,而“=”的要求就是一定要將“=”右邊的數(shù)字轉(zhuǎn)成和左邊一樣,這樣才能進(jìn)行正確的內(nèi)存的寫入(簡(jiǎn)單地將右邊數(shù)字返回的二進(jìn)制數(shù)復(fù)制到左邊的地址所表示的內(nèi)存中)。因此a將為-41。但是上面是編譯器按照“=”的要求自行進(jìn)行了隱式轉(zhuǎn)換,可能是由于程序員的疏忽而沒(méi)有發(fā)現(xiàn)這個(gè)錯(cuò)誤(以為b的值一定在-128到127的范圍內(nèi)),因此編譯器將對(duì)上面的情況給出一個(gè)警告,說(shuō)b的值可能被截?cái)唷榱讼幾g器的疑慮,如下:char a = ( char )b;。這樣稱為顯示類型轉(zhuǎn)換,其告訴編譯器——“我知道可能發(fā)生數(shù)據(jù)截?cái)啵俏冶WC不會(huì)截?cái)唷?。因此編譯器將不再發(fā)出警告。但是如下:char a = ( char )3543;,由于編譯器可以肯定3543一定會(huì)被截?cái)喽鴮?dǎo)致錯(cuò)誤的返回值,因此編譯器將給出警告,說(shuō)明3543將被截?cái)啵还芮懊娴念愋娃D(zhuǎn)換操作符是否存在。
  現(xiàn)在應(yīng)該可以推出——15 + 15.0f;返回的是一個(gè)float類型的數(shù)字。因此如果如下:char a = 15 + 15.0f;,編譯器將發(fā)出警告,說(shuō)數(shù)據(jù)可能被截?cái)唷R虼烁某扇缦拢篶har a = ( char )15 + 15.0f;,但類型轉(zhuǎn)換操作符“()”的優(yōu)先級(jí)比“+”高,結(jié)果就是15先被轉(zhuǎn)換為char然后再由于“+”的要求而被隱式轉(zhuǎn)成float,最后返回float給“=”而導(dǎo)致編譯器依舊發(fā)出警告。為此,就需要提高“+”的優(yōu)先級(jí),如下:char a = ( char )( 15 + 15.0f );就沒(méi)事了(或char( 15 + 15.0f )),其表示我保證15 + 15.0f不會(huì)導(dǎo)致數(shù)據(jù)截?cái)唷?
  應(yīng)該注意類型轉(zhuǎn)換操作符“()”和前綴“++”、“!”、負(fù)號(hào)“-”等的優(yōu)先級(jí)一樣,并且是從右向左計(jì)算的,因此( char )-34;將會(huì)先計(jì)算-34的值,然后再計(jì)算( char )的值,這也正好符合人的習(xí)慣。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI