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

C 語言也能做物件導向:從 Unix 設計哲學到 Linux 核心的 OO 實踐

     編輯:關於C語言

前言:OO 不屬於 C++ 專利

物件導向 (OO) 是設計思維,而非語言語法。C 雖無 class、virtual、inheritance 關鍵字,但透過結構體 + 函數指標 + 封裝慣例,完整實現封裝、繼承、多型三大支柱。Unix「一切皆檔案」與 Linux 核心 kobject/subsys 體系,正是 C 語言 OO 設計的教科書級範例。

核心模式:虛函數表 (vtable) 手工實現


// 介面定義 (基類)
struct FileOps {
    ssize_t (*read)  (void *self, void *buf, size_t len);
    ssize_t (*write) (void *self, const void *buf, size_t len);
    int     (*close) (void *self);
    off_t   (*lseek) (void *self, off_t offset, int whence);
};

// 具體實現 (衍生類:普通檔案)
struct RegularFile {
    const struct FileOps *ops;  // 指向 vtable
    int fd;                     // 封裝狀態
    char *path;
};

static ssize_t reg_read(void *self, void *buf, size_t len) {
    struct RegularFile *f = (struct RegularFile*)self;
    return read(f->fd, buf, len);
}
static ssize_t reg_write(void *self, const void *buf, size_t len) {
    struct RegularFile *f = (struct RegularFile*)self;
    return write(f->fd, buf, len);
}
static int reg_close(void *self) {
    struct RegularFile *f = (struct RegularFile*)self;
    int ret = close(f->fd);
    free(f->path);
    free(f);
    return ret;
}

// vtable 實例 (唯讀,共享)
static const struct FileOps regular_ops = {
    .read  = reg_read,
    .write = reg_write,
    .close = reg_close,
    .lseek = (off_t(*)(void*,off_t,int))lseek  // 直接用系統呼叫
};

// 建構函數
struct RegularFile *RegularFile_Open(const char *path, int flags, mode_t mode)
{
    struct RegularFile *f = calloc(1, sizeof(*f));
    if (!f) return NULL;
    f->fd = open(path, flags, mode);
    if (f->fd < 0) { free(f); return NULL; }
    f->path = strdup(path);
    f->ops = ®ular_ops;
    return f;
}

// 使用端:多型呼叫
void CopyFile(struct FileOps *src, struct FileOps *dst, size_t n)
{
    char buf[4096];
    while (n > 0) {
        size_t chunk = n < sizeof(buf) ? n : sizeof(buf);
        ssize_t r = src->read(src, buf, chunk);
        if (r <= 0) break;
        ssize_t w = dst->write(dst, buf, r);
        if (w != r) break;
        n -= r;
    }
}

Linux 核心中的 OO 設計精華

1. kobject — 所有核心物件的基類


struct kobject {
    const char *name;
    struct list_head entry;        // 父子/兄弟鏈表
    struct kobject *parent;        // 繼承關係 (目錄樹)
    struct kset *kset;             // 群組 (類別)
    struct kref kref;              // 參考計數 (共享所有權)
    const struct kobj_type *ktype; // 虛函數表 (release, sysfs_ops)
    struct kernfs_node *sd;        // sysfs 節點
};
  • 封裝:內部欄位不對外暴露,透過 kobject_init/kobject_add/kobject_put 管理生命週期。
  • 繼承:struct device、struct bus_type、struct class 皆以 struct kobject 為首欄位 (C 語言「結構體首欄位地址等於結構體地址」特性),實現 Embedding 型繼承。
  • 多型:kobj_type->release()、kobj_type->sysfs_ops->show/store() 依具體類型分派。

2. file_operations — 字元裝置驅動的介面契約


struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read)  (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    // ... 更多操作
};

驅動開發者填寫此結構體註冊 cdev_add,用戶空間 open/read/write/ioctl 經 VFS 分派至對應函數指標 — 純 C 實現的介面導向程式設計。

3. sysfs — 核心物件模型的可視化映射

/sys 目錄樹即為 kobject 層級關係的直接投影:


/sys/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
├── size
├── stat
├── queue/
│   ├── scheduler
│   └── nr_requests
└── holder/

每個目錄對應一個 kobject,屬性檔案對應 kobj_attribute (show/store 回呼),完美展示「物件圖 → 檔案系統」的雙向映射。

C 語言 OO 設計模式速查表

模式C 實現技術關鍵點
封裝不透明指標 (Opaque Pointer) + 存取函數.h 只宣告 struct Foo;,.c 定義完整結構;提供 Foo_Create/Destroy/Get/Set
單一繼承結構體首欄位嵌入基類struct Derived { Base base; ...; },(Base*)derived == derived
多重繼承多個基類嵌入 + container_of 宏container_of(ptr, type, member) 從成員指標反推物件首址
虛函數/多型函數指標表 (vtable) + 首欄位指向 vtablevtable 為 const 全域單例,建構時指派
建構/解構Type_Init(Type*) / Type_Fini(Type*) + malloc/free分離記憶體配置與初始化,支援靜態/動態/陣列分配
介面/抽象類純虛函數表 (全為函數指標的結構體)無資料成員,僅定義契約;實作者嵌入並填寫
參考計數kref / atomic_t + Get/Put最後一次 Put 呼叫 release 回呼釋放資源

現代 C 專案中的 OO 實踐案例

  • systemd:sd-bus、sd-event、Unit 物件模型,大量使用 refcount、vtable、container_of。
  • libuv:uv_handle_t 基類,uv_tcp_t/uv_timer_t/uv_fs_t 衍生,事件迴圈多型分派。
  • FFmpeg:AVClass 為核心物件模型,AVCodec/AVFormatContext/AVFilterContext 皆繼承,支援選項系統、日誌分類、版本檢查。
  • QEMU:Object 基類 (GObject 風格),DeviceClass/BusClass、屬性系統、信號/槽、動態鑄造。

結語

C 語言缺乏語法糖,反而迫使開發者直面物件模型本質:資料佈局、函數分派、生命週期、所有權轉移。Linux 核心、systemd、libuv、FFmpeg 證明:純 C 即可構建百萬行級、高效能、高可維護性的物件導向系統。學會在 C 中手寫 vtable、嵌入繼承、container_of、參考計數,不僅能讀懂頂級開源專案核心架構,更能在嵌入式、核心模組、高頻交易、遊戲引擎等「不能用 C++/Rust」的場景自信落地 OO 設計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved