常量是 C++ 程序中不可變的數據實體,正確理解其類型推導、內存佈局與隱式轉換規則,對編寫健壯、高效的代碼至關重要。本文系統梳理四大常量類別,揭示編譯器背後的類型判斷邏輯與常見陷阱。
一、數值常量:從字面量到類型推導
1. 整型常量的類型判斷規則
C++ 根據數值範圍與後綴自動推導類型:
| 數值範圍 | 無後綴類型 | L/l 後綴 | U/u 後綴 | UL/ul 後綴 |
| -32768 ~ 32767 | int | long | unsigned int | unsigned long |
| -2147483648 ~ 2147483647 | long | long | unsigned long | unsigned long |
| 超過長整型範圍 | long long (C++11) | long long | unsigned long long | unsigned long long |
關鍵點:十進制無符號常量不存在,但非負整數可隱式轉換給 unsigned 變量。123U 強制為 unsigned int,123LL 強制為 long long (C++11)。
2. 進制表示法
int dec = 1357; // 十進制
int oct = 020; // 八進制 (0 開頭) = 16 十進制
int hex = 0x20; // 十六進制 (0x/0X 開頭) = 32 十進制
int bin = 0b1010; // 二進制 (C++14, 0b/0B 開頭) = 10 十進制
3. 浮點常量的精度與後綴
| 形式 | 示例 | 默認類型 | F/f 後綴 | L/l 後綴 |
| 十進制小數 | 3.14, .5, 5. | double | float | long double |
| 指數形式 | 3.14e0, .314e1, 31.4e-1 | double | float | long double |
內存存儲:無論源碼寫成何種形式,編譯器均按 IEEE 754 標準化指數形式 存儲(符號位+指數位+尾數位)。VC6.0 下 double 與 long double 均佔 8 字節,GCC 下 long double 佔 12/16 字節。
二、字符常量:ASCII 編碼與轉義機制
1. 基本字符常量
- 單引號包裹單字符:
'a', 'Z', '#'
- 佔 1 字節,存儲 ASCII 碼值 (如
'A' = 65)
- 區分大小寫:
'a' (97) ≠ 'A' (65)
2. 轉義字符完整表
| 轉義序列 | 含義 | ASCII 碼 |
\n | 換行 (LF) | 10 |
\r | 回車 (CR) | 13 |
\t | 水平製表 | 9 |
\v | 垂直製表 | 11 |
\b | 退格 | 8 |
\f | 換頁 | 12 |
\\ | 反斜杠 | 92 |
\' | 單引號 | 39 |
\" | 雙引號 | 34 |
\? | 問號 (避免三字母組) | 63 |
\0 | 空字符 (字符串結束符) | 0 |
\ooo | 八進制 (1-3位) | - |
\xhh | 十六進制 (1-2位) | - |
3. 字符與整數互轉實戰
char c = 'A'; // 存儲 65
int i = c; // 隱式轉換:i = 65
char c2 = i + 32; // 'a' (97) - 大小寫轉換技巧
printf("%c %d\n", c2, c2); // 輸出: a 97
陷阱:char 是否有符號由實現定義!有符號時範圍 -128~127,無符號 0~255。跨平台代碼應顯式使用 signed char/unsigned char 或 uint8_t/int8_t。
三、字符串常量:存儲佈局與特殊操作
1. 內存結構
"abc" // 佔 4 字節:'a' 'b' 'c' '\0'
// 編譯器自動在末尾添加 '\0' (不計入字符串長度)
sizeof("abc") == 4
strlen("abc") == 3
2. 字符常量 vs 字符串常量
| 形式 | 類型 | 大小 | 示例 |
'a' | char | 1 字節 | 單字符 |
"a" | const char[2] | 2 字節 | 'a' + '\0' |
3. 續行與嵌入引號
// 反斜杠續行 (行尾不能有空格)
cout << "We must study C\
++ hard!"; // 輸出: We must study C++ hard!
// 嵌入雙引號
cout << "I say \"Thank you!\"\n"; // 輸出: I say "Thank you!"
// 雙反斜杠輸出單反斜杠
cout << "abc\\n"; // 輸出: abc\n (5字符: a b c \ n)
四、符號常量:#define 與 const 的對決
1. 宏定義常量
#define PRICE 30
#define PI 3.141592653589793
// 預處理階段文本替換,無類型檢查,不佔運行時內存
// 優點:一改全改,無運行開銷
// 缺點:無作用域,調試不可見,類型不安全
2. const 修飾的變量 (現代 C++ 首選)
const double PI = 3.141592653589793;
constexpr int MAX_SIZE = 1024; // C++11 編譯期常量
// 優點:有類型、有作用域、可調試、支持類型檢查
// constexpr 保證編譯期計算,可用於數組大小、模板參數
3. 兩者對比總結
| 維度 | #define | const/constexpr |
| 處理階段 | 預處理 | 編譯 |
| 類型安全 | 無 | 有 |
| 作用域 | 文件級 (無) | 塊/命名空間級 |
| 調試可見性 | 不可見 | 可見 |
| 內存佔用 | 無 (文本替換) | 可能佔用 (非 constexpr) |
| 指針/引用 | 不能取地址 | 可取地址 |
五、常見陷阱與最佳實踐
陷阱 1:整數字面量溢出
int a = 2147483648; // 錯誤:超過 int 範圍
long b = 2147483648L; // 正確:長整型後綴
auto c = 2147483648; // C++11 自動推導為 long/long long
陷阱 2:浮點精度比較
// 錯誤
if (x == 0.1) ...
// 正確:使用 epsilon 比較
const double EPS = 1e-9;
if (fabs(x - 0.1) < EPS) ...
陷阱 3:字符串字面量修改導致未定義行為
char *p = "hello"; // 危險:指向只讀內存區
p[0] = 'H'; // UB:運行時崩潰
// 正確
const char *p = "hello"; // 或 char arr[] = "hello";
最佳實踐清單
- 優先使用
constexpr (編譯期常量) 和 const (運行期只讀)
- 魔數全部賦予語義名稱:
const int MAX_CONNECTIONS = 100;
- 相關常量組織為
enum class 或 namespace
- 跨文件共享常量:頭文件
extern const int VAL;,源文件定義
- 避免在頭文件定義非
constexpr/inline 變量 (C++17)
總結
C++ 常量體系涵蓋 數值 (整數/浮點/進制/後綴)、字符 (ASCII/轉義/整數互轉)、字符串 (結束符/續行/嵌入)、符號 (宏/const/constexpr) 四大類。深度理解類型推導規則、內存佈局、隱式轉換陷阱,配合現代 C++ (constexpr, enum class, inline) 最佳實踐,能顯著提升代碼的可讀性、安全性與性能。