C++ 入門指南 V2.00 - 單元 20 - 型態轉換問題




C++ 是強型態的程式語言 (programming language) ,每一個變數 (variable) 都必須宣告所屬的型態 (type) ,如果變數屬於基本內建型態 (primitive built-in type) ,而在運算式 (expression) 中遇到型態不相符的情況,基本內建型態的變數會自動進行隱性型態轉換



轉換方向就是儲存範圍小的型態自動轉換成儲存範圍大的型態,字元 (character) char 或布林 (Boolean) bool 依需要會轉換成整數 (integer) ,整數型態則會依序由 shortlong long ,如果含有浮點數 (floating-point number) 型態的運算元 (operand) ,就會轉換成 floatdouble


可是編譯器 (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;

先來編譯執行看看結果囉



變數 b0 ?這是因為 short 型態可以儲存的最大正整數為 65535 ,而 65536 超過 1 ,編譯器就讓 b0 了,以二進位來看就像是


1000000000000000


因為對 short 型態而言,最左端的 1 並不在儲存範圍,因此編譯器自動略去導致只有儲存右邊 15 個 0


?000000000000000


當然這是特意舉的例子,實際上從儲存範圍大的型態轉換到儲存範圍小的型態都容易造成資料遺失,編譯器的處理可能莫衷一是,無法預期。


我們可以在編譯時加上 -Wconversion 的旗標 (flag) ,看看編譯器對於這方面有什麼建議



GCC 有很多 -W 開頭的旗標,都是編譯器對程式檢查後的警告訊息。

簡單講就是會遺失精確度 (precision) ,最好的解決辦法是自己寫程式用心點,遇到型態不相符的情況都自己做好型態轉換,舊式的強制型態轉換就是用小括弧標出運算式最後計算出的型態,如上例的第 9 行應改成


009 short b = (short) a;

第 12 行應改成


012 int c = (int) b;

強制轉型並不會保留精確度,也就是 int65536short 變數仍是會變成 0 ,如果不希望編譯器代為處理轉換後的數字,就得自己寫關於轉型的程式設定。

這麼一來編譯重新加上 -Wconversion 旗標,就不會有警告訊息出現了



同樣的型態轉換問題發生在單元 15 裡 set_code_array() 的原始版本上,在 encrypt01.cpp 的第 25 行


025 s += m + 97; 

完整的 encrypt01.cpp 程式碼,請參考單元 15

因為 s 的型態為 string ,而 m + 97 的結果卻是 intstring+= 對字元型態 char 多載過,可以將字元接在 string 後,也由於 charint 可互通,因此這裡是先把 int 的轉換成 char ,再由 stringchar 進行連接。


我們的範例並沒有出錯,因為 char 是從 -128127 之間的整數, 'a' 的值等於 97 ,最多加 25122 ,並不會超過 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

重點整理


  1. C++ 屬於強型態的程式語言,每一個變數在使用前都必須宣告所屬的型態。
  2. 基本內建型態的變數會在運算式中自動進行隱性型態轉換,轉換方向是由儲存範圍小的型態往儲存範圍大的型態進行,反之會損失精確度。
  3. GCC 編譯時加上 -Wconversion 旗標可顯示轉換警告訊息。
  4. 舊式強制型態轉換是用小括弧加上型態名稱。

問題與討論


  1. 為什麼變數都要有型態?為什麼使用變數前都要先宣告所屬的型態?
  2. 為什麼基本內建型態會自動進行隱性型態轉換?
  3. 什麼是舊式強制型態轉換,跟新式有什麼不同?

練習


  1. 承接上一個單元的猜數字遊戲,將猜數字遊戲改成用類別 GuessGame 設計,將類別宣告寫在 exercise2001.h 中,原本的命令列遊戲放在 Run() 裡頭。
  2. 承上題,實作建構函數 GuessGame() ,此建構函數接受一個參數,並以此參數設定資料成員 length ,然後呼叫 set_game() 初始化答案 answer 及紀錄猜測次數的 times ,實作程式寫在 exercise2001.cpp 中。
  3. 承上題,繼續實作 set_game() ,這是把原本 RandomNumber() 改成類別的成員函數版本,注意,由於需要 FindNumber() 所以也要連帶一起實作,並將測試程式寫在 exercise2002.cpp 中,印出建立 GuessGame 後的 answer 值。

the end

沒有留言: