Cでの疑似OOP





Cはオブジェクト指向言語ではありません。 そして、それは以下で説明するものはすべて松葉杖と自転車であることを意味します。

OOPには、カプセル化、継承、ポリモーフィズムという3つの柱があります。 以下に、これらのことをCで実現する方法を示します。



カプセル化

開発者からデータを非表示にします。 OOP言語では、通常、クラスフィールドを非表示にし、それらにアクセスするためにセッターとゲッターを作成します。 Cのデータを非表示にするために、静的キーワードがあります。これは、他の目的に加えて、変数(関数、構造)の可視性を1つのファイルに制限します。



例:

//foo.c static void foo1 () { puts("foo1"); } void foo2 () { puts("foo2"); } //main.c #include <stdio.h> int main() { foo1(); foo2(); return 0; }
      
      





コンパイラーはエラーを出します

 [main.c:(.text+0x1b): undefined reference to `foo1' collect2.exe: error: ld returned 1 exit status]
      
      





この機会を利用して、パブリックデータとプライベートデータを異なるファイルに分離し、プライベートデータへのポインタのみを構造に保持できます。 2つの構造が必要です。1つはプライベート、もう1つは作業用のメソッドとプライベートへのポインタを備えています。 オブジェクトの関数を呼び出すには、それを呼び出す構造体にポインターを渡す最初のパラメーターに同意します。



セッター、ゲッター、プライベートフィールドへのポインター、および構造を作成して削除する関数を含む構造を宣言します。

 //point2d.h typedef struct point2D { void *prvtPoint2D; int (*getX) (struct point2D*); void (*setX)(struct point2D*, int); //... } point2D; point2D* newPoint2D(); void deletePoint2D(point2D*);
      
      





ここでは、この構造で作業できるように、プライベートフィールドと関数ポインターが初期化されます。

 //point2d.c #include <stdlib.h> #include "point2d.h" typedef struct private { int x; int y; } private; static int getx(struct point2D*p) { return ((struct private*)(p->prvtPoint2D))->x; } static void setx(struct point2D *p, int val) { ((struct private*)(p->prvtPoint2D))->x = val; } point2D* newPoint2D() { point2D* ptr; ptr = (point2D*) malloc(sizeof(point2D)); ptr -> prvtPoint2D = malloc(sizeof(private)); ptr -> getX = &getx; ptr -> setX = &setx; // .... return ptr; }
      
      





現在、この構造を使用した作業は、セッターとゲッターを使用して実行できます。

 // main.c #include <stdio.h> #include "point2d.h" int main() { point2D *point = newPoint2D(); int p = point->getX(point); point->setX(point, 42); p = point->getX(point); printf("p = %d\n", p); deletePoint2D(point); return 0; }
      
      





上記のように、「コンストラクター」で2つの構造が作成され、プライベートフィールドの操作は関数を介して実行されます。 もちろん、プライベート構造体にヌルポインターを割り当てることを誰も安全にできないという理由だけで、このオプションは理想的ではありません。 ただし、1つのポインターを残すことは、すべてのデータをパブリック構造体に保存するよりも優れています。



継承

言語のメカニズムが提供されていないため、松葉杖なしで行う方法はありません。 頭に浮かぶ解決策は、単純に構造内で構造を宣言することです。 ただし、そのフィールドに直接アクセスできるようにするために、C11には匿名構造を宣言する機能があります。 これらは、gccとMicrosoftコンパイラの両方でサポートされています。 こんな感じです。

 typedef struct point2D { int x,y; } typedef struct point3D { struct point2D; int z; } point3D; #include <stdio.h> #include "point3d.h" int main() { point3D *point = newPoint3D(); int p = point->x; printf("p = %d\n", p); return 0; }
      
      





-fms-extensionsフラグを使用してコンパイルする必要があります。 したがって、名前をバイパスして構造体のフィールドにアクセスすることが可能になります。

ただし、構造と列挙のみが匿名にできることを理解する必要がありますが、匿名のプリミティブデータ型を匿名として宣言することはできません。



多型

プログラミング言語と型理論では、多型とは、異なる型のデータを処理する関数の能力を指します。 そして、このような機会は、C11で導入されたキーワード_Genericを提供します。 しかし、gccのすべてのバージョンがサポートしているわけではないことに注意してください。 型と値のペアは_Genericに渡され、コンパイル時に目的の値に変換されます。 一般的に、一度見たほうがいいです。



渡された構造のタイプを決定し、その名前を文字列として返す「関数」を作成しましょう。

 //points.h #define typename(x) _Generic((x), \ point3D : "point3D", \ point2D : "point2D", \ point3D * : "pointer to point3D", \ point2D * : "pointer to point2D" \ ) //main.c int main() { point3D *point = newPoint3D(); puts(typename(point)); return 0; }
      
      





ここでは、データのタイプに応じて、異なる値が返されることがわかります。 また、_Genericは何らかの値を返すため、関数へのポインターを返さないのはなぜですか。同じ「関数」を異なるデータ型で機能させることができます。



 //points.h double do2D(point2D *p); double do3D(point3D *p); #define doSomething(X) _Generic((X), \ point3D* : do3D, \ point2D* : do2D \ ) (X) //main.c int main() { point3D *point = newPoint3D(); printf("d = %f\n", doSomething(point)); return 0; }
      
      





これで、同じ関数を異なる構造で使用できます。



関連記事:

habrahabr.ru/post/205570

habrahabr.ru/post/154811



All Articles