C 語言程式設計教學:類別 (class) 和物件 (object)

PUBLISHED ON SEP 24, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Email Email

    C 語言沒有內建的物件導向 (object-oriented) 語法,但我們仍然可以用 C 語言寫出有物件導向思維的語法 (可見這裡)。早期就有一本經典線上教材 Object-Oriented Programming with ANSI C 整本都在講用 C 寫物件導向程式的方式 (出處),有需要的讀者可自行前往拜讀。本系列文章以簡短的文字說明搭配短例,讓讀者很快學會使用 C 寫物件導向程式的一些手法。

    註:本系列文章所用的手法和該教材不同,讀者可自行比較。

    由於 C 語言沒有內建的物件導向語法,在各種 C 語言教材中 (包括本文) 實作物件導向程式的手法都是利用 C 語言的特性去模擬出來的;這些手法沒有一定的準則 (gold standard),也不會放到 C 標準中。最重要的不是原封不動地照抄這些手法,而是理解為什麼要用這些手法,達成了什麼效果,從中慢慢建立自己慣用的方式。

    物件 (object) 是帶有狀態 (state) 和行為 (behavior) 的抽象實體,而類別 (class) 則是建立物件的藍圖;對於類別和物件,可以想像餅乾模子和餅乾的關係。狀態透過存取屬性 (fields) 來儲存;行為透過函式 (function) 或副常式 (subroutine) 來實作。至於封裝 (encapsulation)、繼承 (inheritance)、多型 (polymorphism) 等特性,則是透過不同面向來加強物件,不是必備條件。

    在本文中,我們使用二維空間的點 (point) 來展示如何製作類別和物件,在這裡我們沒有用到進階的物件導向特性,僅是一個帶有狀態和行為的簡單物件。

    我們先看 Point 物件的使用方式:

    #include <assert.h>
    #include <stdbool.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include "point.h"
    
    int main(void)
    {
        bool failed = false;
        
        // Create a Point object.
        Point* pt = point_new(0, 0);
        if (!pt) {
            perror("Failed to allocate Point pt");
            failed = true;
            goto POINT_FREE;
        }
    
        // Check x and y.
        if (!(point_x(pt) == 0)) {
            failed = true;
            goto POINT_FREE;
        }
        
        if (!(point_y(pt) == 0)) {
            failed = true;
            goto POINT_FREE;
        }
        
        // Mutate x and y.
        point_set_x(pt, 3);
        point_set_y(pt, 4);
        
        // Check x and y again.
        if (!(point_x(pt) == 3)) {
            failed = true;
            goto POINT_FREE;
        }
        
        if (!(point_y(pt) == 4)) {
            failed = true;
            goto POINT_FREE;
        }
        
    POINT_FREE:
        // Free the object.
        point_free(pt);
        
        if (failed) {
            exit(EXIT_FAILURE);
        }
    
        return 0;
    }

    這個例子相當簡單,就是透過 Point 物件 pt 存取座標點 xy,可以看得出來物件和函式有基本的連動。

    接著,我們來看 Point 類別的公開方法,在 C 語言中透過標頭檔 (header) 來宣告某個類別的公開方法:

    #ifndef POINT_H
    #define POINT_H
    
    // Declare Point class.
    typedef struct point {
        double x;
        double y;
    } Point;
    
    // The constructor of Point.
    Point* point_new(double x, double y);
    
    // The getters of Point.
    double point_x(Point *self);
    double point_y(Point *self);
    
    // The setters of Point.
    void point_set_x(Point *self, double x);
    void point_set_y(Point *self, double y);
    
    // The destructor of Point.
    void point_free(void *self);
    
    #endif // POINT_H

    最後來看 Point 類別內部的實作:

    #include <assert.h>
    #include <stdlib.h>
    #include "point.h"
    
    // The constructor of Point.
    Point* point_new(double x, double y)
    {
        Point* pt = (Point *) malloc(sizeof(Point));
        if (!pt) {
            return pt;
        }
        
        pt->x = x;
        pt->y = y;
        
        return pt;
    }
    
    // The getter of x.
    double point_x(Point *self)
    {
        assert(self);
    
        return self->x;
    }
    
    // The setter of x.
    void point_set_x(Point *self, double x)
    {
        assert(self);
    
        self->x = x;
    }
    
    // The getter of y.
    double point_y(Point *self)
    {
        assert(self);
    
        return self->y;
    }
    
    // The setter of y.
    void point_set_y(Point *self, double y)
    {
        assert(self);
    
        self->y = y;
    }
    
    // The destructor of Point.
    void point_free(void *self)
    {
        if (!self) {
            return;
        }
            
        free(self);
    }

    本例的 Point 物件相當簡單,但精神上已經是一個物件了。即使我們完全不使用進階的物件導向特性,也可以撰寫以物件為基礎的 (object-based) 程式,利用物件來組織程式碼。

    註:一般的 object-based programming 是指有封裝但沒有繼承和多型的物件,我們採更寬鬆的定義,只要有狀態和方法連動的物件即可。