How to Implement Object‑Oriented Programming in C: From Encapsulation to Polymorphism
This article explains why and how to apply object‑oriented programming concepts such as encapsulation, inheritance, and polymorphism using plain C, provides the necessary prerequisites, and walks through complete code examples—including virtual tables—to demonstrate OOP techniques without relying on C++.
Why Use C for Object‑Oriented Programming
Although C is traditionally a procedural language, many object‑oriented concepts existed early in its evolution and were used in operating system kernels and communication protocols. Implementing OOP in C can yield higher efficiency and lower resource consumption, which is valuable for MCU development.
Prerequisites
To implement OOP in C you need a solid understanding of structs, functions, pointers, function pointers, and the concepts of base classes, derived classes, inheritance, and polymorphism.
Encapsulation
Encapsulation groups data and related functions into a single unit. In C, standard I/O functions illustrate this idea: fopen() acts like a constructor, fclose() like a destructor, and fread() / fwrite() perform operations on the FILE object.
#ifndef SHAPE_H
#define SHAPE_H
#include <stdint.h>
/* Shape attributes */
typedef struct {
int16_t x;
int16_t y;
} Shape;
/* Interface functions */
void Shape_ctor(Shape * const me, int16_t x, int16_t y);
void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy);
int16_t Shape_getX(Shape const * const me);
int16_t Shape_getY(Shape const * const me);
#endif /* SHAPE_H */ void Shape_ctor(Shape * const me, int16_t x, int16_t y) {
me->x = x;
me->y = y;
}
void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy) {
me->x += dx;
me->y += dy;
}
int16_t Shape_getX(Shape const * const me) { return me->x; }
int16_t Shape_getY(Shape const * const me) { return me->y; } #include "shape.h"
#include <stdio.h>
int main() {
Shape s1, s2;
Shape_ctor(&s1, 0, 1);
Shape_ctor(&s2, -1, 2);
printf("Shape s1(x=%d,y=%d)
", Shape_getX(&s1), Shape_getY(&s1));
printf("Shape s2(x=%d,y=%d)
", Shape_getX(&s2), Shape_getY(&s2));
Shape_moveBy(&s1, 2, -4);
Shape_moveBy(&s2, 1, -2);
printf("Shape s1(x=%d,y=%d)
", Shape_getX(&s1), Shape_getY(&s1));
printf("Shape s2(x=%d,y=%d)
", Shape_getX(&s2), Shape_getY(&s2));
return 0;
}Inheritance
Inheritance creates a new class based on an existing one, reusing its members. In C, single inheritance is achieved by placing the base struct as the first member of the derived struct.
#ifndef RECT_H
#define RECT_H
#include "shape.h"
typedef struct {
Shape super; /* Inherit Shape */
uint16_t width;
uint16_t height;
} Rectangle;
void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y, uint16_t width, uint16_t height);
#endif /* RECT_H */ void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y, uint16_t width, uint16_t height) {
Shape_ctor(&me->super, x, y);
me->width = width;
me->height = height;
} #include "rect.h"
#include <stdio.h>
int main() {
Rectangle r1, r2;
Rectangle_ctor(&r1, 0, 2, 10, 15);
Rectangle_ctor(&r2, -1, 3, 5, 8);
printf("Rect r1(x=%d,y=%d,width=%d,height=%d)
", Shape_getX(&r1.super), Shape_getY(&r1.super), r1.width, r1.height);
printf("Rect r2(x=%d,y=%d,width=%d,height=%d)
", Shape_getX(&r2.super), Shape_getY(&r2.super), r2.width, r2.height);
Shape_moveBy((Shape *)&r1, -2, 3);
Shape_moveBy(&r2.super, 2, -1);
printf("Rect r1(x=%d,y=%d,width=%d,height=%d)
", Shape_getX(&r1.super), Shape_getY(&r1.super), r1.width, r1.height);
printf("Rect r2(x=%d,y=%d,width=%d,height=%d)
", Shape_getX(&r2.super), Shape_getY(&r2.super), r2.width, r2.height);
return 0;
}Polymorphism
Polymorphism is achieved through virtual tables (vtbl) and a virtual pointer (vptr) stored in each object. The base Shape struct contains a pointer to its vtable, which holds function pointers for area and draw. Derived classes provide their own implementations and replace the vptr in their constructors.
#ifndef SHAPE_H
#define SHAPE_H
#include <stdint.h>
struct ShapeVtbl; /* Forward declaration */
typedef struct {
const struct ShapeVtbl *vptr;
int16_t x;
int16_t y;
} Shape;
struct ShapeVtbl {
uint32_t (*area)(Shape const * const me);
void (*draw)(Shape const * const me);
};
void Shape_ctor(Shape * const me, int16_t x, int16_t y);
void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy);
int16_t Shape_getX(Shape const * const me);
int16_t Shape_getY(Shape const * const me);
static inline uint32_t Shape_area(Shape const * const me) { return (*me->vptr->area)(me); }
static inline void Shape_draw(Shape const * const me) { (*me->vptr->draw)(me); }
#endif /* SHAPE_H */ void Shape_ctor(Shape * const me, int16_t x, int16_t y) {
static const struct ShapeVtbl vtbl = { &Shape_area_, &Shape_draw_ };
me->vptr = &vtbl; /* Initialize vptr */
me->x = x;
me->y = y;
}
static uint32_t Shape_area_(Shape const * const me) { return 0; } /* Pure virtual */
static void Shape_draw_(Shape const * const me) { /* pure virtual */ }Rectangle overrides the virtual functions:
static uint32_t Rectangle_area_(Shape const * const me) {
const Rectangle * const r = (const Rectangle *)me;
return (uint32_t)r->width * (uint32_t)r->height;
}
static void Rectangle_draw_(Shape const * const me) {
const Rectangle * const r = (const Rectangle *)me;
printf("Rectangle_draw_(x=%d,y=%d,width=%d,height=%d)
", Shape_getX(me), Shape_getY(me), r->width, r->height);
}
void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y, uint16_t width, uint16_t height) {
static const struct ShapeVtbl vtbl = { &Rectangle_area_, &Rectangle_draw_ };
Shape_ctor(&me->super, x, y);
me->super.vptr = &vtbl; /* Override vptr */
me->width = width;
me->height = height;
}Using an array of Shape pointers, the program can call virtual functions without knowing the concrete type:
Shape const *largestShape(Shape const *shapes[], uint32_t n) {
Shape const *s = NULL;
uint32_t max = 0;
for (uint32_t i = 0; i < n; ++i) {
uint32_t area = Shape_area(shapes[i]);
if (area > max) { max = area; s = shapes[i]; }
}
return s;
}
void drawAllShapes(Shape const *shapes[], uint32_t n) {
for (uint32_t i = 0; i < n; ++i) {
Shape_draw(shapes[i]);
}
} #include "rect.h"
#include "circle.h"
#include <stdio.h>
int main() {
Rectangle r1, r2;
Circle c1, c2;
Shape const *shapes[] = { &c1.super, &r2.super, &c2.super, &r1.super };
Rectangle_ctor(&r1, 0, 2, 10, 15);
Rectangle_ctor(&r2, -1, 3, 5, 8);
Circle_ctor(&c1, 1, -2, 12);
Circle_ctor(&c2, 1, -3, 6);
Shape const *s = largestShape(shapes, sizeof(shapes)/sizeof(shapes[0]));
printf("largestShape s(x=%d,y=%d)
", Shape_getX(s), Shape_getY(s));
drawAllShapes(shapes, sizeof(shapes)/sizeof(shapes[0]));
return 0;
}Conclusion
Object‑oriented programming is a design methodology, not a language feature. C can express encapsulation, single inheritance, and even polymorphism, though the latter requires manual virtual‑table handling. For extensive polymorphic designs, C++ provides built‑in support, but the C approach remains valuable for low‑level, performance‑critical code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
