1. 程式人生 > >C++表示式深入

C++表示式深入

操作符

除法(/)和求模(%)


   如果兩個運算元都為正數,除法和求模操作的結果也是正數(或零);
   如果兩個運算元都為負數,除法操作的結果為正數(或零),而求模操作的結果則為負數(或零);
   如果兩個運算元是一個正一個負,除法和求模的結果取決於機器;求模結果的符號也取決於機器,而除法操作的結果是負數(或零)。

 

   當只有一個運算元為負數時,求模操作結果值的符號可依據分子(被除數)或分母(除數)的符號而定。如果求模的結果隨分子的符號定,則除的結果向零一側取整;如果求模的結果隨分母的符號,則除的結果向負無窮一側取整。


舉例:

21 / 6;    // ok: result is 3
21 / 7;    // ok: result is 3
-21 / -8;  // ok: result is 2
21 / -5;   // machine-dependent: result is -4 or -5
21 % 6;    // ok: result is 3
21 % 7;    // ok: result is 0
-21 % -8;  // ok: result is -5
21 % -5;   // machine-dependent: result is 1 or -4


  

短路求值策略(short-circuit evaluation)


   邏輯與(&&)和邏輯或(||)操作符總是先計算其左運算元,然後再計算其右運算元。只有在僅靠左運算元的值無法確定整個邏輯表示式的值時,才會計算其右運算元。

 

移位操作符

 

   移位操作符的運算元的型別必須是整型、列舉型別或者整型提升型別。運算結果的型別是左運算元的提升型別。


   移位操作的右運算元不可以是負數,而且必須是嚴格小於左運算元位數的值;否則,計算結果是未定義的。


左移:E1 << E2;

   如果E1是unsigned型別,那麼其運算結果相當於,E1乘2的E2次冪,然後再對該unsigned型別的個數取模。如果E1是unsigned long型,那麼是對 ULONG_MAX + 1 取模;否則,是對 UINT_MAX + 1 取模。(ULONG_MAX和UINT_MAX定義於<climits>)


右移:E1 >> E2;

   如果E1的型別是unsigned型別,或者E1是signed型別但是非負值,則計算結果相當於,E1除2的E2次冪,然後取結果的整數部分。如果E1的型別是signed型別,且是負值,則計算結果是依賴於實現的。


複合賦值操作符

 

   複合賦值操作符的語法格式形如:a op= b; 其計算結果相當於:a = a op b;


   但前後兩種形式是有區別的:使用複合賦值操作時,左運算元只計算了一次;而使用長表示式時,該運算元計算了兩次,第一次是作為右值參與op的計算,第二次是作為左值參與賦值計算。


sizeof操作符

 

   sizeof操作符的作用是返回物件或型別名的長度,返回值的型別為size_t,長度的單位是位元組。sizeof表示式的結果是編譯時常量,具有三種形式:
   sizeof (type name);
   sizeof (expr);
   sizeof expr;
最後一種形式使sizeof更像一個操作符,前兩種形式容易使人誤解為函式。


運算元的計算順序

 

   C++中只規定了這四個操作符(&&、||、?:、,)的運算元的計算順序。除此之外,對於其他操作符並沒有規定其運算元的計算順序。(注:C++也沒有規定函式呼叫時引數的計算順序)


   運算元的計算順序引發的問題是C++程式設計師(包括C程式設計師)常犯的錯誤,例如:
   ia[i++] < ia[i]
假如初始i=1,那麼這個表示式會有兩種可能解釋: ia[1] < ia[1] 或者 ia[1] < ia[2]。前面的解釋是先計算操作符<的右運算元,後面的解釋是先計算操作符<的左運算元。


   另外一個常見的錯誤就是在同一表示式中分別在不同的子表示式修改和引用變數。例如:a + a++。


   由此可見,正確地理解運算元的計算順序對編寫正確的程式是多麼地重要。

 

new和delete


   值初始化()可以在建立內建型別物件時,將其初始化為0;對於類型別,用不用值初始化都將是呼叫其預設建構函式。例如:
int* pi1 = new int;          // pi1 points to an uninitialized int
int* pi2 = new int();        // pi2 points to an int value-initialized to 0
string* ps1 = new string;    // ps1 points to an initialized empty string
string* ps2 = new stirng();  // ps2 points to an initialized empty string


   如果指標的值為0,則對其進行delete操作是合法的。例如:
   int* p = NULL;
   delete p;    // ok: it's safe

 

懸垂指標 (dangling pointer)

 


   當刪除指標所指物件後,該指標就變為所謂的懸垂指標,其指向了一個已經被刪除的物件的位置。


   建議:當刪除了指標所指物件後,應當立即將指標置0。

 

記憶體洩漏(memory leak):指動態分配的記憶體沒有被正常地釋放。

 


算術轉換

整型提升(integral promotion)

 

   對於所有比int小的整型,包括char、signed char、unsigned char、short和unsigned short,如果該型別所有可能的值都能被int型表示,則它們被提升為int型;否則,它們將被提升為unsigned int型。如果將bool值提升為int型,則false轉換為0,true轉換為1。

 

   同樣,如果unsigned int和long一起參與計算,則當long能表示所有unsigned int時,unsigned int轉換為long;否則兩個運算元都被轉換為unsigned long。

 

指標轉換


   指向任何資料型別的指標都可以轉換為void*型別;整型常量0可以轉換為任意指標型別。

 

bool型轉換


   算術值和指標值都可以轉換為bool型別。如果指標或算術值為0,則其bool值為false;否則為true。例如:
   double pi = 3.14;
   bool b = pi;        // b is true

 

列舉型別轉換


   C++自動地將列舉型別(enum)的物件或其成員轉換為整型。將列舉物件或其成員提升為什麼型別由機器定義,並且依賴於列舉成員的最大值。無論其最大值是什麼,列舉物件或其成員至少提升為int型。如果int型無法表示該列舉成員的最大值,則提升到能表示所有列舉成員值的最小型別(unsigned int、long或unsigned long)。

 

強制型別轉換

 

   強制型別轉換的語法:cast-name<type>(expr);
其中,cast-name是static_cast、dynamic_cast、const_cast、reinterpret_cast之一,type為轉換的目標型別,expr為被轉換的表示式。


static-cast

   編譯器隱式執行的任何型別都可以由static_cast顯式完成。

 

dynamic_cast

   可以使用dynamic_cast操作符將基類物件的引用或指標轉換為同一繼承層次中其他型別的引用或指標。dynamic_cast轉換在執行時會進行檢查,如果繫結到引用或指標的物件不是目標型別的物件,則dynamic_cast失敗。如果轉換到指標型別的dynamic_cast失敗,則dynamic_cast的結果為0;如果轉換到引用型別的dynamic_cast失敗,則丟擲一個 bad_cast異常。

 

const_cast

   去掉const屬性。

 

reinterpret_cast

   為運算元的位模式提供重新解釋。如:int* ip; char* pc = reinterpret_cast<char*>(ip);
顯然這種解釋依賴於機器,在做這種轉換時一定要清楚自己在幹什麼。

 

建議:慎用強制轉換
   因為強制轉換關閉了正常的型別檢查。在用強制型別轉換時,先考慮是不是設計有缺陷。

 

舊式強制型別轉換

 

   舊式強制轉換有兩種形式:
   type (expr);    // function-style cast notation
   (type) expr;    // C-style cast notation


   保留舊式強制轉換是為了對標準C++之前編寫的程式保持向後相容,並保持與C相容。