はじめに
今回はオブジェクト指向言語で使われる”クラス”を自分なりにC言語で実装してみました。
どうすればできるかなーといった興味本位によるものですので、実用性等は無いに等しいことを予めご了承ください。
要件定義
今回実装するにあたって、以下の要件を定義した。
- インスタンス変数とインスタンスメソッドを定義できる
- カプセル化
- 継承可能
- ポリモーフィズム
実際に作成したコード
#include <stdio.h>
//親クラス
typedef struct _Parent{
//インスタンス変数
int _x;
int _y;
//インスタンスメソッド
void(*initialize)(struct _Parent*,int,int);
int(*get_x)(struct _Parent*);
int(*get_y)(struct _Parent*);
void(*set_x)(struct _Parent*,int);
void(*set_y)(struct _Parent*,int);
int(*calc)(struct _Parent*);
} Parent;
//子クラス
typedef struct _Child{
//親クラスの継承
Parent parent;
void(*initialize)(struct _Child*,int,int);
int(*get_x)(struct _Child*);
int(*get_y)(struct _Child*);
void(*set_x)(struct _Child*,int);
void(*set_y)(struct _Child*,int);
int(*calc)(struct _Child*);
} Child;
Parent new_Parent();
void initialize_Parent(Parent*,int,int);
int get_x_Parent(Parent*);
int get_y_Parent(Parent*);
void set_x_Parent(Parent*, int);
void set_y_Parent(Parent*, int);
int calc_Parent(Parent*);
Child new_Child();
void initialize_Child(Child*,int,int);
int get_x_Child(Child*);
int get_y_Child(Child*);
void set_x_Child(Child*, int);
void set_y_Child(Child*, int);
int calc_Child(Child*);
int main(void) {
Parent parent = new_Parent();
parent.initialize(&parent,5,9);
parent.calc(&parent);
Child child = new_Child();
child.initialize(&child,4,8);
child.calc(&child);
return 0;
}
//new関数
Parent new_Parent(){
Parent parent = {-1,-1,initialize_Parent,get_x_Parent,get_y_Parent,set_x_Parent,set_y_Parent,calc_Parent};
return parent;
}
//初期化メソッド
void initialize_Parent(Parent* self, int x, int y){
self->_x=x;
self->_y=y;
}
//ゲッター
int get_x_Parent(Parent* self){
return self->_x;
}
int get_y_Parent(Parent* self){
return self->_y;
}
//セッター
void set_x_Parent(Parent* self, int x){
self->_x=x;
}
void set_y_Parent(Parent* self, int y){
self->_y=y;
}
//calcメソッド
int calc_Parent(Parent *self){
printf("x+y=%d\n",self->_x+self->_y);
return self->_x+self->_y;
}
Child new_Child(){
Child child = {new_Parent(),initialize_Child,get_x_Child,get_y_Child,set_x_Child,set_y_Child,calc_Child};
return child;
}
void initialize_Child(Child* self, int x, int y){
((Parent*)self)->initialize(((Parent*)self),x,y);
}
int get_x_Child(Child* self){
return ((Parent*)self)->_x;
}
int get_y_Child(Child* self){
return ((Parent*)self)->_y;
}
void set_x_Child(Child* self, int x){
((Parent*)self)->_x=x;
}
void set_y_Child(Child* self, int y){
((Parent*)self)->_y=y;
}
//オーバーライド
int calc_Child(Child* self){
printf("x*y=%d\n",((Parent*)self)->_x*((Parent*)self)->_y);
return ((Parent*)self)->_x*((Parent*)self)->_y;
}
解説
##クラスの定義
//親クラス
typedef struct _Parent{
int _x;
int _y;
void(*initialize)(struct _Parent*,int,int);
int(*get_x)(struct _Parent*);
int(*get_y)(struct _Parent*);
void(*set_x)(struct _Parent*,int);
void(*set_y)(struct _Parent*,int);
int(*sum)(struct _Parent*);
} Parent;
関数ポインタを構造体のメンバに加えることでメソッドを表現した。
関数ポインタの書き方は関数の戻り値の型(*変数名)(関数の引数の型)
オブジェクトへのアクセサは命名規則で表現した。
javascriptと同様に先頭に'_'をつけるとプライベートな変数としている。
new関数
Parent new_Parent(){
Parent parent = {-1,-1,initialize_Parent,get_x_Parent,get_y_Parent,set_x_Parent,set_y_Parent,sum_Parent};
return parent;
}
インスタンス化はクラスごとにnew関数を作ることで表現した。
構造体はデフォルト値を持つことができないため、クラスをnewするときにメソッドの実体を与えている。
今回の関数では仕様上、インスタンス変数には必ずデフォルト値が必要となるため、-1を初期値として代入している。
C言語は関数のオーバーロードができないため、関数名をnewで統一できなかったのが唯一の心残り。
メソッドの定義
//初期化メソッド
void initialize_Parent(Parent* self, int x, int y){
self->_x=x;
self->_y=y;
}
//ゲッター
int get_x_Parent(Parent* self){
return self->_x;
}
int get_y_Parent(Parent* self){
return self->_y;
}
//セッター
void set_x_Parent(Parent* self, int x){
self->_x=x;
}
void set_y_Parent(Parent* self, int y){
self->_y=y;
}
//calcメソッド
int calc_Parent(Parent *self){
printf("x+y=%d\n",self->_x+self->_y);
return self->_x+self->_y;
}
全てのメソッドは第一引数でレシーバのポインタをselfとして受け取る(ちょっとPythonっぽい)。
継承
typedef struct _Child{
Parent parent;
void(*initialize)(struct _Child*,int,int);
int(*get_x)(struct _Child*);
int(*get_y)(struct _Child*);
void(*set_x)(struct _Child*,int);
void(*set_y)(struct _Child*,int);
int(*sum)(struct _Child*);
} Child;
Child new_Child(){
Child child = {new_Parent(),initialize_Child,get_x_Child,get_y_Child,set_x_Child,set_y_Child,calc_Child};
return child;
}
void initialize_Child(Child* self, int x, int y){
((Parent*)self)->initialize(((Parent*)self),x,y);
}
int get_x_Child(Child* self){
return ((Parent*)self)->_x;
}
int get_y_Child(Child* self){
return ((Parent*)self)->_y;
}
void set_x_Child(Child* self, int x){
((Parent*)self)->_x=x;
}
void set_y_Child(Child* self, int y){
((Parent*)self)->_y=y;
}
//オーバーライド
int calc_Child(Child* self){
printf("x*y=%d\n",((Parent*)self)->_x*((Parent*)self)->_y);
return ((Parent*)self)->_x*((Parent*)self)->_y;
}
スーパークラスのインスタンスをサブクラスにメンバとして保持させることでクラスの継承を表現した。
スーパークラスの各メンバへはselfをアップキャストしてアクセスする。
ポリモーフィズム
int main(void) {
//インスタンス化
Parent parent = new_Parent();
//初期化
parent.initialize(&parent,5,9);
//メソッド呼び出し
parent.calc(&parent); // => x+y=14
//インスタンス化
Child child = new_Child();
//初期化
child.initialize(&child,4,8);
//メソッド呼び出し
child.calc(&child); // => x*y=32
return 0;
}
メソッドのオーバーライドが可能なのでポリモーフィズムも担保されている。