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
involves 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 a static keyword, which, in addition to its other purposes, limits the visibility of a variable (function, structure) to one file.
Example:
//foo.c
static void foo1 () {
puts("foo1");
}
void foo2 () {
puts("foo2");
}
//main.c
#include
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 this opportunity, you can separate public and private data into different files, and only keep a pointer to private data in the structure. It will take two structures: one private, and the second with methods for working and a pointer to private. To call functions on the object, we agree on the first parameter to pass a pointer to the structure that calls it.
We declare a 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*);
Here, a private field and function pointers will be initialized so that you can work with this structure.
//point2d.c
#include
#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
#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 shown above, two structures are created in the "constructor", and work with private fields is carried out through functions. Of course, this option is not ideal, if only because no one is safe from assigning a null pointer to the private structure. However, leaving one pointer is better than storing all the data in a public structure.
Inheritance
as a language mechanism is not provided, therefore, crutches can not do without it. 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, C11 has the ability to declare anonymous structures. They are supported by both gcc and the microsoft compiler. It looks like this.
typedef struct point2D {
int x,y;
}
typedef struct point3D {
struct point2D;
int z;
} point3D;
#include
#include "point3d.h"
int main() {
point3D *point = newPoint3D();
int p = point->x;
printf("p = %d\n", p);
return 0;
}
You need to 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 anonymous.
Polymorphism
In programming languages and type theory polymorphism refers to the ability of a function to process data of different types. And such an opportunity provides the keyword _Generic, which was introduced in C11. But it is worth mentioning that not all versions of gcc support it. Type-value pairs are passed to _Generic, and when compiled, they are translated to the desired value. In general, it is better to see once.
Let's create a “function” that will determine the type of structure passed to 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 type of data, a different value will be returned. And since _Generic returns some value, so why not return a pointer to a function, then you can make the same "function" 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