【C++學習筆記】03《你的變數不是你的變數》
【C++學習筆記】05《行為定義》
在 C++ 中,邏輯運算子(Logical Operators) 主要用來處理條件判斷,邏輯運算在執行時,會伴隨隱性轉型、短路求值,以及可能發生的副作用,若不了解其運作方式,容易在條件判斷中產生非預期結果。看似簡單,但其實藏了不少容易忽略或常忘的細節。
一、bool與隱性轉型(Implicit Conversion)
在 C++ 中,bool 可以自動轉換成整數型別:
true -> 1
false -> 0
因此以下程式碼是合法的:
int a = true + true; // a = 2
int b = false + 5; // b = 5
這種轉換稱為隱性轉型,由編譯器根據「運算子與型別」自動決定。
📌 注意:
◆bool → int是允許的
◆反過來int → bool則是0為false,非0為true
◆此轉換僅適用於 C++ 內建算術型別之間,屬於標準隱性轉型(standard conversion)。
二、邏輯運算子(Logical Operators)
C++ 提供三種基本邏輯運算子:
&& // AND
|| // OR
! // NOT
使用範例:
int a = 0;
int b = 1;
if(a == 0 && b == 1)
{
cout << "true" << endl;//print "true"
}
if(a == 0 || b == 0)
{
cout << "true";//print "true"
}
! 只需要一個運算元,用來反轉布林結果:
!(3 > 2) // false
等同於
not (3 > 2)//not為替代關鍵字
int a = 0;
int b = 1;
if(!(a > b))
{
cout << "A";//print A
}
C++ 也提供對應的保留字:and/or/not關鍵字
if (a > 0 and b > 0) {}
符號 關鍵字
&& | and
|| | or
! | not
📌實務上大多仍使用符號版本,閱讀他人程式碼時需能辨識。
邏輯運算與副作用(Side Effect)
副作用在程式開發中可謂極其重要,在運算或函式呼叫過程中,除了回傳值之外,還改變了程式狀態,例如:
-修改變數的值
-改變物件狀態
-I/O操作
-呼叫具有狀態影響的函式
常見具有副作用的操作
i++; // 修改 i
++i;
i = 5;
func(); // 若func內部修改狀態
短路求值與副作用
在 C++ 中:
◆&&:若左邊為false,右邊不會執行
◆||:若左邊為true,右邊不會執行
這稱為短路求值(Short-circuit Evaluation),此特性會直接影響具有副作用的運算是否會被執行。
int i = 0;
if (false && ++i)
{
// 不會進入
}
cout << i; // i 仍為 0
📌因此,條件判斷中應避免依賴副作用來完成必要邏輯
三、運算只會一次做一件事
本節後續內容雖不全屬於邏輯運算子,但皆與「運算式求值順序、隱性轉型與條件判斷安全性」密切相關,因此一併整理。
C++ 不會同時執行多個運算,而是依序進行。但是,C++不會保證所有運算的求值順序,若語言規範未明確定義順序,結果可能因編譯器而異。
實際執行順序由以下因素決定:
-運算子優先順序
-運算子結合性(Associativity)
-隱性轉型規則
賦值運算子的順序
在C++中,賦值運算子(=)是右結合(right-associative),
這代表在同一個運算式中,會先解析右邊的賦值結果,再套用到左邊。
a = b = c = 9;
實際的解析方式等同於:
a = (b = (c = 9));
const:讓物件成為唯讀
const用來表示「初始化後不可再被修改」。
const int x = 10;
x = 20; // 編譯錯誤
使用 const 的好處:
◆避免誤修改
◆提高程式可讀性
◆讓編譯器進行更多檢查與最佳化
複合賦值運算子
複合賦值只是語法的簡化,但仍是正式運算。
a += 5; // a = a + 5
a -= 3;
a *= 2;
a /= 4;
遞增與遞減運算子
a++與++a 的差異
int a = 5;
int x = a++; // x = 5, a = 6
int y = ++a; // a = 7, y = 7
📌差異在於:
a++:先使用,再遞增
++a:先遞增,再使用
未定義行為(Undefined Behavior)
以下程式碼是錯誤示範:
a = a++;
在同一運算式中,對同一變數的修改與讀取之間沒有明確的求值順序(sequencing),因此屬於未定義行為。
未定義行為(Undefined Behavior, UB)將在下一章節單獨整理筆記。
如果你也是非本科背景,或正準備開始學習程式,希望這篇筆記能對你有所幫助。
這裡會持續記錄我在轉職過程中對程式、軟體工程與實務學習的理解,歡迎一起交流,也歡迎指教。
如果這篇內容對你有幫助,歡迎收藏或分享給正在學習程式的朋友。
這裡可能會有點誤導:
a = b = c = 9;
等同於
c = 9;
b = c;
a = b;
不過實際上應該是等同於:
a = (b = (c = 9));
所以會先計算 c = 9,由於 = 運算子的運算結果是 = 運算子右側運算式 9 的運算結果,副作用則是把 c 設為 9,所以上述運算式會變成:
a = (b = 9); // c 變成 9
同理,計算 b = 9 後,這個運算式的運算結果是 = 運算子右側的運算式 '9' 的值,副作用是把 b 設為 9,所以上述運算式會等同於:
a = 9 // b 變成 9
才會得到 a,b,c 都會是 9 的結果。
建議講述運算子的時候,運算子的運算結果與副作用是很重要的一環。像是 a++ 與 ++a 都具備同樣的副作用,會讓 a 遞增,但是差別是後置的 ++ 運算結果是 a 的原始值,但前置的 ++ 運算結果是 a+1,所以
int a = 5;
int x = a++; // x = 5, a = 6
int y = ++a; // a = 7, y = 7
等同於
int x = 5; // 因為 a++ 的計算結果是 a,
// 這個敘述結束後,a 一定會變成 6
再變成:
int y = (6+1); // 因為 ++a 的運算結果是 a+1
// 這個敘述結束後,a 一定會變成 7
只是規格書上並沒有規範 a 本身什麼時候遞增,只確保它所在的那一個 statement 結束時,一定會遞增,這才是為什麼會有未定義行為的原因,有的編譯器的確是會在 ++ 運算當下就改變 a 的值,但有的編譯器就不是,這都符合規格書。如果以『先遞增再使用』的方式來解說,就會覺得不是就已經遞增了,為什麼同一個運算式中第二次用到時會有未定義行為的疑問。
感謝你補充!我原文把 a = b = c = 9; 用三行拆寫成 c=9; b=c; a=b; 確實容易讓人誤以為是單純的逐行順序。
另外,副作用確實也是初學時最容易混淆的地方,我會補強這部分說明,避免造成誤解,謝謝指正!