第5章 運算式
⒈ 運算式(expression_r)是一個C++程式中最低級的計算,由一或多個用一個操作符(operator)串連起來的運算元(operands)組成
⒉ 每個運算式都產生一個結果。運算式可以用作運算元,因此可用多個操作符編寫複合運算式
⒊ 在求解運算式的過程中如果需要儲存運算結果,編譯器會自動建立沒有名字的臨時對象(temporary object),這些對象會在外圍最大的運算式結束後釋放
⒋ 運算式是否合法、合法運算式含義如何(執行什麼操作、結果是什麼類型)均取決於運算元的類型
⒌⒈ 算術操作符
⒈ 按優先順序從高到低排列為:
一元正+, 一元負-; 乘法*, 除法/, 模數%; 加法+, 減法-
⒉ 關於除法/
⑴ 兩整數相除結果仍為整數,商的小數部分被截去
⑵ 一正一負兩整數相除,結果值向0一側還是向-∞一側取整依賴於機器
⒊ 關於模數
⑴ 運算元只能為整型
⑵ 兩運算元均為負時結果為負或0;
兩運算元一正一負時,結果的符號隨哪個運算元而定依賴於機器
⒌⒉ 關係操作符和邏輯操作符
⒈ 按優先順序從高到低排列為:
邏輯非!; 小於<, 小於等於<=, 大於>, 大於等於>=; 相等==, 不等!=; 邏輯與&&; 邏輯或||
⒉ 關係操作符和邏輯操作符接受算術或指標型運算元,並返回 bool 型值
邏輯操作符視其運算元為條件運算式
⒊ 邏輯與&&和邏輯或||操作符支援短路求值(short-circuit evaluation)
⒋ 不應串接使用關係操作符
形如i<j<k的運算式得不到預期結果
⒌⒊ 位操作符
⒈ 按優先順序從高到低排列為:
位取反~; 左移<<, 右移>>; 位與&; 位異或^; 位或|
⒉ 位操作符使用整型運算元,將其視為二進位位的集合
由於負整數的符號位如何處理依賴於機器,因此應使用 unsigned 整型運算元
⒌⒋ 賦值操作符
⒈ 賦值操作符的左運算元須為非 const 左值
賦值運算式的結果即為其左運算元(左值)
⒉ 賦值操作符從右向左結合,因此當各運算元都有相同的通用類型時,允許在一個運算式中進行多次賦值,如:
i = j = k = 0;
⒊ 複合賦值操作符
對於任意二元算術操作符或二元位操作符 op
a op= b;
相當於
a = a op b;
二者顯著的差別在於前者只計算了一次左運算元,後者則計算了兩次
⒌⒌ 自增和自減操作符
⒈ 自增++和自減–操作符為對象加1或減1提供了方便簡短的實現方式,有前置和後置兩種使用形式
前置操作返回加(減)1後的對象(左值),後置操作返回運算元的原值(右值)
⒉ 出於效能考慮,應只在必要時才使用後置操作符
後置操作符需要先儲存原值以便返回,而前置操作符只需加(減)1後直接返回對象即可
⒌⒍ 箭頭操作符
箭頭操作符用於擷取指標指向類類型對象的成員。
下面兩個運算式等價:
p->foo;
(*p).foo;
⒌⒎ 條件操作符
⒈ 條件操作符是C++中唯一的三元操作符,能將簡短的 if-else 語句嵌入運算式
格式為 cond ? expr1 : expr2
首先計算 cond 值,若為 true 則計算並返回 expr1, 為
false 則計算並返回 expr2
⒉ 避免深度嵌套以保證可讀性
⒌⒏ sizeof 操作符
⒈ sizeof 操作符用於返回一個對象或類型的大小(單位位元組,類型
size_t)
形式為 sizeof expr
或 sizeof(type_name)
⒉ sizeof 運算式的結果是編譯時間常量
對錶達式使用 sizeof 時該運算式的值並不會被計算
⒊ 對數組作 sizeof 將得到整個數組在記憶體中的長度
⒌⒐ 逗號操作符
逗號運算式是一組由逗號分隔的運算式,從左向右計算並返回最右邊運算式的值(若該運算式為左值則返回左值)
⒌⒒ new 和
delete 運算式
⒈ new 和
delete 運算式分別用於動態建立和釋放單個對象或一個數組
⒉ new 運算式
⑴ new 運算式動態建立單個對象或一個數組,並返回指向該對象或數組首元素的指標
① 動態數組維數為0值時也將返回有效非零指標,但不能解引用
② 可以建立動態 const 對象或數組,它們無法修改但可以釋放
③ 如果無法擷取需要的空間,系統將拋出 bad_alloc 異常
⑵ 分配與對象(或數組元素)初始化
① 預設內建類型不初始化,類類型調用預設建構函式(必須提供)
單個對象: new [const] 類型名
一個數組: new [const] 類型名[維數]
② 添加空括弧()可執行值初始化(value initialization)
即內建類型對象置為0,類類型對象調用預設建構函式(必須提供)
單個對象: new [const] 類型名()
一個數組: new [const] 類型名[維數]()
註:對於後者,我使用的編譯器中,VC初始化後會把元素置為0,但mingw不會
③ 執行直接初始化
單個對象: new [const] 類型名(初始化式)
動態數組元素不支援類似初始化方式
⒊ delete 運算式
⑴ 由 new 動態分配的對象或數組需要顯式地使用
delete 運算式釋放(否則會造成記憶體流失)
⑵ delete ptr 和
delete[] ptr 分別釋放單個對象和動態數組
① 如果指標不指向由 new 分配的對象,則對其
delete 不合法
例外:對零指標 delete 是合法的
② 讀寫已釋放的對象或對同一記憶體空間多次 delete 都可能導致錯誤
因此 delete 之後應立即重設該指標的值(一般重設為0)
③ 釋放動態數組如果丟掉方括弧[],將可能導致編譯器無法發現的錯誤
⒌⒑ 複合運算式的求值
⒈ 含有兩個或以上操作符的運算式稱為複合運算式(compound expression_r)
運算元和操作符的結合方式決定了符合運算式的值,而前者取決於操作符的優先順序和結合性:
運算元優先與優先順序更高的操作符結合;操作符優先順序相同時則由結合性決定結合方向
⒉ 圓括弧()淩駕於優先順序之上
若不確定結合方式,則使用圓括弧()強制確定運算元的組合
⒊ 結合方式確定並不意味著運算元的計算順序並不確定
若修改了運算元的值,則不要在同一語句的其它地方再使用它,除非運算元的計算次序不成問題
⒋
運算子優先順序與結合性表
⒌⒓ 類型轉換
⒈ 隱式類型轉換
兩個類型間存在可轉換關係,則稱兩個類型相關
⑴ 何時發生
當編譯器期望獲得某種類型的資料卻得到另一種類型的,就會嘗試自動轉換類型
具體情況包括:
① 運算式中不同類型的多個運算元被轉換到同一類型
② 用作條件的運算式被轉換為 bool 型
③ 用一運算式對某變數初始化或賦值時,前者轉換為後者的類型
④ 給函數傳遞實參和傳回值時可能發生隱式類型轉換
⑵ 內建類型轉換規則
① 算術類型間的轉換
ⅰ 浮點型轉換為整型時小數部分被拋棄(在賦值、初始化等情況下發生)
ⅱ 二元(算術、關係等)操作符運算式中,為試圖保留精度會把較小的類型轉換為較大的類型
a型別提升
· 將比 int 小的整型提升為
int 或(當 int 不足以容納原類型的所有可能值時)unsigned
int
· 將 float 提升為
double
b signed 與
unsigned 之間的轉換
· 對於 signed 較小類型和
unsigned 較大或相同類型,前者轉換為後者
· 對於 signed 較大類型和
unsigned 較小類型,如何操作依賴於機器:
前者若能表示後者所有可能值則後者轉換為前者,否則都轉換為前者的 unsigned 形式
以上兩種情況,負值有可能會被轉換為 unsigned 導致溢出,造成意料之外的結果
② 轉換為指標
ⅰ 運算式中的數組名會自動轉換為指向數組首元素的指標
但以下情況除外:
a數組作為取地址操作符&的運算元
b數組作為 sizeof 操作符的運算元
c使用數組對數組的引用初始化時
ⅱ 指向任意類型對象的指標都可轉換為 void* 型
ⅲ 整型常量0可轉換為任意指標類型
③ bool 類型轉換
ⅰ 算術值和指標值轉換為 bool 型
0 → false 非0 →
true
ⅱ bool 值轉換為算術類型
true → 1
false → 0
④ 枚舉成員轉換為整型
枚舉類型對象或枚舉成員可自動轉換為整型(具體類型依賴於機器和枚舉成員最大值,但至少為int)
⑤ 轉換為 const 對象
非 const 對象在初始化相關的
const 型引用時自動轉換為 const
非 const 對象的地址或指向非
const 對象的指標也可轉換為指向 const 對象的指標
⒉ 顯式類型轉換
⑴ 若可能,避免使用強制類型轉換
確實需要使用時也應盡量小心
⑵ 命名的強制類型轉換
① dynamic_cast
用於運行時類型識別(RTTI)
② const_cast
可以添加或去除指標、資料成員指標或引用的 const 特性
③ static_cast
ⅰ 可以顯式完成編譯器隱式執行的任何類型轉換
因潛在精度損失產生的編譯器警告會被關閉
· 可以用以避免不必要的隱式轉換,如
已知 d 為 double, i 為
int, 將 i*=d; 寫成 i*=static_cast<int>(d); 可省去將 i 轉換為
double 這一非必要步驟
④ reinterpret_cast
為運算元的位元模式提供較低層次的重新解釋,結果依賴編譯器和機器
任何不當使用都可能導致執行階段錯誤
⑶ 舊式強制轉換
在合法使用 static_cast 或
const_cast 的地方,提供與命名強制轉換一樣的功能
若兩種轉換均不合法,就執行 reinterpret_cast
由於不易判別每個顯式轉換的潛在風險,不推薦使用