程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++從零開始(四)——賦值操作符

C++從零開始(四)——賦值操作符

編輯:C++入門知識

C++從零開始(四)——賦值操作符
原始出處:網絡

本篇是《C++從零開始(二)》的延續,說明《C++從零開始(二)》中遺留下來的關於表達式的內容,並為下篇指針的運用做一點鋪墊。雖然上篇已經說明了變量是什麼,但對於變量最關鍵的東西卻由於篇幅限制而沒有說明,下面先說明如何訪問內存。


賦值語句

前面已經說明,要訪問內存,就需要相應的地址以表明訪問哪塊內存,而變量是一個映射,因此變量名就相當於一個地址。對於內存的操作,在一般情況下就只有讀取內存中的數值和將數值寫入內存(不考慮分配和釋放內存),在C++中,為了將一數值寫入某變量對應的地址所標識的內存中(出於簡便,以後稱變量a對應的地址為變量a的地址,而直接稱變量a的地址所標識的內存為變量a),只需先書寫變量名,後接“=”,再接欲寫入的數字(關於數字,請參考《C++從零開始(二)》)以及分號。如下:
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++;將只剩下簡寫的意義而已。
應當注意一點,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的值復制到這塊臨時內存中,然後返回這塊臨時內存的地址。由於這塊臨時內存是因為編譯器的需要而分配的,與程序員完全沒有關系,因此程序員是不應該也不能寫這塊臨時內存的(因為編譯器負責編譯代碼,如果程序員欲訪問這塊內存,編譯器將報錯),但可以讀取它的值,這也是返回地址的主要目的。所以如下的語句沒有問

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved