Article by Ayman Alheraki on January 11 2026 10:35 AM
While C does not support object-oriented programming (OOP) natively as C++ does, it is still possible to replicate some core OOP concepts through low-level approaches. In this article, we will explore how to simulate classes, inheritance, polymorphism, as well as generic data types and smart memory management in C.
In C, there is no direct support for classes, but we can use structs to create "object-like" data types. Here’s an example of defining a simple "class" using structs:
typedef struct { char name[50]; int age;} Person;
// Function to create a person object and set its valuesPerson* create_person(const char* name, int age) { Person* new_person = (Person*)malloc(sizeof(Person)); if (new_person) { strcpy(new_person->name, name); new_person->age = age; } return new_person;}
// Function to print person detailsvoid print_person(Person* person) { if (person) { printf("Name: %s, Age: %d\n", person->name, person->age); }}Here, the Person struct acts like a "class" with specific data and functions associated with it.
While C doesn’t support inheritance, a similar effect can be achieved through nested structs. For example, we can create a "derived class" by including one struct inside another:
typedef struct { char brand[50];} Vehicle;
typedef struct { Vehicle base_vehicle; int max_speed;} Car;In this example, Car contains Vehicle, which resembles inheritance in OOP.
In C++, polymorphism is implemented through virtual functions. In C, we can achieve a similar effect with function pointers. By defining function pointers in structs, we can simulate virtual functions.
typedef struct { void (*speak)();} Animal;
void cat_speak() { printf("Meow!\n");}
void dog_speak() { printf("Woof!\n");}
int main() { Animal cat = { .speak = cat_speak }; Animal dog = { .speak = dog_speak };
cat.speak(); dog.speak();
return 0;}Here, Animal is defined with a function pointer speak, which can be assigned to represent a specific "sound" for each animal.
C does not have templates like C++, but macros can be used to create generic functions. For example, a macro can be created to swap elements of different types:
int main() { int a = 5, b = 10; SWAP(a, b, int); printf("a = %d, b = %d\n", a, b);
float x = 1.2, y = 3.4; SWAP(x, y, float); printf("x = %.1f, y = %.1f\n", x, y);
return 0;}This macro performs swapping for any data type, mimicking the idea of templates.
C does not have smart pointers like std::unique_ptr or std::shared_ptr, but we can create functions to simplify memory management and make it safer. Here’s an example of a basic "smart pointer" to automatically free memory when it’s no longer needed:
typedef struct { int* data;} SmartPointer;
SmartPointer* create_smart_pointer(int value) { SmartPointer* sp = (SmartPointer*)malloc(sizeof(SmartPointer)); if (sp) { sp->data = (int*)malloc(sizeof(int)); *(sp->data) = value; } return sp;}
void delete_smart_pointer(SmartPointer* sp) { if (sp) { free(sp->data); free(sp); }}
int main() { SmartPointer* sp = create_smart_pointer(42); printf("Value: %d\n", *(sp->data)); delete_smart_pointer(sp);
return 0;}In this example, we use SmartPointer as a simple substitute for unique_ptr in C++ to automatically free memory.
Although C lacks native support for OOP features, some of its principles can be simulated through structures, function pointers, and macros. Understanding these techniques allows C programmers to create large, efficient programs and to apply advanced concepts similar to object-oriented programming.