作者Feis (永遠睡不著 @@)
看板C_and_CPP
標題[分享] Boolean in C/C++
時間Tue Feb 18 07:14:54 2014
前面幾篇有提到 bool 的問題。今天突然失眠,心血來潮,來分享一下 bool 的故事。
當然內容算是比較基礎而有許多主觀認知的,有什麼遺漏錯誤還煩請指正。
[關於 Boolean (布林)]
Boolean (布林) 作為一種資料型別 (data type) 時只具有兩種值: 『真』(true) 與
『偽』(false)。
Boolean 可以進行的運算包含:而且 (and)、或 (or) 與非 (not) ,還有由這三種基本
運算延伸出來的各式邏輯運算。
基本上, Boolean 是不能做一般我們認為的算術運算 (+, -, *, /, ...) 的。
Boolean 在一般程式語言中最重要的角色是作為『流程控制』的條件判斷。
在 C/C++ 裡面,if、for 和 while 等流程控制都仰賴於條件的 Boolean 運算結果。
[在 C89 裡面的 Boolean]
雖然 Boolean 對於『流程控制』具有巨大的重要性,但在 C89 內並沒有直接替
Boolean 設定一個專有的資料型別。
在 C89 中 Boolean 是隱性地跨越多個型別、運算以及語法中實現。
首先,C89 替內建型別所具有的 Boolean 值做了規定:
* 數值型別的 Boolean 值:
0 的值為『偽』,其他的值為『真』。
* 指標型別的 Boolean 值:null 的值為『偽』,其他的值為『真』
(剛好
0 當指標值時是代表 null 指標)
if (
0) {
printf(
"True");
// 不會印 True.
}
if (
0.1) {
printf(
"True");
// 會印 True.
}
這個決定讓我們不必產生一個 Boolean 的專有型別,因為內建型別都可以當做
Boolean 用。
而且,
0 為 『偽』,非
0 為『真』也是一種容易記誦的方式, 使人不由得敬佩設計者
的巧思。
再來,C89 規定了關係 (>, <, ==, ...) 與邏輯 (&&, ||, ...) 等運算的結果值。 當
結果為『真』時會算出
int 型別的
1,而當結果為『偽』時會算出
int 型別的
0。
將上面稍微整理一下,C89 共做了:
1. 判斷內建型別的 Boolean 值時,將
0 作為『偽』而非
0 作為『真』。
2. 運算結果為『偽』時算出
int 的
0,而為『真』時算出
int 的
1。
以上這兩點造成我們容易覺得 Boolean 在 C89 裡面就是
int,但這是一個常見的誤解。
什麼意思呢?
如果我們寫成:
typedef int bool;
// 把 bool 定義成 int
bool a =
0.1;
if (a) {
printf(
"True");
// 此時不會印出 True,因為 a 的值為 0 (偽)
}
將
0.1 轉型成
int 會是
0,但是將
0.1 解釋成 Boolean 時因為是非
0 值所以應該要
是『真』(
1)。
所以
int 是不能直接替代 Boolean 的。
此外,C89 並沒有替 Boolean 提供專用的型別還有很多缺點:
因為 Boolean 運算的結果是用
int 的
1 與
0 來表示真偽,使得將 Boolean 運算結果
作為整數運算的技巧大量地應用在 C89 以前的程式碼裡,因此產生了一些程式碼不直觀
的惡夢。例如:
if (
0 ==
0 ==
0) {
printf(
"True");
// 不會印 True
}
if (
4 >
3 >
2) {
printf(
"True");
// 不會印 True
}
如果再搭配了逐位元運算, 儲存 Boolean 所需要的空間大小可以由
int 再濃縮
成 1 bit,使得 Boolean 在 C89 中達到使用空間的大幅縮減。
但以上兩個技巧遺留下了大量無法撼動要依賴 Boolean 值是
1 或
0 的程式碼。
[在 C++98 中的 Boolean]
作為一個新的語言,C++98 選擇了一個比較直接的決定:
* 將
bool 作為 Boolean 型別。
* 將
true 與
false 作為
bool 的字面常數。
* 當運算結果為『真』時,算出
bool 型別的
true。
* 當運算結果為『偽』時,算出
bool 型別的
false。
乍看之下似乎得到了解脫,因為我們有了表示 Boolean 的
bool 型別了。
不幸的是,為了跟大量原有的 C 程式碼相容,使得 C++98 面臨到一些困難:
1. 需要引入三個新的保留字:
bool 、
true 和
false 。 使得與原有的 C 程式碼可能
會造成名稱衝突。
(C99 不這麼做,後面會提)
2. 因為假設 Boolean 為『真』時算出
1 且為『偽』時算出
0 的原有 C 程式碼太多,
C++98 只好允許
true 可以被隱性轉型成
int 的
1 ,而
false 可以被隱性轉型成
int 的
0 。此外,數值或指標型別也都要能隱性轉成
true 或
false 以符合在 C89
裡面的用法。
這樣的作法使得
bool 用起來跟整數型別相似,造成我們之前的惡夢還是存在 (可以
參考與 Java 的差異),而這對於要求型別安全的 C++ 來說更是恐怖。
(C/C++ 各版本都有的問題)
3. 為了得到以前使用位元運算將 Boolean 表示為 1 bit 的效用可以直接使用在
bool
上,C++98 對 vector<
bool> 做了特製化,使得每個
bool 在這 vector 中都只耗用
1 bit,但也產生了使用上的問題或多執行緒的惡夢。
(C++ 各版本都有的問題)
4. 有些時候,我們想將物件作為流程控制或邏輯運算的條件,並依照物件的狀態決定流
程的進行。
例如:
Object a;
if (a) {
printf(
"True");
// 如果 a 物件符合期望就印
}
因此我們需要讓物件可以轉型成
bool。但是讓物件可以隱性轉型成
bool 的風險太
大,因為這意味著物件可以隱性成整數型別。
設計師為了避免將一般物件被誤當成整數用,原本想讓物件可以隱性轉型成
bool 型
別只好改為轉型成指標型別。
例如: std::basic_ios::operator void *() const;
(C++11 有部分解決, 後面會提)
[C99 的 Boolean]
為了跟 C89 的相容性, C99 並沒有選擇跟 C++98 一樣的道路去引進三個新的保留字,
而是:
* 將
_Bool 作為 Boolean 的型別,
* 在
stdbool.h 中設定了四個 macro:
- 將
bool 定義為
_Bool,
- 將
true 定義為
1,
- 將
false 定義為
0,
- 將 __bool_true_false_are_defined 定義為
1。
引入了叫
_Bool 的保留字作為 Boolean 的型別名稱。因為名字太奇怪, 不容易跟原有
C 程式碼撞名,而不會遇到跟 C++98 一樣的窘況。
將
bool,
true 和
false 設計為 macro,使得我們在程式碼中可以有條件的選擇是否用
這三個名稱。 也就是說,如果你不
#include <stdbool.h> 的話,
bool 、
true 和
false 預設是未定義的。這樣可以避免跟原有程式碼撞名,或有必要時做一些處理。
此外,你可以發現,因為是使用 macro,Boolean 運算的結果依然是
int 型態,這點使
得一般的運算結果跟 C89 是一致的。
換句話說,C99 解決 C89 問題的作法是提供的一個真正的 Boolean 型別, 但是其他都
沒有真的改。而
_Bool 的用途主要是讓剛剛錯誤的程式碼正常執行:
_Bool a =
0.1;
// a 會是『真』而不是 0
if (a) {
printf(
"True");
// 會印出 True
}
[C++11 的 Boolean]
C++11 的
bool 大致上依循 C++98 的腳步,但多了一點調整:
之前我們不敢讓物件隱性轉型成
bool ,因為怕被誤當做整數用。但是現在我們可以用
C++11 的 explicit cast operator 來讓物件只能顯性轉型成
bool。
explicit cast operator 的意思是,原本我們在類別裡面自定的轉型運算子不能被指定
為只提供顯性轉型。也就是:
class Object {
operator bool()
const;
// 轉型成 bool 的運算子
};
提供上面的轉型運算子意味著:
Object a;
a + 1;
// 是合法的,a 會被隱性轉型成 bool 後轉型成 int 運算
這樣並不好,讓物件可以當整數用簡直是惡夢!
我們可以使用 C++11 提供的 explicit cast operator 規定我們使用轉型運算子時需要
是顯性的:
class Object {
explicit operator bool()
const;
};
如果對這個類別使用之前的程式碼:
Object a;
a + 1;
// 編譯失敗,因為 a 無法隱性轉型成 bool 或整數
當我們真的需要將 a 轉型成
bool 時需要使用顯性轉型:
Object a;
if ((
bool)a) {
// 顯性轉型成功
printf(
"True");
}
但是這樣用起來又有點麻煩,所以 C++11 讓流程控制或邏輯運算時的條件具有顯性轉成
bool 的語意 (其他時候則不會)。
換句話說,在 C++11 裡面,
Object a;
if (a) {
printf(
"True");
}
會自動解釋成
Object a;
if ((
bool)a) {
printf(
"True");
}
這樣就達到讓物件要轉型成
bool 需要是顯性的, 但是使用在邏輯運算或做流程控制
時,又不需要一直寫顯性轉型的程式碼。
但是其他的時候,因為 a 不能被隱性轉型成
bool,而得到更多的型別安全。
--
以上..
寫得很主觀, 細節沒仔細查證, 應該也有很多疏漏的.
歡迎與感謝大家的意見...
--
推 jenny2921:失眠首推 02/18 07:26
推 user1120:推! 02/18 11:37
推 gsn955206:推 02/18 16:01
推 lc85301:詳細推 02/18 19:35
推 mabinogi805:詳細推上!! 02/18 20:37
推 suhorng:推 (突然有點希望所有的轉型都必須是explicit XD) 02/18 20:47
推 LPH66:推一個 (如果真照樓上說的話 1+0.1 就編不過了) 02/18 21:26
→ suhorng:是阿, 也可以寫1.0+0.1 XD 02/18 21:45
→ purincess:樓上讓我想到我曾經想規定自己寫超嚴謹的C,例如 02/18 22:41
→ purincess:float a = 0.3; <- 規定自己不能這樣寫 02/18 22:41
→ purincess:float a = 0.3f; <- 這樣才可以 02/18 22:41
→ purincess:還有只要是assign進去unsigned type的integer literal一 02/18 22:42
→ purincess:定要寫u之類 02/18 22:42
→ purincess:然後結論是: 還好我現在工作大多用python... 02/18 22:42
→ purincess:(?) 02/18 22:42
推 sa074463:給推!! 02/18 22:45
※ 編輯: Feis 來自: 140.112.29.148 (02/19 17:21)
推 singlovesong:python ipython 無敵 bj4 lol.. 02/19 18:00
推 sunev:別再把boolean翻譯成「布林」了... 02/21 00:59
→ AnyaAlstreim:不然要翻譯成什麼? 02/21 05:40
→ suhorng:布爾? 02/21 12:59
推 EdisonX:好奇 boolean 該翻什麼+1 ? 我也一直以為是布林(boring ?) 02/21 22:10
→ suhorng:bool-ean,翻成布爾就好了吧~ 02/21 22:35
→ suhorng:就像我們不會把 Gaussian-Jordan elimination 翻成高纖 02/21 22:35
→ suhorng:喬丹消去法一樣 02/21 22:36
推 ot32em:推!! 02/21 23:44
推 EdisonX:嗯,只能說高中課本裡寫的是布林,所以才會有這窘境吧 02/22 00:40
推 BlazarArc:高纖 XD 02/22 02:08
→ suhorng:Guassian不念"高斯",Hamiltonian也不念"漢米頓"~ 02/22 13:01
→ suhorng:Boolean本來就不唸布爾。 02/22 13:01
推 yoco315:高中的時候看過最慘的,電腦書上寫「有材運算」 03/18 02:52
→ yoco315:看半天看不懂什麼是「有材運算」 03/18 02:52
→ yoco315:有沒有有材運算的八卦? 03/18 02:53
→ purincess:應該是打布林的人打倉頡打錯之類 (? 03/19 01:34