前言
C 語言沒有物件的概念,但可以用結構及函式模擬。本文透過實例展示如何模擬物件。
C# 的例子
在寫 C 程式碼前,先用真正有類別及物件的語言,觀看一下如何操作物件。這裡以 C# 為例。
使用類別建立物件
建立物件後,就可以呼叫物件的欄位或方法:
Point p = new Point(3.0, 4.0);
Assert(3.0 == p.X);
Assert(4.0 == p.Y);
Assert(5.0 == p.Magnitude);
藉由 . 運算子,物件 p 可以和其欄位或方法連動。
也可以把物件當成資料傳遞:
Point a = new Point(1.0, 2.0);
Point b = new Point(4.0, 6.0);
double dist = Point.Distance(a, b);
Assert(5.0 == dist);
透過這兩個例子,可以看出物件如何操作。
Point 的參考實作
以下是 Point 的其中一種實作方式,給讀者參考:
using static System.Math;
public class Point
{
private readonly double _x;
private readonly double _y;
public Point(double x, double y)
{
_x = x;
_y = y;
}
public double X
{
get
{
return _x;
}
}
public double Y
{
get
{
return _y;
}
}
public double Magnitude
{
get
{
double dx = _x - 0.0;
double dy = _y - 0.0;
return Sqrt(dx * dx + dy * dy);
}
}
public static double Distance(Point a, Point b)
{
double dx = a.X - b.X;
double dy = a.Y - b.Y;
return Sqrt(dx * dx + dy * dy);
}
}
這裡把 Point 視為數學上的點,建立物件後就不能改變其位置。如果把 Point 視為資料,可以改成位置可變。
在外部程式使用物件
接下來,我們在 C 操作等效物件:
point_t p;
point_new(&p, 3.0, 4.0);
assert(3.0 == point_x(&p));
assert(4.0 == point_y(&p));
assert(5.0 == point_magnitude(&p));
建構函式跟 C# 不太一樣。這牽涉到記憶體細節。詳見下文。
同樣可以將物件當成資料傳遞:
point_t a;
point_new(&a, 1.0, 2.0);
point_t b;
point_new(&b, 4.0, 6.0);
assert(5.0 == point_distance(&a, &b));
宣告 point_t 的界面
C 語言的 header 視為界面,需要額外寫出來:
#ifndef POINT_H
#define POINT_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct point_t point_t;
struct point_t {
double _x;
double _y;
};
void point_new(point_t *self, double x, double y);
double point_magnitude(point_t *self);
double point_distance(point_t *a, point_t *b);
#define point_x(self) ((self)->_x)
#define point_y(self) ((self)->_y)
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* POINT_H */
配置記憶體
這份 header 透露了一些記憶體細節。實作 C 物件時,可以選擇直接宣告結構或用 opaque pointer 封裝。
直接宣告結構時,可以在系統堆疊上建立結構:
point_t p;
point_new(&p, 3.0, 4.0);
用 opaque pointer 時,勢必要在系統堆積配置記憶體:
point_t *p = point_new(3.0, 4.0);
一般來說,只有公開 API 需要用 opaque pointer 封裝,內部物件則不必要。
使用巨集模擬函式
這裡是巨集,而不是函式:
#define point_x(self) ((self)->_x)
使用者在編譯程式碼時,巨集會自動展開,省下函式呼叫的開銷。
point_t 的參考實作
這裡是 point_t 的其中一種實作方式:
#include <math.h>
#include "point.h"
void point_new(point_t *self, double x, double y)
{
self->_x = x;
self->_y = y;
}
double point_magnitude(point_t *self)
{
double dx = point_x(self) - 0.0;
double dy = point_y(self) - 0.0;
return sqrt(dx * dx + dy * dy);
}
double point_distance(point_t *a, point_t *b)
{
double dx = point_x(a) - point_x(b);
double dy = point_y(a) - point_y(b);
return sqrt(dx * dx + dy * dy);
}
結語
將結構視為物件導向程式的 this 指標,傳遞給函式,就可以模擬物件。這種撰碼模式在 C 語言常用,可用來組織程式碼。