首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

《C專家編程》精華筆記(前兩章):ANSI C標準、const陷阱、整型提升、斷言藝術

     編輯:關於C

第一章:編譯器設計哲學與 ANSI C 標準精讀

1. 效率幾乎就是一切

編譯器設計者的金科玉律:在正確性前提下極致追求效率。這解釋了許多看似怪異的優化行為(如刪除無副作用代碼、重排指令)。

2. 閱讀 ANSI C 標準的樂趣與裨益

標準文檔(如 C89/C99)是解決語言律師級爭議的最終權威。重點關注:

  • 類型兼容性規則:char* 可作為參數傳遞,但 char** 不能隱式轉換為 const char**(防止通過雙重指針修改常量)。
  • 多維數組傳遞:必須指明前 n-1 維大小,如 void f(int a[][10]),因為編譯器需計算行偏移。

第二章:const 迷宮完全導航

const 是 C 語言最易誤用的關鍵字之一,核心在於區分指針本身與指向的數據。

四種核心組合


const int *p;       // (1) 指向常量的指針:數據只讀,指針可變
int *const p;       // (2) 常指針:指針只讀,數據可讀寫
const int *const p; // (3) 雙重 const:指針與數據均只讀
int *p;             // (4) 普通指針:雙方均可讀寫

記憶口訣:讀聲音法

從變量名向左讀:
const int *p → p 是 pointer to int const → 指向常量的指針
int const *p → 同上(等價)
int *const p → p 是 const pointer to int → 常指針

賦值兼容性矩陣(✅ 合法 / ❌ 非法)

源類型 \ 目標類型int*const int*int* constconst int* const
int*✅✅❌❌
const int*❌✅❌✅
int* const✅✅✅✅
const int* const❌✅❌✅

核心原則:只能增加 const 資格,不能減少。將「指向常量的指針」賦值給「普通指針」會打破只讀承諾,故非法。

實戰代碼演示


int main() {
    const int x = 2, y = 3;
    int z = 3;
    
    const int *pc = &x;     // 指向常量的指針
    int *const cp = &z;     // 常指針
    int *pp = &z;           // 普通指針
    
    pc = &z;        // ✅ pc 可指向非常量
    // *pc = 10;    // ❌ 不可通過 pc 修改數據
    pc = pp;        // ✅ 普通指針 → 指向常量指針(增加 const)
    // pp = pc;     // ❌ 指向常量指針 → 普通指針(減少 const)
    *cp = x;        // ✅ 通過常指針修改數據合法
    // cp = &x;     // ❌ 常指針不可修改指向
    // pp = &x;     // ⚠️ 實現定義(某些編譯器警告)
    pp = cp;        // ✅ 常指針 → 普通指針(指針值拷貝,不涉及 const 轉換)
    
    const int *const cpc = &x; // 雙重 const:終極保護
    return 0;
}

第三章:整型提升與尋常算術轉換——隱形殺手

C 語言整型運算遵循嚴格的類型提升規則,違反直覺是大量 Bug 溫床。

關鍵規則

  1. 整型提升:char、short、bool、位域等「窄類型」參與運算前自動提升為 int(或 unsigned int)。
  2. 尋常算術轉換:雙目運算符兩操作數轉換為同一類型,規則優先級:
    long double > double > float > unsigned long long > long long > unsigned long > long > unsigned int > int。
  3. 有符號/無符號混合:若無符號類型秩 ≥ 有符號類型,有符號轉為無符號(導致負數變巨大正數)。

經典陷阱:sizeof 表達式類型


#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))
// sizeof 結果為 size_t (無符號)
// 與有符號 int 比較時,int 被轉為無符號
// 若 array 為空,結果為 0,但負數索引轉為巨大無符號數 → 死循環/越界

// 修正:強制轉換為有符號
#define TOTAL_ELEMENTS (int)(sizeof(array) / sizeof(array[0]))

第四章:斷言藝術與常見錯誤模式

malloc 字符串空間錯誤


// ❌ 錯誤:漏掉結尾 \0
char *p = malloc(strlen(str)); 

// ✅ 正確:+1 容納 \0
char *p = malloc(strlen(str) + 1);

字符串數組初始化漏逗號


char *str[] = {
    "aaa", "bbb", "ccc", "ddd"
    "eee"  // ← 漏掉逗號!編譯器不報錯,自動拼接為 "dddeee"
};
// 結果:數組長度 4 而非 5,邏輯錯誤難排查

switch case 缺失 break

97% 的無 break 是錯誤。且 default 拼寫錯誤編譯器不檢查(視為普通標籤)。

逗號運算符陷阱


i = 1, 2;   // 等價於 (i = 1), 2 → i = 1
i = (1, 2); // 逗號表達式取右值 → i = 2

賦值運算符優先級高於逗號運算符。

靜態局部變量實現「首次執行不同」


void init_once() {
    static int initialized = 0;
    if (!initialized) {
        // 首次執行初始化邏輯
        initialized = 1;
    }
    // 後續執行邏輯
}

總結:專家思維清單

  1. 隨時查閱標準文檔,不依賴「經驗」或「某編譯器行為」。
  2. const 讀聲音法,賦值只能加 const 不能減。
  3. 整型提升/轉換規則刻入肌肉記憶,混合符號運算必加審視。
  4. sizeof 結果無符號,比較時強轉。
  5. 斷言用於檢測「不可能發生」的邏輯錯誤,非錯誤處理替代品。
  6. 字符串數組初始化逐行檢查逗號,啟用 -Wmissing-braces 等警告。
  7. switch 每個 case 必加 break 或註釋 /* falls through */。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved