基本資料型別是所有其他資料型別的基礎構件。本章涵蓋數值、整數、浮點數、字元與字串、布林變數、列舉型別、具名常數、陣列,以及自訂型別的使用技巧與常見陷阱。

12.1 數值概論#

  • 避免魔術數字(Magic Numbers):不要在程式中直接出現無解釋的數值常數(如 10047524)。使用具名常數或全域變數取代,好處有三:變更更可靠、變更更容易、程式碼更易讀。
  • 硬編碼的 0 和 1 可以接受:用於遞增、遞減或陣列起始索引的 01 是合理的。經驗法則:程式本體中唯一應出現的字面值只有 01
  • 預防除以零錯誤:每次使用除法運算子時,都應思考分母是否可能為零,並撰寫防護程式碼。
  • 明確進行型別轉換:讓讀者在型別轉換發生時一目了然,例如 C++ 的 (float) i 或 VB 的 CSng(i)
  • 避免混合型別比較:浮點數與整數的比較幾乎保證不會正確運作,應手動轉換為相同型別再比較。
  • 留意編譯器警告:頂尖程式設計師會修正程式碼以消除所有編譯器警告。

12.2 整數#

  • 注意整數除法7/10 在整數運算中通常等於 0 而非 0.710 * (7/10) 等於 0,而非 7。解決方法是重新排列運算式,將除法放在最後:(10*7) / 10
  • 注意整數溢位(Integer Overflow):乘法或加法可能產生超出最大整數值的結果。
常見整數型別範圍
  • 有號 8-bit:-128 ~ 127 / 無號 8-bit:0 ~ 255

  • 有號 16-bit:-32,768 ~ 32,767 / 無號 16-bit:0 ~ 65,535

  • 有號 32-bit:-2,147,483,648 ~ 2,147,483,647 / 無號 32-bit:0 ~ 4,294,967,295

  • 有號 64-bit:約 -9.2 x 10^18 ~ 9.2 x 10^18 / 無號 64-bit:0 ~ 約 1.8 x 10^19

  • 預防方式:逐一思考運算式中每個項目可能的最大值,評估結果是否超出範圍。若會超出,改用 64 位元整數或浮點數。

  • 注意中間結果溢位:即使最終結果在範圍內,中間運算結果也可能溢位。例如 1000000 * 1000000 / 1000000 的中間結果需容納 10^12,遠超 32 位元整數上限,導致錯誤結果 -727

  • 12.3 浮點數#

    浮點數的核心問題是:許多十進制小數無法以二進位精確表示。32 位元浮點表示法的 1/3 僅精確到 7 位數(0.33333330)。

    • 避免對數量級差異極大的數字做加減法:例如 1,000,000.00 + 0.1 可能仍等於 1,000,000.00,因為 32 位元沒有足夠的有效位數。解法是先排序再從最小值開始累加。
    • 避免浮點數的相等比較:兩條不同的運算路徑得到「應該相等」的數字,實際上往往不相等。例如 0.1 累加 10 次不等於 1.0

    應使用容差比較函式取代 ==:定義可接受的誤差範圍(如 ACCEPTABLE_DELTA = 0.00001),當兩值之差的絕對值小於此閾值時視為相等。

    • 預期捨入誤差(Rounding Errors):常見對策包括:
      • 改用更高精度的型別(如 float 改為 double
      • 改用二進位編碼十進制(BCD)變數,可防止許多捨入錯誤,尤其適用於金額計算
      • 改用整數模擬小數:例如以「分」(cents)為單位的整數取代以「元」為單位的浮點數,再建立封裝類別隱藏實作細節
    • 檢查語言與函式庫的支援:某些語言(如 Visual Basic)提供 Currency 等專用型別,若有則應善加利用。

    12.4 字元和字串#

    • 避免魔術字元與魔術字串:如同魔術數字,不應在程式中散布未解釋的字面字元或字串。應使用具名常數或集中管理的字串資源。
    • 注意差一錯誤(Off-by-One Errors):字串的索引操作如同陣列,須留意邊界。
    • 及早決定 Unicode 策略:Java 中所有字串皆為 Unicode;C/C++ 需額外處理。應儘早決定是否及何時使用 Unicode。
    • 及早決定國際化/在地化策略:考量是否將所有字串存放於外部資源檔,以及是建立各語言的獨立版本還是在執行期決定語言。
    • 統一字串型別的轉換策略:在程式內部保持單一格式,僅在輸入輸出時才轉換。

    C 語言的字串注意事項#

    C++ 的 std::string 已消除大部分 C 字串陷阱,但直接使用 C 字串時仍需注意以下事項。

    • 區分字串指標與字元陣列:字串操作應使用 strcmp()strcpy() 等函式,而非 = 運算子。
    • 宣告長度為 CONSTANT+1:為 null 結尾字元保留空間,統一以 CONSTANT 操作字串長度。
    • 初始化為 null:宣告時初始化為 { 0 },或使用 calloc() 取代 malloc()
    • 優先使用字元陣列而非指標,並使用 strncpy() 取代 strcpy() 以限制最大長度。

    12.5 布林變數#

    布林變數不易誤用,且善加利用能讓程式更清晰。

    • 用布林變數記錄程式意圖:將複雜的布林運算式指派給有意義名稱的變數,使 if 測試一目了然。例如將 (elementIndex < 0) || (MAX_ELEMENTS < elementIndex) 指派給 finished,比直接嵌入 if 條件清楚得多。
    • 用布林變數簡化複雜測試:將多層條件拆解為數個布林變數,每個代表一個獨立的邏輯概念,可大幅降低閱讀者的心智負擔。
    • 必要時自行定義布林型別:若語言沒有內建布林型別(如 C),可用 typedef int BOOLEAN;enum Boolean { True=1, False=(!True) }; 自行建立。

    12.6 列舉型別#

    列舉型別(Enumerated Types) 允許以英文單字描述一組物件的每個成員,是取代「1 代表紅色、2 代表綠色」這類做法的強大替代方案。

    使用建議說明
    提高可讀性if chosenColor = Color_Red 遠比 if chosenColor = 1 清楚,用於函式參數時效果尤其顯著
    提高可靠性編譯器能進行更嚴格的型別檢查,防止 color = Country_England 這類無意義的賦值
    提高可修改性新增列舉值只需修改型別定義並重新編譯
    作為布林的替代方案true/false 不夠表達語意時(如需區分 Status_SuccessStatus_WarningStatus_FatalError),列舉更適合
    檢查無效值ifcase 中使用 else 子句捕捉無效值
    定義 First 和 Last 項目使用 Country_FirstCountry_Last 配合明確數值,方便迴圈遍歷
    保留第一個項目為無效值將映射到 0 的第一個元素設為 InvalidFirst,有助於捕捉未正確初始化的變數

    對列舉元素指派明確數值時要小心:若值不連續(如 1, 2, 4, 8),迴圈遍歷時會經過無效值(3, 5, 6, 7)。

    • 若語言不支援列舉型別:可用類別(class)或全域變數模擬,例如 Java 中建立私有建構子的類別搭配 public static final 成員,既可讀又型別安全。

    12.7 具名常數#

    具名常數(Named Constants) 是值一旦指派就不可變更的變數,用名稱取代數字來參照固定數量(如 MAXIMUM_EMPLOYEES 取代 1000),實現程式的「參數化」與單點控制(Single-Point Control)

    • 在資料宣告中使用具名常數:例如用 LOCAL_NUMBER_LENGTH 描述電話號碼長度,而非字面值 7。當長度改變時只需修改一處。
    • 避免字面值,即使看似「安全」For i = 1 To 12 中的 12 雖然「一年不太可能變成 13 個月」,但用 NUM_MONTHS_IN_YEAR 能消除任何疑慮。更進一步可用列舉型別 Month_January To Month_December

    一致使用具名常數:在某處用具名常數、另一處用字面值代表同一實體,是極為危險的做法。修改常數定義時會遺漏硬編碼的字面值,導致隱蔽的缺陷。

    • 若語言不支援具名常數:以適當作用域的變數或類別模擬,優先順序為區域 > 類別 > 全域。

    12.8 陣列#

    使用建議說明
    確保索引在陣列邊界內陣列的所有問題歸根結底都與隨機存取有關,最常見的就是越界存取
    考慮使用容器取代陣列集合、堆疊、佇列等循序存取結構較不易出錯,研究顯示能提高軟體可靠性
    檢查陣列端點第一個元素、最後一個元素是否正確存取?有無差一錯誤?
    多維陣列的下標順序Array[i][j]Array[j][i] 很容易搞混,應仔細確認
    避免索引串擾巢狀迴圈中容易將 Array[j] 寫成 Array[i],使用有意義的索引名稱可降低風險
    在 C 中使用 ARRAY_LENGTH() 巨集#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) 可自動計算陣列長度

    12.9 建立你自己的型別(型別別名)#

    自訂型別是語言能賦予你的最強大能力之一,能讓程式更易讀、更易修改,且不需要設計新類別。

    使用建議說明
    核心概念透過 typedef(C/C++)或等效機制建立語意化型別,日後需改型別時只需修改一行
    資訊隱藏某些語言(如 Ada)支援將型別宣告為 private,真正隱藏底層實作。C++ 的 typedef 僅提供形式上的隱藏
    使用面向問題領域的名稱CoordinateCurrencyAge 等名稱,避免 BigIntegerLongString 這類描述電腦資料的名稱
    不要重新定義預定義型別自訂一個名為 Integer 的型別會造成混淆
    為可攜性定義替代型別例如定義 INT32 取代 int,在不同平台上可重新定義以保持一致
    考慮建立類別而非 typedef當需要更多控制與彈性時,類別是更好的選擇

    要點#

    • 每種基本資料型別都有各自的使用規則與陷阱,應善用檢核清單確認常見問題。
    • 自訂型別能讓程式更易修改且更具自我文件性。
    • 當使用 typedef 或等效機制建立簡單型別時,應考慮是否需要建立完整的類別來獲得更好的封裝。