Is there an Equivalent to Classes in C (Can you FAKE one?)


When you first learn C you become aware that it is a so called procedural language. In contrast to object oriented languages there is no concept of a class and no such keyword in C. But is there a similar concept?

There is no exact Equivalent to a Class in C. You can emulate some aspects of Classes like Encapsulation and Information Hiding with structs and the Keyword static.

C Class Equivalent
A Glimpse into the C Class Equivalent

If you want to know how you can emulate classes in C, read on. We’ll cover it in this article.

Is there an Equivalent to Classes in C?

There is no exact equivalent to a class in C. However we can emulate some aspects of classes in order to get some of the advantages of object orientation. If you want to know why there are no classes in C you will find the answer here.

When you take a look at the syntax of C you will find a thing called struct. In C++, a struct is essentially the same as a class with one minor difference: All members are public by default instead of private. In C, there is no private, so all members of a struct are always public.

A struct in C cannot have any functions (methods) which is the second big reason why it is not equivalent to a class. Although you can have function pointers this soon becomes a mess when you try to use it as methods in a class because of the initialization pitfalls you will face.

Can you FAKE Classes in C?

As I mentioned earlier, you can emulate some aspects of a class in C with structs and the keyword static. We will also need pointers and some discipline when creating functions (methods) for our class. We will approach our target step by step with the help of an example.

Please note that there is more than one way to emulate classes in C. This is the one I found to be good and that I personally use in my programs. This may change over time and you can use it as I describe or come up with your own ideas.

A C++ Class for our Example

Here is a class written in C++ which we will take as a template for our example.

#include <string>

class Person {
public:
    Person(std::string firstName, std::string lastName, int age);
    std::string getFullName();
    int getAge();
    void happyBirthday();
    
private:
    std::string firstName;
    std::string lastName;
    int age;
};
#include "person.hpp"
#include <iostream>

Person::Person(std::string firstName, std::string lastName, int age) {
    this->firstName = firstName;
    this->lastName  = lastName;
    this->age       = age;
}
    
std::string Person::getFullName() {
    return this->firstName + " " + this->lastName; }

int Person::getAge() {
    return this->age;
}
    
void Person::happyBirthday() {
    this->age++; 
}

Step 1: Encapsulation in C

There are three private variables in the C++ Class. If we just created a struct with these variables in the person.h file we would make them public to the rest of the program. On the other hand we need a declaration of a struct in order to use it as a class surrogate.

The solution is to declare the struct with it’s name only. And then we provide a new function which will return a pointer to the real struct so that we are able to reserve adequate memory.

struct person_t;                 /* Declaration for the Compiler */
struct person_t* person_new();   /* Creation Function */

The “_t” suffix is very common in C and I use it, too.

In person.c we define the person struct and allocate the memory in our person_new function.

#include "person.h"

typedef struct person_t {
  char* firstName;
  char* lastName;
  int  age;
} person_t;

person_t* person_new() {
    return (person_t*)malloc(sizeof(person_t));
} 

Step 2: Construction and Destruction in C

In C++ you have a this when you want to access class members and methods. It is automatically there within the boundaries of the class. In C you have to do it manually. The first parameter of every class method has to be a pointer to the class defining struct.

This is how we declare the constructor and the destructor in C. As I mentioned at the start you will need some discipline for naming. The same discipline is needed for calling all these methods, especially new, constructor and destructor.

struct person_t;                 /* Declaration for the Compiler */
struct person_t* person_new();   /* Creation Function */

void person_ctr(struct person_t* obj, const char* firstName, 
                  const char* lastName, const int age); /* Constructor */
void person_dtr(struct person_t* obj); /* Destructor */
...

void person_ctr(person_t* obj, const char* firstName, const char* lastName, const int age) {
    obj->firstName = (char*)firstName;
    obj->lastName = (char*)lastName;
    obj->age = age;
}

void person_dtr(person_t* obj) {
    /* Free all allocated memory */
    free(obj);
}

Step 3: Information Hiding in C

In order to just give away the interface but not the details of your implementation, you will want to use the principles of information hiding. Steps 1 and 2 built the foundation, now we implement the methods from our C++ example in our C class equivalent.

Here we will need again the discipline of naming conventions and sticking to it. You may want to use other conventions if they work better for you. But the point is to have them and to stick with them.

...

const char* person_getFullName(struct person_t* obj);
int person_getAge(struct person_t* obj);
void person_happyBirthday(struct person_t* obj);
...

static char fullName[50];
const char* person_getFullName(struct person_t* obj) {
    strcat(fullName, obj->firstName);
    strcat(fullName, " ");
    strcat(fullName, obj->lastName);
    return fullName;
}

int person_getAge(struct person_t* obj) {
    return obj->age;
}

void person_happyBirthday(struct person_t* obj) {
    obj->age++;
}

Step 4: Wrapping it into a Class and Show an Example in C

Now you get the full code of the person class we just created.

struct person_t;                 /* Declaration for the Compiler */
struct person_t* person_new();   /* Creation Function */

void person_ctr(struct person_t* obj, const char* firstName, 
                  const char* lastName, const int age); /* Constructor */
void person_dtr(struct person_t* obj); /* Destructor */

const char* person_getFullName(struct person_t* obj);
int person_getAge(struct person_t* obj);
void person_happyBirthday(struct person_t* obj);
#include "person.h"
#include <stdlib.h>
#include <string.h>

typedef struct person_t {
  char* firstName;
  char* lastName;
  int  age;
} person_t;

person_t* person_new() {
    return (person_t*)malloc(sizeof(person_t));
} 

void person_ctr(person_t* obj, const char* firstName, const char* lastName, const int age) {
    obj->firstName = (char*)firstName;
    obj->lastName = (char*)lastName; 
    obj->age = age;
}

void person_dtr(person_t* obj) {
    /* Free all allocated memory */
    free(obj);
}

static char fullName[50];
const char* person_getFullName(struct person_t* obj) {
    strcat(fullName, obj->firstName);
    strcat(fullName, " ");
    strcat(fullName, obj->lastName);
    return fullName;
}

int person_getAge(struct person_t* obj) {
    return obj->age;
}

void person_happyBirthday(struct person_t* obj) {
    obj->age++;
}

And here is an example of how to use this class in C. It’s about naming conventions, discipline, watching out for allocated memory and some common sense.

#include "person.h"
#include <stdio.h>

int main() {
    struct person_t* p = person_new();
    person_ctr(p, "John", "Wayne", 28);
    
    printf("Hi, my name is %s\n", person_getFullName(p));
    printf("I am %d years old\n", person_getAge(p));
    
    person_happyBirthday(p);

    printf("Now I am %d years old\n", person_getAge(p));

    person_dtr(p);

    return 0;
}

This is a way to emulate encapsulation and information hiding in C. Next time we will focus on inheritance and polymorphism.

Marco Lieblang

Professional Programmer since 2003, passionate Programmer since the mid 90's. Developing in many languages from C/C++ to Java, C#, Python and some more. And I also may know a bit about Assembly Languages and Retro Systems.

Recent Posts