Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

A Deep Dive into Object-Oriented Programming in C

June 28, 2024

A Deep Dive into Object-Oriented Programming in C

Object-oriented programming (OOP) is a powerful paradigm that helps in designing and organizing code by modeling real-world entities into objects. While C is known for being a procedural programming language, it is still possible to implement OOP concepts in C with the help of structures and function pointers. In this blog post, we will explore the fundamentals of OOP in C and demonstrate its capabilities through extensive examples and explanations.

Understanding Structures and Encapsulation

In C, structures act as containers to hold related data items. We can leverage structures to create user-defined datatypes, enabling us to encapsulate data and functionality into a single entity. Let’s consider an example of a Circle structure that represents a circle’s properties.

1
2
3
4
typedef struct {
    float radius;
    float (*calculate_area)(float radius);
} Circle;

In the code above, we define a Circle structure containing a radius variable and a function pointer named calculate_area. The function pointer is used to associate a method with the Circle structure. By encapsulating data and methods within the structure, we create a cohesive unit that represents a circle.

Implementing Inheritance with Function Pointers

In OOP, inheritance allows us to create new classes (derived classes) from existing ones (base classes), inheriting their properties and methods. To achieve a similar effect in C, we can use function pointers and structures.

Let’s create a Shape base structure that acts as a blueprint for different shapes. Each shape will have a common draw function pointer representing its drawing behavior.

1
2
3
typedef struct {
    void (*draw)();
} Shape;

We can define derived structures like Rectangle and Triangle, which extend the base Shape structure and provide their own implementation of the draw function pointer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
typedef struct {
    Shape shape;
    int width;
    int height;
} Rectangle;

void draw_rectangle() {
    // Drawing logic for rectangle
}

typedef struct {
    Shape shape;
    int base;
    int height;
} Triangle;

void draw_triangle() {
    // Drawing logic for triangle
}

By embedding a Shape structure within the derived structures, we create an “is-a” relationship, allowing objects of type Rectangle and Triangle to be treated as Shape. This way, we achieve a form of inheritance in C.

Providing Polymorphism with Function Pointers

Polymorphism allows objects of different types to be treated as the same type, thus enabling flexibility and code reuse. In C, we can achieve polymorphism by leveraging function pointers within structures.

Let’s extend our Shape base structure to support polymorphism. We will add a draw function pointer that can be assigned different drawing functions based on the concrete shape.

1
2
3
typedef struct {
    void (*draw)();
} Shape;

Now, let’s define our derived structures with different drawing behaviors.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
typedef struct {
    Shape shape;
    int width;
    int height;
} Rectangle;

void draw_rectangle() {
    // Drawing logic for rectangle
}

typedef struct {
    Shape shape;
    int base;
    int height;
} Triangle;

void draw_triangle() {
    // Drawing logic for triangle
}

To achieve polymorphism, we can assign specific drawing functions to the draw function pointer based on the type of shape.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Rectangle rect;
rect.shape.draw = &draw_rectangle;

Triangle tri;
tri.shape.draw = &draw_triangle;

// Drawing a rectangle
rect.shape.draw();

// Drawing a triangle
tri.shape.draw();

By invoking the draw function through the shape member of each structure, we achieve polymorphic behavior. This enables us to treat objects of different types uniformly, reducing the need for repetitive code logic.

Conclusion

Even though C is not designed as an object-oriented language, we can implement OOP concepts like encapsulation, inheritance, and polymorphism by leveraging structures and function pointers. By organizing code into objects and creating relationships between them, we can write cleaner and more maintainable code in C. Understanding these concepts and their implementation in C can greatly enhance your programming skills and make your code more robust.

In this blog post, we explored the fundamentals of OOP in C and demonstrated how to represent objects using structures, implement inheritance with function pointers, and achieve polymorphism through function pointers. Armed with this knowledge, you can start designing and organizing your C code using object-oriented principles. Happy coding!


➡️ Getting Started with nRF52 Development


⬅️ Troubleshooting Common Issues in ESP32 Development: Tips and Tricks


Go back to Posts.