基礎決定上限。本文系統整理 if/for/switch/goto 最佳實踐、#define vs const、檔案拷貝/動態記憶體/strcpy/memcpy/sizeof 實戰代碼、復合表達式陷阱——這些是寫出正確、高效、可維護 C/C++ 代碼的肌肉記憶。
bool b; if (b) / if (!b)
int i; if (i == 0) / if (i != 0)
float f; if (fabs(f) <= EPS) // 絕不直接 == 0
int* p; if (p == nullptr) / if (p)
char c; if (c == '\0') / if (c)
if (cond) return x; else return y; // 等價於
return cond ? x : y;
for(r) for(c) sum += a[r][c] 比列優先快(空間局部性)。if (cond) for() Do(); else for() DoOther(); 比迴圈內 if 快。i < N 而非 i <= N-1:直觀且避免無符號溢位。僅支援整數類型(int, char, long, enum),不可用 float/double/指標/字串。
多層巢狀迴圈/條件的統一錯誤出口:
if (err) goto cleanup;
// ...
cleanup: release(); return err;
| 維度 | #define | const |
|---|---|---|
| 類型安全 | 無 | 編譯器檢查 |
| 除錯可見性 | 不可見 | 符號表可見 |
| 作用域 | 檔案級(無作用域) | 區塊/類別/命名空間 |
| 記憶體 | 不佔存儲(純替換) | 可能佔存儲(可優化為立即數) |
現代建議:constexpr(C++11)取代所有編譯期常量;const 修飾參數/回傳值/成員函數。
const int* p; // 指向常數的指標,*p 不可改
int* const p; // 常數指標,p 不可改
const int& r; // 常數引用,綁定後不可修改被引用物件
參數傳遞決策樹:
void f(int x)(值傳遞最快,無建構/解構)void f(const T& x)(避免拷貝,不可修改)void f(T& out) 或 void f(T* out)const T f() 防止 f() = x 這類無意義賦值。
// 正確:雙重指標或回傳指標
void GetMem(char** p, int n) { *p = (char*)malloc(n); }
char* GetMem2(int n) { return (char*)malloc(n); }
// 錯誤:單指標傳值,修改的是副本
void GetMem3(char* p, int n) { p = (char*)malloc(n); } // 呼叫端 p 仍為 NULL
char* strcpy(char* dst, const char* src) {
if (!dst || !src) return nullptr;
char* ret = dst;
while ((*dst++ = *src++) != '\0');
return ret;
}
void* memcpy(void* dst, const void* src, size_t n) {
if (!dst || !src || n == 0) return nullptr;
char* d = (char*)dst; const char* s = (const char*)src;
while (n--) *d++ = *s++;
return dst;
}
| 運算式 | 結果 | 原因 |
|---|---|---|
sizeof(char[100]) | 100 | 陣列完整大小 |
sizeof(char*) | 4 | 指標大小 |
char a[]="hello"; sizeof(a) | 6 | 含結束符 |
void f(char a[100]) { sizeof(a); } | 4 | 參數退化為指標 |
#pragma pack(1) struct {int i; char j;}; | 5 | 無對齊填充 |
d = (a = b + c) + r → 拆分為兩句,提升可讀性與除錯性。if (a < b < c) **不是**數學連鎖比較,實為 ((a < b) < c)(布林值 0/1 再比較 c),極易出錯。將這些模式刻入指尖,代碼評審時你就能一眼看出「這裡會越界」「那裡參數退化」「這個宏會副作用」——這就是資深工程師的直覺來源。