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

PUBLISHED ON SEP 24, 2018 — PROGRAMMING

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 是指有封裝但沒有繼承和多型的物件,我們採更寬鬆的定義,只要有狀態和方法連動的物件即可。

comments powered by Disqus