C++ 是強型態的程式語言 (programming language) ,每一個變數 (variable) 都必須宣告所屬的型態 (type) ,如果變數屬於基本內建型態 (primitive built-in type) ,而在運算式 (expression) 中遇到型態不相符的情況,基本內建型態的變數會自動進行隱性型態轉換
轉換方向就是儲存範圍小的型態自動轉換成儲存範圍大的型態,字元 (character) char 或布林 (Boolean) bool 依需要會轉換成整數 (integer) ,整數型態則會依序由 short 到 long long ,如果含有浮點數 (floating-point number) 型態的運算元 (operand) ,就會轉換成 float 或 double 。
可是編譯器 (compiler) 默認反過來的情況,這是說像是最後計算結果是 int 的話,同樣可放到 short 裡,例如
001 | #include <iostream> |
002 | |
003 | using namespace std; |
004 | |
005 | int main() { |
006 | int a = 65536; |
007 | cout << a <<endl; |
008 | |
009 | short b = a; |
010 | cout << b <<endl; |
011 | |
012 | int c = b; |
013 | cout << c <<endl; |
014 | |
015 | return 0; |
016 | } |
017 | |
018 | /* type_demo.cpp |
019 | Kaiching Chang |
020 | 2014-5 */ |
此例將 int 的變數值指派給 short 的變數
009 | short b = a; |
然後再將此 short 變數值重新指派給另一個 int 變數
012 | int c = b; |
先來編譯執行看看結果囉
變數 b 是 0 ?這是因為 short 型態可以儲存的最大正整數為 65535 ,而 65536 超過 1 ,編譯器就讓 b 歸 0 了,以二進位來看就像是
1000000000000000
因為對 short 型態而言,最左端的 1 並不在儲存範圍,因此編譯器自動略去導致只有儲存右邊 15 個 0
?000000000000000
當然這是特意舉的例子,實際上從儲存範圍大的型態轉換到儲存範圍小的型態都容易造成資料遺失,編譯器的處理可能莫衷一是,無法預期。
我們可以在編譯時加上 -Wconversion 的旗標 (flag) ,看看編譯器對於這方面有什麼建議
GCC 有很多 -W 開頭的旗標,都是編譯器對程式檢查後的警告訊息。
簡單講就是會遺失精確度 (precision) ,最好的解決辦法是自己寫程式用心點,遇到型態不相符的情況都自己做好型態轉換,舊式的強制型態轉換就是用小括弧標出運算式最後計算出的型態,如上例的第 9 行應改成
009 | short b = (short) a; |
第 12 行應改成
012 | int c = (int) b; |
強制轉型並不會保留精確度,也就是 int 的 65536 到 short 變數仍是會變成 0 ,如果不希望編譯器代為處理轉換後的數字,就得自己寫關於轉型的程式設定。
這麼一來編譯重新加上 -Wconversion 旗標,就不會有警告訊息出現了
同樣的型態轉換問題發生在單元 15 裡 set_code_array() 的原始版本上,在 encrypt01.cpp 的第 25 行
025 | s += m + 97; |
完整的 encrypt01.cpp 程式碼,請參考單元 15 。
因為 s 的型態為 string ,而 m + 97 的結果卻是 int , string 的 += 對字元型態 char 多載過,可以將字元接在 string 後,也由於 char 與 int 可互通,因此這裡是先把 int 的轉換成 char ,再由 string 與 char 進行連接。
我們的範例並沒有出錯,因為 char 是從 -128 到 127 之間的整數, 'a' 的值等於 97 ,最多加 25 到 122 ,並不會超過 127 的範圍。
雖說是不會出錯,不過為了後續維護上的方便,像是要改寫成使用其他程式庫的字串型態等等,因此我們在 encrypt.cpp 的最終版本一樣加上強制型態轉換
037 | s += (char) m + DIFF; |
完整的實作檔案可以參考「範例程式碼」的 encrypt.cpp 。
這是舊式的型態轉換,稱為舊式是因為這是從 C 語言沿襲而來的強制型態轉換, C++ 還有加入用關鍵字 static_cast 等的型態轉換。
對了,最終版本的 encrypt.cpp 也把 97 換成了 DIFF ,這是用前置處理器指令 (preprocessor directive) #define 定義的常數。
前置處理 (preprocess) 是編譯器在實際編譯前所處理的工作,接下來我們繼續討論前置處理吧!
中英文術語對照
程式語言 | programming language |
變數 | variable |
型態 | type |
基本內建型態 | primitive built-in type |
運算式 | expression |
字元 | character |
布林 | Boolean |
整數 | integer |
浮點數 | floating-point number |
運算元 | operand |
編譯器 | compiler |
旗標 | flag |
精確度 | precision |
前置處理器指令 | preprocessor directive |
前置處理 | preprocess |
重點整理
- C++ 屬於強型態的程式語言,每一個變數在使用前都必須宣告所屬的型態。
- 基本內建型態的變數會在運算式中自動進行隱性型態轉換,轉換方向是由儲存範圍小的型態往儲存範圍大的型態進行,反之會損失精確度。
- GCC 編譯時加上 -Wconversion 旗標可顯示轉換警告訊息。
- 舊式強制型態轉換是用小括弧加上型態名稱。
問題與討論
- 為什麼變數都要有型態?為什麼使用變數前都要先宣告所屬的型態?
- 為什麼基本內建型態會自動進行隱性型態轉換?
- 什麼是舊式強制型態轉換,跟新式有什麼不同?
練習
- 承接上一個單元的猜數字遊戲,將猜數字遊戲改成用類別 GuessGame 設計,將類別宣告寫在 exercise2001.h 中,原本的命令列遊戲放在 Run() 裡頭。
- 承上題,實作建構函數 GuessGame() ,此建構函數接受一個參數,並以此參數設定資料成員 length ,然後呼叫 set_game() 初始化答案 answer 及紀錄猜測次數的 times ,實作程式寫在 exercise2001.cpp 中。
- 承上題,繼續實作 set_game() ,這是把原本 RandomNumber() 改成類別的成員函數版本,注意,由於需要 FindNumber() 所以也要連帶一起實作,並將測試程式寫在 exercise2002.cpp 中,印出建立 GuessGame 後的 answer 值。
the end
沒有留言:
張貼留言