優秀的 C 函數庫應盡量降低與調用方的耦合。既然「程序 = 數據結構 + 算法」,庫內部勢必包含大量結構體定義,但對外接口應避免暴露結構體細節,參數優先使用基本類型,實現信息隱藏與編譯防火牆效果。
在頭文件中前向聲明結構體,僅對外暴露指針類型:
/* MySDK.h - 對外接口 */
typedef struct _Window Window; // 不完整類型聲明
Window* CreateWindow(void);
void ShowWindow(Window* pWin);
/* MySDK.c - 內部實現 */
struct _Window {
int width, height, x, y;
unsigned char color[3];
int isShow;
};
Window* CreateWindow(void) {
Window* p = malloc(sizeof(struct _Window));
if (p) { /* 初始化欄位 */ }
return p;
}
優勢:編譯器仍能進行類型檢查(防止傳入錯誤指針類型),調用方無法訪問結構體成員,強制通過 API 操作。
原理:C 編譯器對不完整類型的指針操作不需要知道完整佈局,僅需類型名稱匹配。
使用整數或 `void*` 作為間接引用,內部維護「句柄 → 物件」映射表:
/* 對外接口 */
typedef int HANDLE; // 或 void*
HANDLE CreateWindow(void);
void ShowWindow(HANDLE hWnd);
經典應用:Win32 API (`HWND`)、Linux 文件描述符 (`int fd`)、OpenGL 紋理對象 (`GLuint`)。
| 維度 | 不透明指針 | 句柄 |
|---|---|---|
| 類型安全 | 編譯期強類型檢查 | 僅基本類型檢查,易誤傳不同對象句柄 |
| 安全性 | 調用方可獲取記憶體地址,潛在風險 | 地址不外洩,防逆向、防誤改 |
| 跨平台 | 32/64 位指針長度不同,需條件編譯 | 定長整數(如 32 位)天然跨平台 |
| 效能 | 直接訪問,零開銷 | 需查表映射,微小額外開銷 |
| 頭文件乾淨度 | 需前向聲明 | 無需任何聲明,極簡 |
實踐中,許多成熟庫(如 SQLite 的 `sqlite3*`、libuv 的 `uv_loop_t*`)均採用不透明指針,在類型安全與實現簡潔間取得平衡。