⬆️ ⬇️

Pseudo OOP in C





The C language is not an object-oriented language. And that means everything that will be described below is crutches and bicycles.

OOP includes three pillars: encapsulation, inheritance, polymorphism. Below I will show how these things can be achieved in C.



Encapsulation

implies hiding data from the developer. In OOP languages, we usually hide class fields, and to access them we write setters and getters. To hide data in C, there is the keyword static, which, among other things, restricts the visibility of a variable (function, structure) to a single file.



Example:

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


The compiler gives an error

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


Having such an opportunity, you can separate public and private data by different files, and only a pointer to private data is stored in the structure. It will take two structures: one private, and the second with methods for working and a pointer to private. In order to call functions on an object, we agree on the first parameter to pass a pointer to the structure that calls it.



We will declare the structure with setters, getters, and a pointer to a private field, as well as functions that will create the structure and delete it.

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


The private field and function pointers will be initialized here so that you can work with this structure.

 //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; } 


Now, work with this structure can be done using setters and getters.

 // 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; } 


As was shown above, in the “constructor” two structures are created, and work with private fields is carried out through functions. Of course, this option is not perfect, if only because no one is immune from assigning a null pointer to a private structure. However, leaving one pointer is better than storing all the data in a public structure.

')

Inheritance

as a mechanism of language is not provided, so there is no way to do without crutches. The solution that comes to mind is simply to declare the structure within the structure. But in order to be able to access its fields directly, there is an opportunity to declare anonymous structures in C11. They are supported by both gcc and the compiler from microsoft. It looks like this.

 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; } 


Compile with the -fms-extensions flag. Thus, it becomes possible to access the fields of the structure bypassing its name.

But we must understand that only structures and enumerations can be anonymous, but we cannot declare anonymous primitive data types as anonymous.



Polymorphism

In programming languages ​​and type theory, polymorphism refers to the ability of a function to process data of different types. And this feature provides the keyword _Generic, which was introduced in C11. But it is worth mentioning that not all versions of gcc support it. In _Generic, type-value pairs are passed, and when compiled, they are translated to the desired value. In general, it is better to see once.



Create a “function” that will determine the type of structure passed into it, and return its name as a string.

 //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; } 


Here you can see that, depending on the data type, a different value will be returned. And since _Generic returns some value, so why not return the pointer to the function, then you can get the same “function” to work with different data types.



 //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; } 


Now the same function can be used with different structures.



Related articles:

habrahabr.ru/post/205570

habrahabr.ru/post/154811

Source: https://habr.com/ru/post/263547/



All Articles