Fundamentals 17 min read

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++.

21CTO
21CTO
21CTO
How to Implement Object‑Oriented Programming in C: From Encapsulation to Polymorphism

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

C programmingEncapsulationobject‑oriented programmingPolymorphismInheritanceOOP in C
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.