C is not, of course, an object oriented language and does not even have any discernible features of one. The three core characteristics of object oriented programming are frequently stated to be encapsulation, inheritance and polymorphism, but a more fundamental characteristic is the combining of data (or properties) and the functions (or methods) which work on that data into a single entity which can be used to access both.
This can be simulated in C by adding function pointers to a struct. The nuts and bolts of doing so are not as slick as in a real OOP language such as C++ and frankly look a bit clunky, but in this post I will write a short demonstration of the principle. Whether it is actually worthwhile is a moot point!
In this project I will write a very simple "class" called Photo - it will have two "properties", Title and Description, and one "method", print. This is the bare minimum to demonstrate the technique, but if you should decide to take it further all you need to do is add more properties and methods.
Create a new folder and within it create the following empty files. You can download the source code from the Downloads page or clone/download from Github if you prefer.
- photo.h
- photo.c
- main.c
Source Code Links
Open photo.h and enter the following.
photo.h
//---------------------------------------------------------------- // STRUCT Photo //---------------------------------------------------------------- typedef struct Photo { char* Title; char* Description; void (*print)(struct Photo*); }Photo; //---------------------------------------------------------------- // FUNCTION PROTOTYPES //---------------------------------------------------------------- void new_photo(Photo* photo, char* Title, char* Description); void print_photo(struct Photo* photo);
Here we just declare a struct and two function prototypes. Now open photo.c and enter this code.
photo.c
#include<stdlib.h> #include<stdio.h> #include"photo.h" //---------------------------------------------------------------- // FUNCTION print_photo //---------------------------------------------------------------- void print_photo(struct Photo* photo) { printf("Title: %s\n", photo->Title); printf("Description: %s\n", photo->Description); } //---------------------------------------------------------------- // FUNCTION NewPhoto //---------------------------------------------------------------- void new_photo(Photo* photo, char* Title, char* Description) { // Properties photo->Title = Title; photo->Description = Description; // Methods photo->print = print_photo; }
In photo.c, after the #includes, we implement print_photo and new_photo.
Note that print_photo has a Photo* argument so it knows which Photo to print, even though a pointer to the function is later added to a struct. Object oriented languages have some concept of self-referencing, for example C++, C# and other curly-bracket OOP languages have the keyword this, Visual Basic has the keyword me and Python has the keyword self. These are used by class methods when they need to use other members of their own class. C does not have anything like this so even if a pointer to a function is added to a struct the function still needs to be told (via an argument) the actual struct it is to work with.OOP languages also have syntax to create a new instance of a class, for example using the new keyword which calls a special method in the class called the constructor. Again C does not have this so we need a function, in this case new_photo, which can be called by name rather than a fixed keyword. Our new_photo function takes a Photo struct declared by the calling code as well as values for the two properties, and then sets the property values from the arguments.
After this it does something which is core to the whole concept of simulating OOP in C - it sets the print property to print_photo, or to be precise to a function pointer.
To summarise the process of object oriented programming in C:
- Add a suitable function pointer to your struct for each function you need to work with that struct. It must have at least one argument - a pointer to the struct.
- Prototype and implement the functions, using the struct pointer to access the struct.
- Create a single "constructor" function along the lines of new_photo to set properties and also function pointers.
That was probably simpler than you might have thought, so let's go ahead and use our new Photo class. Open main.c and enter this code.
main.c
#include<stdio.h> #include<math.h> #include<time.h> #include<locale.h> #include<stdlib.h> #include"photo.h" //-------------------------------------------------------- // FUNCTION main //-------------------------------------------------------- int main(int argc, char* argv[]) { puts("------------------------------------"); puts("| codedrome.com |"); puts("| Object Oriented Programming in C |"); puts("------------------------------------\n"); Photo p1; new_photo(&p1, "Thing", "Photograph of a thing"); p1.print(&p1); Photo p2; new_photo(&p2, "Another Thing", "Photograph of another thing"); p2.print(&p2); Photo p3; new_photo(&p3, "Yet Another Thing", "Photograph of yet another thing"); p3.print(&p3); return EXIT_SUCCESS; }
The main function does the same thing three times as you can see, so let's just dissect one of the usages.
1 Photo p1; 2 new_photo(&p1, "Thing", "Photograph of a thing"); 3 p1.print(&p1);
Line 1 just creates a new variable of type Photo.
Line 2 is the equivalent of a constructor call in a proper OOP language, although in such a language lines 1 and 2 would probably be combined.
Line 3 calls the print method in a way which looks very object oriented in that we first have the object name followed by the method name using dot notation. The only giveaway that we are not actually using an OOP language is the pointer to the struct as a function argument.
You can now compile and run the program with this in your terminal.
Compile and run
gcc main.c photo.c -std=c11 -lm -o main ./main
Program output
------------------------------------ | codedrome.com | | Object Oriented Programming in C | ------------------------------------ Title: Thing Description: Photograph of a thing Title: Another Thing Description: Photograph of another thing Title: Yet Another Thing Description: Photograph of yet another thing
So was it worth it? We could have achieved the same result without bothering to add a function pointer to the struct using code like this. (Incidentally this code will work fine with the existing photo.h and photo.c. It just bypasses the new_photo function and print_photo pointer.)
1 Photo p1; 2 p1.Title ="Thing"; 3 p1.Description = "Photograph of a thing"; 4 print_photo(&p1);
For a simple example like this, the answer is probably no, it isn't worthwhile. However, few real world examples are this simple and if you have a more complex program with a number of more complex structs it may make the code more readable if you explicitly tie together the structs and the code which works on them. In such situations you might well decide it is worth stepping up to C++ but that is a big step. C++ is far more complex than C, and even more complex that its original name of "C with Classes" might imply. I worked in C++ for a number of years and frankly I don't like it. Maybe Java and C# owe their success to many other people not liking C++ and looking for a better alternative! But if you want to stick with C but think a little bit of OOP might come in handy occasionally give the ideas in this post a try.