| 導購 | 订阅 | 在线投稿
分享
 
 
當前位置: 王朝網路 >> c/c++ >> C++程序設計從零開始之賦值操作符
 

C++程序設計從零開始之賦值操作符

2008-06-01 02:05:58  編輯來源:互聯網  简体版  手機版  評論  字體: ||
 
 
  賦值語句

  前面已經說明,要訪問內存,就需要相應的地址以表明訪問哪塊內存,而變量是一個映射,因此變量名就相當于一個地址。對于內存的操作,在一般情況下就只有讀取內存中的數值和將數值寫入內存(不考慮分配和釋放內存),在C++中,爲了將一數值寫入某變量對應的地址所標識的內存中(出于簡便,以後稱變量a對應的地址爲變量a的地址,而直接稱變量a的地址所標識的內存爲變量a),只需先書寫變量名,後接「=」,再接欲寫入的數字以及分號。如下:

  

  a = 10.0f; b = 34;

  由于接的是數字,因此就可以接表達式並由編譯器生成計算相應表達式所需的代碼,也就可如下:

  

  c = a / b * 120.4f;

  上句編譯器將會生成進行除法和乘法計算的CPU指令,在計算完畢後(也就是求得表達式a / b * 120.4f的值了後),也會同時生成將計算結果放到變量c中去的CPU指令,這就是語句的基本作用(對于語句,在《C++從零開始(六)》中會具體說明)。

  上面在書寫賦值語句時,應該確保此語句之前已經將使用到的變量定義過,這樣編譯器才能在生成賦值用的CPU指令時查找到相應變量的地址,進而完成CPU指令的生成。如上面的a和b,就需要在書寫上面語句前先書寫類似下面的變量定義:

  

  float a; long b;

  直接書寫變量名也是一條語句,其導致編譯器生成一條讀取相應變量的內容的語句。即可以如下書寫:

  a;

  上面將生成一條讀取內存的語句,即使從內存中讀出來的數字沒有任何應用(當然,假如編譯器開了優化選項,則上面的語句將不會生成任何代碼)。從這一點以及上面的c = a / b * 120.4f;語句中,都可以看出一點——變量是可以返回數字的。而變量返回的數字就是按照變量的類型來解釋變量對應內存中的內容所得到的數字。這句話也許不是那麽輕易理解,在看過後面的類型轉換一節後應該就可以理解了。

  因此爲了將數據寫入一塊內存,使用賦值語句(即等號);要讀取一塊內存,書寫標識內存的變量名。所以就可以這樣書寫:a = a + 3;

  假設a原來的值爲1,則上面的賦值語句將a的值取出來,加上3,得到結果4,將4再寫入a中去。由于C++使用「=」來代表賦值語句,很輕易使人和數學中的等號混淆起來,這點應注重。

  而如上的float a;語句,當還未對變量進行任何賦值操作時,a的值是什麽?上帝才知道。當時的a的內容是什麽(對于VC編譯器,在開啓了調試選項時,將會用0xCCCCCCCC填充這些未初始化內存),就用IEEE的real*4格式來解釋它並得到相應的一個數字,也就是a的值。因此應在變量定義的時候就進行賦值(但是會有性能上的影響,不過很小),以初始化變量而防止出現莫名其妙的值,如:float a = 0.0f;。

  賦值操作符

  上面的a = a + 3;的意思就是讓a的值增加3。在C++中,對于這種情況給出了一種簡寫方案,即前面的語句可以寫成:a += 3;。應當注重這兩條語句從邏輯上講都是使變量a的值增3,但是它們實際是有區別的,後者可以被編譯成優化的代碼,因爲其意思是使某一塊內存的值增加一定數量,而前者是將一個數字寫入到某塊內存中。所以假如可能,應盡量使用後者,即a += 3;。這種語句可以讓編譯器進行一定的優化(但由于現在的編譯器都非常智能,能夠發現a = a + 3;是對一塊內存的增值操作而不是一塊內存的賦值操作,因此上面兩條語句實際上可以認爲完全相同,僅僅只具有簡寫的功能了)。

  對于上面的情況,也可以應用在減法、乘法等二元非邏輯操作符(不是邏輯值操作符,即不能a &&= 3;)上,如:a *= 3; a -= 4; a = 34; a >>= 3;等。

  除了上面的簡寫外,C++還提供了一種簡寫方式,即a++;,其邏輯上等同于a += 1;。同上,在電腦編程中,加一和減一是經常用到的,因此CPU專門提供了兩條指令來進行加一和減一操作(轉成彙編語言就是Inc和Dec),但速度比直接通過加法或減法指令來執行要快得多。爲此C++中也就提供了「++」和「—」操作符來對應Inc和Dec。所以a++;雖然邏輯上和a = a + 1;等效,實際由于編譯器可能做出的優化處理而不同,但還是如上,由于編譯器的智能化,其是有可能看出a = a + 1;可以編譯成Inc指令進而即使沒有使用a++;卻也依然可以得到優化的代碼,這樣a++;將只剩下簡寫的意義而已。

  

  

  

  

  

   QQRead.com 推出數據恢複指南教程 數據恢複指南教程

  

  

   數據恢複故障解析

   常用數據恢複方案

   硬盤數據恢複教程

  

  

   數據保護方法

   數據恢複軟件

   專業數據恢複服務指南

  

  

  

  

  

  應當注重一點,a = 3;這句語句也將返回一個數字,也就是在a被賦完值後a的值。由于其可以返回數字,按照《C++從零開始(二)》中所說,「=」就屬于操作符,也就可以如下書寫:

  

  c = 4 + ( a = 3 );

  之所以打括號是因爲「=」的優先級較「+」低,而更常見和正常的應用是:c = a = 3;

  應該注重上面並不是將c和a賦值爲3,而是在a被賦值爲3後再將a賦值給c,雖然最後結果和c、a都賦值爲3是一樣的,但不應該這樣理解。由于a++;表示的就是a += 1;就是a = a + 1;,因此a++;也將返回一個數字。也由于這個原因,C++又提供了另一個簡寫方式,++a;。

  假設a爲1,則a++;將先返回a的值,1,然後再將a的值加一;而++a;先將a的值加一,再返回a的值,2。而a—和—a也是如此,只不過是減一罷了。

  上面的變量a按照最上面的變量定義,是float類型的變量,對它使用++操作符並不能得到預想的優化,因爲float類型是浮點類型,其是使用IEEE的real*4格式來表示數字的,而不是二進制原碼或補碼,而前面提到的Inc和Dec指令都是出于二進制的表示優點來進行快速增一和減一,所以假如對浮點類型的變量運用「++」操作符,將完全只是簡寫,沒有任何的優化效果(當然,假如CPU提供了新的指令集,如MMX等,以對real*4格式進行快速增一和減一操作,且編譯器支持相應指令集,則還是可以産生優化效果的)。

  賦值操作符的返回值

  在進一步了解++a和a++的區別前,先來了解何謂操作符的計算(Evaluate)。操作符就是將給定的數字做一些處理,然後返回一個數字。而操作符的計算也就是執行操作符的處理,並返回值。前面已經知道,操作符是個符號,其一側或兩側都可以接數字,也就是再接其他操作符,而又由于賦值操作符也屬于一種操作符,因此操作符的執行順序變得相當重要。

  對于a + b + c,將先執行a + b,再執行( a + b ) + c的操作。你可能覺得沒什麽,那麽如下,假設a之前爲1:

  

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

  上句執行後a爲5。而c = ( a += 3 ) + ( a *= 2 );執行後,a就是8了。那麽c呢?結果可能會大大的出乎你的意料。前者的c爲10,而後者的c爲16。

  上面其實是一個障眼法,其中的「+」沒有任何意義,即之所以會從左向右執行並不是因爲「+」的緣故,而是因爲( a *= 2 )和( a += 3 )的優先級相同,而按照「()」的計算順序,是從左向右來計算的。但爲什麽c的值不是預想的2 + 5和4 + 8呢?因爲賦值操作符的返回值的關系。

  賦值操作符返回的數字不是變量的值,而是變量對應的地址。這很重要。前面說過,光寫一個變量名就會返回相應變量的值,那是因爲變量是一個映射,變量名就等同于一個地址。C++中將數字看作一個很非凡的操作符,即任何一個數字都是一個操作符。而地址就和長整型、單精度浮點數這類一樣,是數字的一種類型。當一個數字是地址類型時,作爲操作符,其沒有要操作的數字,僅僅返回將此數字看作地址而標識的內存中的內容(用這個地址的類型來解釋)。地址可以通過多種途徑得到,如上面光寫一個變量名就可以得到其對應的地址,而得到的地址的類型也就是相應的變量的類型。假如這句話不能理解,在看過下面的類型轉換一節後應該就能了解了。

  所以前面的c = ( a += 3 ) + ( a *= 2 );,由于「()」的參與改變了優先級而先執行了兩個賦值操作符,然後兩個賦值操作符都返回a的地址,然後計算「+」的值,分別計算兩邊的數字——a的地址(a的地址也是一個操作符),也就是已經執行過兩次賦值操作的a的值,得8,故最後的c爲16。而另一個也由于同樣的原因使得c爲10。

  現在考慮操作符的計算順序。當同時出現了幾個優先級相同的操作符時,不同的操作符具有不同的計算順序。前面的「()」以及「-」、「*」等這類二元操作符的計算順序都是從左向右計算,而「!」、負號「-」等前面介紹過的一元操作符都是從右向左計算的,如:!-!!a;,假設a爲3。先計算從左朝右數第三個「!」的值,導致計算a的地址的值,得3;然後邏輯取反得0,接著再計算第二個「!」的值,邏輯取反後得1,再計算負號「-」的值,得-1,最後計算第一個「!」的值,得0。

  賦值操作符都是從右向左計算的,除了後綴「++」和後綴「—」(即上面的a++和a--)。因此上面的c = a = 3;,因爲兩個「=」優先級相同,從右向左計算,先計算a = 3的值,返回a對應的地址,然後計算返回的地址而得到值3,再計算c = ( a = 3 ),將3寫入c。而不是從左向右計算,即先計算c = a,返回c的地址,然後再計算第二個「=」,將3寫入c,這樣a就沒有被賦值而出現問題。又:

  

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

  由于「*=」和「+=」的優先級相同,從右向左計算先計算a += 4,得a爲5,然後返回a的地址,再計算a的地址得a的值5,計算「*=」以使得c的值爲10。

  因此按照前面所說,++a將返回a的地址,而a++也因爲是賦值操作符而必須返回一個地址,但很明顯地不能是a的地址了,因此編譯器將編寫代碼以從棧中分配一塊和a同樣大小的內存,並將a的值複制到這塊臨時內存中,然後返回這塊臨時內存的地址。由于這塊臨時內存是因爲編譯器的需要而分配的,與程序員完全沒有關系,因此程序員是不應該也不能寫這塊臨時內存的(因爲編譯器負責編譯代碼,假如程序員欲訪問這塊內存,編譯器將報錯),但可以讀取它的值,這也是返回地址的主要目的。所以如下的語句沒有問題:

  

  ( ++a ) = a += 34;

  

  

  但( a++ ) = a += 34;就會在編譯時報錯,因爲a++返回的地址所標識的內存只能由編譯器負責處理,程序員只能獲得其值而已。

  a++的意思是先返回a的值,也就是上面說的臨時內存的地址,然後再將變量的值加一。假如同時出現多個a++,那麽每個a++都需要分配一塊臨時內存(注重前面c = ( a += 3 ) + ( a *= 2 );的說明),那麽將有點糟糕,而且a++的意思是先返回a的值,那麽到底是什麽時候的a的值呢?在VC中,當表達式中出現後綴「++」或後綴「—」時,只分配一塊臨時內存,然後所有的後綴「++」或後綴「—」都返回這個臨時內存的地址,然後在所有的可以計算的其他操作符的值計算完畢後,再將對應變量的值寫入到臨時內存中,計算表達式的值,最後將對應變量的值加一或減一。

  因此:a = 1; c = ( a++ ) + ( a++ );執行後,c的值爲2,而a的值爲3。而如下:

  

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

  執行時,先分配臨時內存,然後由于5個「()」,其計算順序是從左向右,

  計算++a的值,返回增一後的a的地址,a的值爲2

  計算a++的值,返回臨時內存的地址,a的值仍爲2

  計算b *= a++中的a++,返回臨時內存的地址,a的值仍爲2

  計算b *= a++中的「*=」,將a的值寫入臨時內存,計算得b的值爲2,返回b的地址

  計算a *= 2的值,返回a的地址,a的值爲4

  計算a *= a++中的a++,返回臨時內存的地址,a的值仍爲4

  計算a *= a++中的「*=」,將a的值寫入臨時內存,返回a的地址,a的值爲16

  計算剩下的「+」,爲了進行計算,將a的值寫入臨時內存,得值16 + 16 + 2 + 16 + 16爲66,寫入c中

  計算三個a++欠下的加一,a最後變爲19。

  上面說了那麽多,無非只是想告誡你——在表達式中運用賦值操作符是不被推崇的。因爲其不符合平常的數學表達式的習慣,且計算順序很輕易搞混。假如有多個「++」操作符,最好還是將表達式分開,否則很輕易導致錯誤的計算順序而計算錯誤。並且導致計算順序混亂的還不止上面的a++就完了,爲了讓你更加地重視前面的紅字,下面將介紹更令人火大的東西,假如你已經同意上面的紅字,則下面這一節完全可以跳過,其對編程來講可以認爲根本沒有任何意義(要不是爲了寫這篇文章,我都不知道它的存在)。

  序列點(Sequence Point)和附加效果(Side Effect)

  在計算c = a++時,當c的值計算(Evaluate)出來時,a的值也增加了一,a的值加一就是計算前面表達式的附加效果。有什麽問題?它可能影響表達式的計算結果。

  對于a = 0; b = 1; ( a *= 2 ) && ( b += 2 );,由于兩個「()」優先級相同,從左向右計算,計算「*=」而返回a的地址,再計算「+=」而返回b的地址,最後由于a的值爲0而返回邏輯假。很正常,但效率低了點。

  假如「&&」左邊的數字已經是0了,則不再需要計算右邊的式子。同樣,假如「」左邊的數字已經非零了,也不需要再計算右邊的數字。因爲「&&」和「」都是數學上的,數學上不管先計算加號左邊的值還是右邊的值,結果都不會改變,因此「&&」和「」才會做剛才的解釋。這也是C++保證的,既滿足數學的定義,又能提供優化的途徑(「&&」和「」右邊的數字不用計算了)。

  因此上面的式子就會被解釋成——假如a在自乘了2後的值爲0,則b就不用再自增2了。這很明顯地違反了我們的初衷,認爲b無論如何都會被自增2的。但是C++卻這樣保證,不僅僅是因爲數學的定義,還由于代碼生成的優化。但是按照操作符的優先級進行計算,上面的b += 2依舊會被執行的(這也正是我們會書寫上面代碼的原因)。爲了實現當a爲0時b += 2不會被計算,C++提出了序列點的概念。

  序列點是一些非凡位置,由C++強行定義(C++並未給出序列點的定義,因此不同的編譯器可能給出不同的序列點定義,VC是按照C語言定義的序列點)。當在進行操作符的計算時,假如碰到序列點,則序列點處的值必須被優先計算,以保證一些非凡用途,如上面的保證當a爲0時不計算b += 2,並且序列點相關的操作符(如前面的「&&」和「」)也將被計算完畢,然後才恢複正常的計算。

  「&&」的左邊數字的計算就是一個序列點,而「」的左邊數字的計算也是。C++定義了多個序列點,包括條件語句、函數參數等條件下的表達式計算,在此,不需要具體了解有哪些序列點,只需要知道由于序列點的存在而可能導致賦值操作符的計算出乎意料。下面就來分析一個例子:

  

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

  按照優先級的順序,編譯器發現要先計算a *= 2,再計算++a,接著「+=」,最後計算「&&」。然後編譯器發現這個計算過程中,出現了「&&」左邊的數字這個序列點,其要保證被優先計算,這樣就有可能不用計算b += ++a了。所以編譯器先計算「&&」的數字,通過上面的計算過程,編譯器發現就要計算a *= 2才能得到「&&」左邊的數字,因此將先計算a *= 2,返回a的地址,然後計算「&&」左邊的數字,得a的值爲0,因此就不計算b += ++a了。而不是最開始想象的由于優先級的關系先將a加一後再進行a的計算,以返回1。所以上面計算完畢後,a爲0,b爲1,返回0,表示邏輯假。

  因此序列點的出現是爲了保證一些非凡規則的出現,如上面的「&&」和「」。再考慮「,」操作符,其操作是計算兩邊的值,然後返回右邊的數字,即:a, b + 3將返回b + 3的值,但是a依舊會被計算。由于「,」的優先級是最低的(但高于前面提到的「數字」操作符),因此假如a = 3, 4;,那麽a將爲3而不是4,因爲先計算「=」,返回a的地址後再計算「,」。又:

  

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

  由于「&&」左邊數字是一個序列點,因此先計算a *= 2, b的值,但根據「,」的返回值定義,其只返回右邊的數字,因此不計算a *= 2而直接計算b = a – 1得0,「&&」就返回了,但是a *= 2就沒有被計算而導致a的值依舊爲1,這違反了「,」的定義。爲了消除這一點(當然可能還有其他應用「,」的情況),C++也將「,」的左邊數字定爲了序列點,即一定會優先執行「,」左邊的數字以保證「,」的定義——計算兩邊的數字。所以上面就由于「,」左邊數字這個序列點而導致a *= 2被優先執行,並導致b爲1,因此由于「&&」是序列點且其左邊數字非零而必須計算完右邊數字後才恢複正常優先級,而計算c = a,得2,最後才恢複正常優先級順序,執行a += 2和「+」。結果就a爲4,c爲2,b爲5。

  所以前面的a = 3, 4;其實就應該是編譯器先發現「,」這個序列點,而發現要計算「,」左邊的值,必須先計算出a = 3,因此才先計算a = 3以至于感覺序列點似乎沒有發生作用。下面的式子請自行分析,執行後a爲4,但假如將其中的「,」換成「&&」,a爲2。

  

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

  

  

  假如上面你看得很暈,沒關系,因爲上面的內容根本可以認爲毫無意義,寫在這裏也只是爲了進一步向你證實,在表達式中運用賦值運算符是不好的,即使它可能讓你寫出看起來簡練的語句,但它也使代碼的可維護性降低。

  

  

  

  

  

   QQRead.com 推出數據恢複指南教程 數據恢複指南教程

  

  

   數據恢複故障解析

   常用數據恢複方案

   硬盤數據恢複教程

  

  

   數據保護方法

   數據恢複軟件

   專業數據恢複服務指南

  

  

  

  

  

  類型轉換

  數字可以是浮點數或是整型數或其他,也就是說數字是具有類型的。注重《C++從零開始(三)》中對類型的解釋,類型只是說明如何解釋狀態,而在前面已經說過,出于方便,使用二進制數來表示狀態,因此可以說類型是用于告訴編譯器如何解釋二進制數的。

  所以,一個長整型數字是告訴編譯器將得到的二進制數表示的狀態按照二進制補碼的格式來解釋以得到一個數值,而一個單精度浮點數就是告訴編譯器將得到的二進制數表示的狀態按照IEEE的real*4的格式來解釋以得到一個是小數的數值。很明顯,同樣的二進制數表示的狀態,按照不同的類型進行解釋將得到不同的數值,那麽編譯器如何知道應該使用什麽類型來進行二進制數的解釋?

  前面已經說過,數字是一種很非凡的操作符,其沒有操作數,僅僅返回由其類型而定的二進制數表示的狀態(以後爲了方便,將「二進制數表示的狀態」稱作「二進制數」)。而操作符就是執行指令並返回數字,因此所有的操作符到最後一定執行的是返回一個二進制數。這點很重要,對于後面指針的理解有著重要的意義。

  先看15;,這是一條語句,因爲15是一個數字。所以15被認爲是char類型的數字(因爲其小于128,沒超出char的表示範圍),將返回一個8位長的二進制數,此二進制數按照補碼格式編寫,爲00001111。

  再看15.0f,同上,其由于接了「f」這個後綴而被認爲是float類型的數字,將返回一個32位長的二進制數,此二進制數按照IEEE的real*4格式編寫,爲1000001011100000000000000000000。

  雖然上面15和15.0f的數值相等,但由于是不同的類型導致了使用不同的格式來表示,甚至連表示用的二進制數的長度都不相同。因此假如書寫15.0f == 15;將返回0,表示邏輯假。但實際卻返回1,爲什麽?

  上面既然15和15.0f被表示成完全不同的兩個二進制數,但我們又認爲15和15.0f是相等的,但它們的二進制表示不同,怎麽辦?將表示15.0f的二進制數用IEEE的real*4格式解釋出15這個數值,然後再將其按8位二進制補碼格式編寫出二進制數,再與原來的表示15的二進制數比較。

  爲了實現上面的操作,C++提供了類型轉換操作符——「()」。其看起來和括號操作符一樣,但是格式不同:(<類型名>)<數字>或<類型名>(<數字>)。

  上面類型轉換操作符的<類型名>不是數字,因此其將不會被操作,而是作爲一個參數來控制其如何操作後面的<數字>。<類型名>是一個標識符,其唯一標識一個類型,如char、float等。類型轉換操作符的返回值就如其名字所示,將<數字>按照<類型名>標識的類型來解釋,返回類型是<類型名>的數字。因此,上面的例子我們就需要如下編寫:15 == ( char )15.0f;,現在其就可以返回1,表示邏輯真了。但是即使不寫( char ),前面的語句也返回1。這是編譯器出于方便的緣故而幫我們在15前添加了( float ),所以依然返回1。這被稱作隱式類型轉換,在後面說明類的時候,還將提到它。

  某個類型可以完全代替另一個類型時,編譯器就會進行上面的隱式類型轉換,自動添加類型轉換操作符。如:char只能表示-128到127的整數,而float很明顯地能夠表示這些數字,因此編譯器進行了隱式類型轉換。應當注重,這個隱式轉換是由操作符要求的,即前面的「==」要求兩面的數字類型一致,結果發現兩邊不同,結果編譯器將char轉成float,然後再執行「==」的操作。注重:在這種情況下,編譯器總是將較差的類型(如前面的char)轉成較好的類型(如前面的float),以保證不會發生數值截斷問題。如:-41 == 3543;,左邊是char,右邊是short,由于short相對于char來顯得更優(short能完全替代char),故實際爲:( short )-41 == 3543;,返回0。而假如是-41 == ( char )3543;,由于char不能表示3543,則3543以補碼轉成二進制數0000110111010111,然後取其低8位,而導致高8位的00001101被丟棄,此被稱爲截斷。結果( char )3543的返回值就是類型爲char的二進制數11010111,爲-41,結果-41 == ( char )3543;的返回值將爲1,表示邏輯真,很明顯地錯誤。因此前面的15 == 15.0f;實際將爲( float )15 == 15.0f;。

  注重前面之所以會朝好的方向發展(即char轉成float),完全是因爲「==」的緣故,其要求這麽做。下面考慮「=」:short b = 3543; char a = b;。因爲b的值是short類型,而「=」的要求就是一定要將「=」右邊的數字轉成和左邊一樣,這樣才能進行正確的內存的寫入(簡單地將右邊數字返回的二進制數複制到左邊的地址所表示的內存中)。因此a將爲-41。但是上面是編譯器按照「=」的要求自行進行了隱式轉換,可能是由于程序員的疏忽而沒有發現這個錯誤(以爲b的值一定在-128到127的範圍內),因此編譯器將對上面的情況給出一個警告,說b的值可能被截斷。爲了消除編譯器的疑慮,如下:char a = ( char )b;。這樣稱爲顯示類型轉換,其告訴編譯器——「我知道可能發生數據截斷,但是我保證不會截斷」。因此編譯器將不再發出警告。但是如下:char a = ( char )3543;,由于編譯器可以肯定3543一定會被截斷而導致錯誤的返回值,因此編譯器將給出警告,說明3543將被截斷,而不管前面的類型轉換操作符是否存在。

  現在應該可以推出——15 + 15.0f;返回的是一個float類型的數字。

  

   因此假如如下:char a = 15 + 15.0f;,編譯器將發出警告,說數據可能被截斷。因此改成如下:char a = ( char )15 + 15.0f;,但類型轉換操作符「()」的優先級比「+」高,結果就是15先被轉換爲char然後再由于「+」的要求而被隱式轉成float,最後返回float給「=」而導致編譯器依舊發出警告。爲此,就需要提高「+」的優先級,如下:char a = ( char )( 15 + 15.0f );就沒事了(或char( 15 + 15.0f )),其表示我保證15 + 15.0f不會導致數據截斷。

  應該注重類型轉換操作符「()」和前綴「++」、「!」、負號「-」等的優先級一樣,並且是從右向左計算的,因此( char )-34;將會先計算-34的值,然後再計算( char )的值,這也正好符合人的習慣。
 
 
 
上一篇《用C++Builder編寫Tray程序》
下一篇《C++Builder使用菜單設計器上下文菜單》
 
 
 
 
 
 
日版寵物情人插曲《Winding Road》歌詞

日版寵物情人2017的插曲,很帶節奏感,日語的,女生唱的。 最後聽見是在第8集的時候女主手割傷了,然後男主用嘴幫她吸了一下,插曲就出來了。 歌手:Def...

兄弟共妻,我成了他們夜裏的美食

老鍾家的兩個兒子很特別,就是跟其他的人不太一樣,魔一般的執著。兄弟倆都到了要結婚的年齡了,不管自家老爹怎麽磨破嘴皮子,兄弟倆說不娶就不娶,老父母爲兄弟兩操碎了心...

如何磨出破洞牛仔褲?牛仔褲怎麽剪破洞?

把牛仔褲磨出有線的破洞 1、具體工具就是磨腳石,下面墊一個硬物,然後用磨腳石一直磨一直磨,到把那塊磨薄了,用手撕開就好了。出來的洞啊很自然的。需要貓須的話調幾...

我就是掃描下圖得到了敬業福和愛國福

先來看下敬業福和愛國福 今年春節,支付寶再次推出了“五福紅包”活動,表示要“把欠大家的敬業福都還給大家”。 今天該活動正式啓動,和去年一樣,需要收集“五福”...

冰箱異味産生的原因和臭味去除的方法

有時候我們打開冰箱就會聞到一股異味,冰箱裏的這種異味是因爲一些物質發出的氣味的混合體,聞起來讓人惡心。 産生這些異味的主要原因有以下幾點。 1、很多人有這種習...

《極品家丁》1-31集大結局分集劇情介紹

簡介 《極品家丁》講述了現代白領林晚榮無意回到古代金陵,並追隨蕭二小姐化名“林三”進入蕭府,不料卻陰差陽錯上演了一出低級家丁拼搏上位的“林三升職記”。...

李溪芮《極品家丁》片尾曲《你就是我最愛的寶寶》歌詞

你就是我最愛的寶寶 - 李溪芮 (電視劇《極品家丁》片尾曲) 作詞:常馨內 作曲:常馨內 你的眉 又鬼馬的挑 你的嘴 又壞壞的笑 上一秒吵鬧 下...

烏梅的功效與作用以及烏梅的食用禁忌有哪些?

烏梅,又稱春梅,中醫認爲,烏梅味酸,性溫,無毒,具有安心、除熱、下氣、祛痰、止渴調中、殺蟲的功效,治肢體痛、肺痨病。烏梅泡水喝能治傷寒煩熱、止吐瀉,與幹姜一起制...

什麽是脂肪粒?如何消除臉部脂肪粒?

什麽是脂肪粒 在我們的臉上總會長一個個像脂肪的小顆粒,弄也弄不掉,而且顔色還是白白的。它既不是粉刺也不是其他的任何痘痘,它就是脂肪粒。 脂肪粒雖然也是由油脂...

網絡安全治理:國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰受害者

來源:中國青年報 新的攻擊方法不斷湧現,黑客幾乎永遠占據網絡攻擊的上風,我們不可能通過技術手段杜絕網絡攻擊。國家安全保障的主要方向是打擊犯罪,而不是處置和懲罰...

河南夫妻在溫嶺網絡直播“造人”內容涉黃被刑事拘留

夫妻網絡直播“造人”爆紅   1月9日,溫嶺城北派出所接到南京警方的協查通告,他們近期打掉了一個涉黃直播APP平台。而根據掌握的線索,其中有一對涉案的夫妻主播...

如何防止牆紙老化?牆紙變舊變黃怎麽辦?

如何防止牆紙老化? (1)選擇透氣性好的牆紙 市場上牆紙的材質分無紡布的、木纖維的、PVC的、玻璃纖維基材的、布面的等,相對而言,PVC材質的牆紙最不透氣...

鮮肌之謎非日本生産VS鮮肌之謎假日貨是謠言

觀點一:破日本銷售量的“鮮肌之謎” 非日本生産 近一段時間,淘寶上架了一款名爲“鮮肌之謎的” 鲑魚卵巢美容液,號稱是最近日本的一款推出的全新護膚品,産品本身所...

中國最美古詩詞精選摘抄

系腰裙(北宋詞人 張先) 惜霜蟾照夜雲天,朦胧影、畫勾闌。人情縱似長情月,算一年年。又能得、幾番圓。 欲寄西江題葉字,流不到、五亭前。東池始有荷新綠,尚小如...

關于女人的經典語句

關于女人的經典語句1、【做一個獨立的女人】 思想獨立:有主見、有自己的人生觀、價值觀。有上進心,永遠不放棄自己的理想,做一份自己喜愛的事業,擁有快樂和成就...

未來我們可以和性愛機器人結婚嗎?

你想體驗機器人性愛嗎?你想和性愛機器人結婚嗎?如果你想,機器人有拒絕你的權利嗎? 近日,第二屆“國際人類-機器人性愛研討會”大會在倫敦金史密斯大學落下帷幕。而...

全球最變態的十個地方

10.土耳其地下洞穴城市 變態指數:★★☆☆☆ 這是土耳其卡帕多西亞的一個著名景點,傳說是當年基督教徒們爲了躲避戰爭而在此修建。裏面曾住著20000人,...

科學家稱,人類死亡後意識將在另外一個宇宙中繼續存活

據英國《每日快報》報道,一位科學家兼理論家Robert Lanza博士宣稱,世界上並不存在人類死亡,死亡的只是身體。他認爲我們的意識借助我們體內的能量生存,而且...

《屏裏狐》片頭曲《我愛狐狸精》歌詞是什麽?

《我愛狐狸精》 - 劉馨棋   (電視劇《屏裏狐》主題曲)   作詞:金十三&李旦   作曲:劉嘉   狐狸精 狐狸仙   千年修...

 
 
 
賦值語句   前面已經說明,要訪問內存,就需要相應的地址以表明訪問哪塊內存,而變量是一個映射,因此變量名就相當于一個地址。對于內存的操作,在一般情況下就只有讀取內存中的數值和將數值寫入內存(不考慮分配和釋放內存),在C++中,爲了將一數值寫入某變量對應的地址所標識的內存中(出于簡便,以後稱變量a對應的地址爲變量a的地址,而直接稱變量a的地址所標識的內存爲變量a),只需先書寫變量名,後接「=」,再接欲寫入的數字以及分號。如下: a = 10.0f; b = 34;   由于接的是數字,因此就可以接表達式並由編譯器生成計算相應表達式所需的代碼,也就可如下: c = a / b * 120.4f;   上句編譯器將會生成進行除法和乘法計算的CPU指令,在計算完畢後(也就是求得表達式a / b * 120.4f的值了後),也會同時生成將計算結果放到變量c中去的CPU指令,這就是語句的基本作用(對于語句,在《C++從零開始(六)》中會具體說明)。   上面在書寫賦值語句時,應該確保此語句之前已經將使用到的變量定義過,這樣編譯器才能在生成賦值用的CPU指令時查找到相應變量的地址,進而完成CPU指令的生成。如上面的a和b,就需要在書寫上面語句前先書寫類似下面的變量定義: float a; long b;   直接書寫變量名也是一條語句,其導致編譯器生成一條讀取相應變量的內容的語句。即可以如下書寫: a;   上面將生成一條讀取內存的語句,即使從內存中讀出來的數字沒有任何應用(當然,假如編譯器開了優化選項,則上面的語句將不會生成任何代碼)。從這一點以及上面的c = a / b * 120.4f;語句中,都可以看出一點——變量是可以返回數字的。而變量返回的數字就是按照變量的類型來解釋變量對應內存中的內容所得到的數字。這句話也許不是那麽輕易理解,在看過後面的類型轉換一節後應該就可以理解了。 因此爲了將數據寫入一塊內存,使用賦值語句(即等號);要讀取一塊內存,書寫標識內存的變量名。所以就可以這樣書寫:a = a + 3; 假設a原來的值爲1,則上面的賦值語句將a的值取出來,加上3,得到結果4,將4再寫入a中去。由于C++使用「=」來代表賦值語句,很輕易使人和數學中的等號混淆起來,這點應注重。   而如上的float a;語句,當還未對變量進行任何賦值操作時,a的值是什麽?上帝才知道。當時的a的內容是什麽(對于VC編譯器,在開啓了調試選項時,將會用0xCCCCCCCC填充這些未初始化內存),就用IEEE的real*4格式來解釋它並得到相應的一個數字,也就是a的值。因此應在變量定義的時候就進行賦值(但是會有性能上的影響,不過很小),以初始化變量而防止出現莫名其妙的值,如:float a = 0.0f;。   賦值操作符   上面的a = a + 3;的意思就是讓a的值增加3。在C++中,對于這種情況給出了一種簡寫方案,即前面的語句可以寫成:a += 3;。應當注重這兩條語句從邏輯上講都是使變量a的值增3,但是它們實際是有區別的,後者可以被編譯成優化的代碼,因爲其意思是使某一塊內存的值增加一定數量,而前者是將一個數字寫入到某塊內存中。所以假如可能,應盡量使用後者,即a += 3;。這種語句可以讓編譯器進行一定的優化(但由于現在的編譯器都非常智能,能夠發現a = a + 3;是對一塊內存的增值操作而不是一塊內存的賦值操作,因此上面兩條語句實際上可以認爲完全相同,僅僅只具有簡寫的功能了)。   對于上面的情況,也可以應用在減法、乘法等二元非邏輯操作符(不是邏輯值操作符,即不能a &&= 3;)上,如:a *= 3; a -= 4; a = 34; a >>= 3;等。   除了上面的簡寫外,C++還提供了一種簡寫方式,即a++;,其邏輯上等同于a += 1;。同上,在電腦編程中,加一和減一是經常用到的,因此CPU專門提供了兩條指令來進行加一和減一操作(轉成彙編語言就是Inc和Dec),但速度比直接通過加法或減法指令來執行要快得多。爲此C++中也就提供了「++」和「—」操作符來對應Inc和Dec。所以a++;雖然邏輯上和a = a + 1;等效,實際由于編譯器可能做出的優化處理而不同,但還是如上,由于編譯器的智能化,其是有可能看出a = a + 1;可以編譯成Inc指令進而即使沒有使用a++;卻也依然可以得到優化的代碼,這樣a++;將只剩下簡寫的意義而已。 QQRead.com 推出數據恢複指南教程 數據恢複指南教程 數據恢複故障解析 常用數據恢複方案 硬盤數據恢複教程 數據保護方法 數據恢複軟件 專業數據恢複服務指南   應當注重一點,a = 3;這句語句也將返回一個數字,也就是在a被賦完值後a的值。由于其可以返回數字,按照《C++從零開始(二)》中所說,「=」就屬于操作符,也就可以如下書寫: c = 4 + ( a = 3 );   之所以打括號是因爲「=」的優先級較「+」低,而更常見和正常的應用是:c = a = 3;   應該注重上面並不是將c和a賦值爲3,而是在a被賦值爲3後再將a賦值給c,雖然最後結果和c、a都賦值爲3是一樣的,但不應該這樣理解。由于a++;表示的就是a += 1;就是a = a + 1;,因此a++;也將返回一個數字。也由于這個原因,C++又提供了另一個簡寫方式,++a;。 假設a爲1,則a++;將先返回a的值,1,然後再將a的值加一;而++a;先將a的值加一,再返回a的值,2。而a—和—a也是如此,只不過是減一罷了。   上面的變量a按照最上面的變量定義,是float類型的變量,對它使用++操作符並不能得到預想的優化,因爲float類型是浮點類型,其是使用IEEE的real*4格式來表示數字的,而不是二進制原碼或補碼,而前面提到的Inc和Dec指令都是出于二進制的表示優點來進行快速增一和減一,所以假如對浮點類型的變量運用「++」操作符,將完全只是簡寫,沒有任何的優化效果(當然,假如CPU提供了新的指令集,如MMX等,以對real*4格式進行快速增一和減一操作,且編譯器支持相應指令集,則還是可以産生優化效果的)。   賦值操作符的返回值   在進一步了解++a和a++的區別前,先來了解何謂操作符的計算(Evaluate)。操作符就是將給定的數字做一些處理,然後返回一個數字。而操作符的計算也就是執行操作符的處理,並返回值。前面已經知道,操作符是個符號,其一側或兩側都可以接數字,也就是再接其他操作符,而又由于賦值操作符也屬于一種操作符,因此操作符的執行順序變得相當重要。   對于a + b + c,將先執行a + b,再執行( a + b ) + c的操作。你可能覺得沒什麽,那麽如下,假設a之前爲1: c = ( a *= 2 ) + ( a += 3 );   上句執行後a爲5。而c = ( a += 3 ) + ( a *= 2 );執行後,a就是8了。那麽c呢?結果可能會大大的出乎你的意料。前者的c爲10,而後者的c爲16。   上面其實是一個障眼法,其中的「+」沒有任何意義,即之所以會從左向右執行並不是因爲「+」的緣故,而是因爲( a *= 2 )和( a += 3 )的優先級相同,而按照「()」的計算順序,是從左向右來計算的。但爲什麽c的值不是預想的2 + 5和4 + 8呢?因爲賦值操作符的返回值的關系。   賦值操作符返回的數字不是變量的值,而是變量對應的地址。這很重要。前面說過,光寫一個變量名就會返回相應變量的值,那是因爲變量是一個映射,變量名就等同于一個地址。C++中將數字看作一個很非凡的操作符,即任何一個數字都是一個操作符。而地址就和長整型、單精度浮點數這類一樣,是數字的一種類型。當一個數字是地址類型時,作爲操作符,其沒有要操作的數字,僅僅返回將此數字看作地址而標識的內存中的內容(用這個地址的類型來解釋)。地址可以通過多種途徑得到,如上面光寫一個變量名就可以得到其對應的地址,而得到的地址的類型也就是相應的變量的類型。假如這句話不能理解,在看過下面的類型轉換一節後應該就能了解了。   所以前面的c = ( a += 3 ) + ( a *= 2 );,由于「()」的參與改變了優先級而先執行了兩個賦值操作符,然後兩個賦值操作符都返回a的地址,然後計算「+」的值,分別計算兩邊的數字——a的地址(a的地址也是一個操作符),也就是已經執行過兩次賦值操作的a的值,得8,故最後的c爲16。而另一個也由于同樣的原因使得c爲10。   現在考慮操作符的計算順序。當同時出現了幾個優先級相同的操作符時,不同的操作符具有不同的計算順序。前面的「()」以及「-」、「*」等這類二元操作符的計算順序都是從左向右計算,而「!」、負號「-」等前面介紹過的一元操作符都是從右向左計算的,如:!-!!a;,假設a爲3。先計算從左朝右數第三個「!」的值,導致計算a的地址的值,得3;然後邏輯取反得0,接著再計算第二個「!」的值,邏輯取反後得1,再計算負號「-」的值,得-1,最後計算第一個「!」的值,得0。   賦值操作符都是從右向左計算的,除了後綴「++」和後綴「—」(即上面的a++和a--)。因此上面的c = a = 3;,因爲兩個「=」優先級相同,從右向左計算,先計算a = 3的值,返回a對應的地址,然後計算返回的地址而得到值3,再計算c = ( a = 3 ),將3寫入c。而不是從左向右計算,即先計算c = a,返回c的地址,然後再計算第二個「=」,將3寫入c,這樣a就沒有被賦值而出現問題。又: a = 1; c = 2; c *= a += 4;   由于「*=」和「+=」的優先級相同,從右向左計算先計算a += 4,得a爲5,然後返回a的地址,再計算a的地址得a的值5,計算「*=」以使得c的值爲10。   因此按照前面所說,++a將返回a的地址,而a++也因爲是賦值操作符而必須返回一個地址,但很明顯地不能是a的地址了,因此編譯器將編寫代碼以從棧中分配一塊和a同樣大小的內存,並將a的值複制到這塊臨時內存中,然後返回這塊臨時內存的地址。由于這塊臨時內存是因爲編譯器的需要而分配的,與程序員完全沒有關系,因此程序員是不應該也不能寫這塊臨時內存的(因爲編譯器負責編譯代碼,假如程序員欲訪問這塊內存,編譯器將報錯),但可以讀取它的值,這也是返回地址的主要目的。所以如下的語句沒有問題: ( ++a ) = a += 34;   但( a++ ) = a += 34;就會在編譯時報錯,因爲a++返回的地址所標識的內存只能由編譯器負責處理,程序員只能獲得其值而已。 a++的意思是先返回a的值,也就是上面說的臨時內存的地址,然後再將變量的值加一。假如同時出現多個a++,那麽每個a++都需要分配一塊臨時內存(注重前面c = ( a += 3 ) + ( a *= 2 );的說明),那麽將有點糟糕,而且a++的意思是先返回a的值,那麽到底是什麽時候的a的值呢?在VC中,當表達式中出現後綴「++」或後綴「—」時,只分配一塊臨時內存,然後所有的後綴「++」或後綴「—」都返回這個臨時內存的地址,然後在所有的可以計算的其他操作符的值計算完畢後,再將對應變量的值寫入到臨時內存中,計算表達式的值,最後將對應變量的值加一或減一。   因此:a = 1; c = ( a++ ) + ( a++ );執行後,c的值爲2,而a的值爲3。而如下: a = 1; b = 1; c = ( ++a ) + ( a++ ) + ( b *= a++ ) + ( a *= 2 ) + ( a *= a++ );   執行時,先分配臨時內存,然後由于5個「()」,其計算順序是從左向右,   計算++a的值,返回增一後的a的地址,a的值爲2   計算a++的值,返回臨時內存的地址,a的值仍爲2   計算b *= a++中的a++,返回臨時內存的地址,a的值仍爲2   計算b *= a++中的「*=」,將a的值寫入臨時內存,計算得b的值爲2,返回b的地址   計算a *= 2的值,返回a的地址,a的值爲4   計算a *= a++中的a++,返回臨時內存的地址,a的值仍爲4   計算a *= a++中的「*=」,將a的值寫入臨時內存,返回a的地址,a的值爲16   計算剩下的「+」,爲了進行計算,將a的值寫入臨時內存,得值16 + 16 + 2 + 16 + 16爲66,寫入c中   計算三個a++欠下的加一,a最後變爲19。   上面說了那麽多,無非只是想告誡你——在表達式中運用賦值操作符是不被推崇的。因爲其不符合平常的數學表達式的習慣,且計算順序很輕易搞混。假如有多個「++」操作符,最好還是將表達式分開,否則很輕易導致錯誤的計算順序而計算錯誤。並且導致計算順序混亂的還不止上面的a++就完了,爲了讓你更加地重視前面的紅字,下面將介紹更令人火大的東西,假如你已經同意上面的紅字,則下面這一節完全可以跳過,其對編程來講可以認爲根本沒有任何意義(要不是爲了寫這篇文章,我都不知道它的存在)。   序列點(Sequence Point)和附加效果(Side Effect)   在計算c = a++時,當c的值計算(Evaluate)出來時,a的值也增加了一,a的值加一就是計算前面表達式的附加效果。有什麽問題?它可能影響表達式的計算結果。   對于a = 0; b = 1; ( a *= 2 ) && ( b += 2 );,由于兩個「()」優先級相同,從左向右計算,計算「*=」而返回a的地址,再計算「+=」而返回b的地址,最後由于a的值爲0而返回邏輯假。很正常,但效率低了點。   假如「&&」左邊的數字已經是0了,則不再需要計算右邊的式子。同樣,假如「」左邊的數字已經非零了,也不需要再計算右邊的數字。因爲「&&」和「」都是數學上的,數學上不管先計算加號左邊的值還是右邊的值,結果都不會改變,因此「&&」和「」才會做剛才的解釋。這也是C++保證的,既滿足數學的定義,又能提供優化的途徑(「&&」和「」右邊的數字不用計算了)。   因此上面的式子就會被解釋成——假如a在自乘了2後的值爲0,則b就不用再自增2了。這很明顯地違反了我們的初衷,認爲b無論如何都會被自增2的。但是C++卻這樣保證,不僅僅是因爲數學的定義,還由于代碼生成的優化。但是按照操作符的優先級進行計算,上面的b += 2依舊會被執行的(這也正是我們會書寫上面代碼的原因)。爲了實現當a爲0時b += 2不會被計算,C++提出了序列點的概念。   序列點是一些非凡位置,由C++強行定義(C++並未給出序列點的定義,因此不同的編譯器可能給出不同的序列點定義,VC是按照C語言定義的序列點)。當在進行操作符的計算時,假如碰到序列點,則序列點處的值必須被優先計算,以保證一些非凡用途,如上面的保證當a爲0時不計算b += 2,並且序列點相關的操作符(如前面的「&&」和「」)也將被計算完畢,然後才恢複正常的計算。   「&&」的左邊數字的計算就是一個序列點,而「」的左邊數字的計算也是。C++定義了多個序列點,包括條件語句、函數參數等條件下的表達式計算,在此,不需要具體了解有哪些序列點,只需要知道由于序列點的存在而可能導致賦值操作符的計算出乎意料。下面就來分析一個例子: a = 0; b = 1; ( a *= 2 ) && ( b += ++a );   按照優先級的順序,編譯器發現要先計算a *= 2,再計算++a,接著「+=」,最後計算「&&」。然後編譯器發現這個計算過程中,出現了「&&」左邊的數字這個序列點,其要保證被優先計算,這樣就有可能不用計算b += ++a了。所以編譯器先計算「&&」的數字,通過上面的計算過程,編譯器發現就要計算a *= 2才能得到「&&」左邊的數字,因此將先計算a *= 2,返回a的地址,然後計算「&&」左邊的數字,得a的值爲0,因此就不計算b += ++a了。而不是最開始想象的由于優先級的關系先將a加一後再進行a的計算,以返回1。所以上面計算完畢後,a爲0,b爲1,返回0,表示邏輯假。   因此序列點的出現是爲了保證一些非凡規則的出現,如上面的「&&」和「」。再考慮「,」操作符,其操作是計算兩邊的值,然後返回右邊的數字,即:a, b + 3將返回b + 3的值,但是a依舊會被計算。由于「,」的優先級是最低的(但高于前面提到的「數字」操作符),因此假如a = 3, 4;,那麽a將爲3而不是4,因爲先計算「=」,返回a的地址後再計算「,」。又: a = 1; b = 0; b = ( a += 2 ) + ( ( a *= 2, b = a - 1 ) && ( c = a ) );   由于「&&」左邊數字是一個序列點,因此先計算a *= 2, b的值,但根據「,」的返回值定義,其只返回右邊的數字,因此不計算a *= 2而直接計算b = a – 1得0,「&&」就返回了,但是a *= 2就沒有被計算而導致a的值依舊爲1,這違反了「,」的定義。爲了消除這一點(當然可能還有其他應用「,」的情況),C++也將「,」的左邊數字定爲了序列點,即一定會優先執行「,」左邊的數字以保證「,」的定義——計算兩邊的數字。所以上面就由于「,」左邊數字這個序列點而導致a *= 2被優先執行,並導致b爲1,因此由于「&&」是序列點且其左邊數字非零而必須計算完右邊數字後才恢複正常優先級,而計算c = a,得2,最後才恢複正常優先級順序,執行a += 2和「+」。結果就a爲4,c爲2,b爲5。   所以前面的a = 3, 4;其實就應該是編譯器先發現「,」這個序列點,而發現要計算「,」左邊的值,必須先計算出a = 3,因此才先計算a = 3以至于感覺序列點似乎沒有發生作用。下面的式子請自行分析,執行後a爲4,但假如將其中的「,」換成「&&」,a爲2。 a = 1; b = ( a *= 2 ) + ( ( a *= 3 ), ( a -= 2 ) );   假如上面你看得很暈,沒關系,因爲上面的內容根本可以認爲毫無意義,寫在這裏也只是爲了進一步向你證實,在表達式中運用賦值運算符是不好的,即使它可能讓你寫出看起來簡練的語句,但它也使代碼的可維護性降低。 QQRead.com 推出數據恢複指南教程 數據恢複指南教程 數據恢複故障解析 常用數據恢複方案 硬盤數據恢複教程 數據保護方法 數據恢複軟件 專業數據恢複服務指南   類型轉換   數字可以是浮點數或是整型數或其他,也就是說數字是具有類型的。注重《C++從零開始(三)》中對類型的解釋,類型只是說明如何解釋狀態,而在前面已經說過,出于方便,使用二進制數來表示狀態,因此可以說類型是用于告訴編譯器如何解釋二進制數的。   所以,一個長整型數字是告訴編譯器將得到的二進制數表示的狀態按照二進制補碼的格式來解釋以得到一個數值,而一個單精度浮點數就是告訴編譯器將得到的二進制數表示的狀態按照IEEE的real*4的格式來解釋以得到一個是小數的數值。很明顯,同樣的二進制數表示的狀態,按照不同的類型進行解釋將得到不同的數值,那麽編譯器如何知道應該使用什麽類型來進行二進制數的解釋?   前面已經說過,數字是一種很非凡的操作符,其沒有操作數,僅僅返回由其類型而定的二進制數表示的狀態(以後爲了方便,將「二進制數表示的狀態」稱作「二進制數」)。而操作符就是執行指令並返回數字,因此所有的操作符到最後一定執行的是返回一個二進制數。這點很重要,對于後面指針的理解有著重要的意義。   先看15;,這是一條語句,因爲15是一個數字。所以15被認爲是char類型的數字(因爲其小于128,沒超出char的表示範圍),將返回一個8位長的二進制數,此二進制數按照補碼格式編寫,爲00001111。   再看15.0f,同上,其由于接了「f」這個後綴而被認爲是float類型的數字,將返回一個32位長的二進制數,此二進制數按照IEEE的real*4格式編寫,爲1000001011100000000000000000000。   雖然上面15和15.0f的數值相等,但由于是不同的類型導致了使用不同的格式來表示,甚至連表示用的二進制數的長度都不相同。因此假如書寫15.0f == 15;將返回0,表示邏輯假。但實際卻返回1,爲什麽?   上面既然15和15.0f被表示成完全不同的兩個二進制數,但我們又認爲15和15.0f是相等的,但它們的二進制表示不同,怎麽辦?將表示15.0f的二進制數用IEEE的real*4格式解釋出15這個數值,然後再將其按8位二進制補碼格式編寫出二進制數,再與原來的表示15的二進制數比較。   爲了實現上面的操作,C++提供了類型轉換操作符——「()」。其看起來和括號操作符一樣,但是格式不同:(<類型名>)<數字>或<類型名>(<數字>)。   上面類型轉換操作符的<類型名>不是數字,因此其將不會被操作,而是作爲一個參數來控制其如何操作後面的<數字>。<類型名>是一個標識符,其唯一標識一個類型,如char、float等。類型轉換操作符的返回值就如其名字所示,將<數字>按照<類型名>標識的類型來解釋,返回類型是<類型名>的數字。因此,上面的例子我們就需要如下編寫:15 == ( char )15.0f;,現在其就可以返回1,表示邏輯真了。但是即使不寫( char ),前面的語句也返回1。這是編譯器出于方便的緣故而幫我們在15前添加了( float ),所以依然返回1。這被稱作隱式類型轉換,在後面說明類的時候,還將提到它。   某個類型可以完全代替另一個類型時,編譯器就會進行上面的隱式類型轉換,自動添加類型轉換操作符。如:char只能表示-128到127的整數,而float很明顯地能夠表示這些數字,因此編譯器進行了隱式類型轉換。應當注重,這個隱式轉換是由操作符要求的,即前面的「==」要求兩面的數字類型一致,結果發現兩邊不同,結果編譯器將char轉成float,然後再執行「==」的操作。注重:在這種情況下,編譯器總是將較差的類型(如前面的char)轉成較好的類型(如前面的float),以保證不會發生數值截斷問題。如:-41 == 3543;,左邊是char,右邊是short,由于short相對于char來顯得更優(short能完全替代char),故實際爲:( short )-41 == 3543;,返回0。而假如是-41 == ( char )3543;,由于char不能表示3543,則3543以補碼轉成二進制數0000110111010111,然後取其低8位,而導致高8位的00001101被丟棄,此被稱爲截斷。結果( char )3543的返回值就是類型爲char的二進制數11010111,爲-41,結果-41 == ( char )3543;的返回值將爲1,表示邏輯真,很明顯地錯誤。因此前面的15 == 15.0f;實際將爲( float )15 == 15.0f;。   注重前面之所以會朝好的方向發展(即char轉成float),完全是因爲「==」的緣故,其要求這麽做。下面考慮「=」:short b = 3543; char a = b;。因爲b的值是short類型,而「=」的要求就是一定要將「=」右邊的數字轉成和左邊一樣,這樣才能進行正確的內存的寫入(簡單地將右邊數字返回的二進制數複制到左邊的地址所表示的內存中)。因此a將爲-41。但是上面是編譯器按照「=」的要求自行進行了隱式轉換,可能是由于程序員的疏忽而沒有發現這個錯誤(以爲b的值一定在-128到127的範圍內),因此編譯器將對上面的情況給出一個警告,說b的值可能被截斷。爲了消除編譯器的疑慮,如下:char a = ( char )b;。這樣稱爲顯示類型轉換,其告訴編譯器——「我知道可能發生數據截斷,但是我保證不會截斷」。因此編譯器將不再發出警告。但是如下:char a = ( char )3543;,由于編譯器可以肯定3543一定會被截斷而導致錯誤的返回值,因此編譯器將給出警告,說明3543將被截斷,而不管前面的類型轉換操作符是否存在。   現在應該可以推出——15 + 15.0f;返回的是一個float類型的數字。 因此假如如下:char a = 15 + 15.0f;,編譯器將發出警告,說數據可能被截斷。因此改成如下:char a = ( char )15 + 15.0f;,但類型轉換操作符「()」的優先級比「+」高,結果就是15先被轉換爲char然後再由于「+」的要求而被隱式轉成float,最後返回float給「=」而導致編譯器依舊發出警告。爲此,就需要提高「+」的優先級,如下:char a = ( char )( 15 + 15.0f );就沒事了(或char( 15 + 15.0f )),其表示我保證15 + 15.0f不會導致數據截斷。   應該注重類型轉換操作符「()」和前綴「++」、「!」、負號「-」等的優先級一樣,並且是從右向左計算的,因此( char )-34;將會先計算-34的值,然後再計算( char )的值,這也正好符合人的習慣。
󰈣󰈤
 
 
 
  免責聲明:本文僅代表作者個人觀點,與王朝網路無關。王朝網路登載此文出於傳遞更多信息之目的,並不意味著贊同其觀點或證實其描述,其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。
 
 
陽光靓麗的模特兒(8)
陽光靓麗的模特兒(7)
陽光靓麗的模特兒(6)
陽光靓麗的模特兒(5)
秋-印象
德慶盤龍峽 一
松江印象之三
雲之南(寬幅)
 
>>返回首頁<<
 
 
 
 熱帖排行
 
 
 
 
© 2005- 王朝網路 版權所有