Most modern languages are designed around the object oriented design principles. They contain syntactic elements that codify and require these principles for code implementation.
Unfortunately, as is common with modern minds, we leave everything up to the computer. This results in a gigantic block of generalities, the result of which is slow code that is difficult to maintain. For most modern programmers, the choice is between efficient code and object oriented code.
However, we can apply the Object Oriented Design Principles to C code relatively easily. After all, the principles were originally theorized for use in languages like C.
We’ve already discussed one aspect of encapsulation (that is, the unitary nature of objects), but the principle also includes “data hiding”. In “proper” encapsulation, the structures are perfectly opaque to the end user, so that the only way to access the data is through carefully-constructed methods.
The techniques for constructing methods are already well understood, but how does one create hidden data in C?
Fun fact: If it’s not in the header file, the end-user can’t touch it.
Fun fact #2: We can use typedef to create pointers to objects in code files which do not themselves appear in the header.
Fun fact #3: While the computer can translate between the typedef pointer and a pointer to the original object at link time, the user’s code cannot.
Thus, if we define struct panda in a code file, but the header only contains methods and the following:
typedef struct panda *PANDA
the end user can only access the structure by passing PANDA to the access methods.
Abstraction and Polymorphism
We perform abstraction in only two ways:
- Function pointers – used to look up objects
- Hiding data with “typedef”
Function pointers are too complicated to go into now, but basically we can write functions that accept pointers to functions, the results of which produce data that the top-level function can work with. This allows us to create essentially polymorphic functions.
Because abstraction is relatively complicated in C, it encourages programmers to rely on patterns and techniques instead of function-overloading and “let the computer figure it out” mental patterns. That means we don’t employ template classes, interface classes, or abstract classes (which I would argue ultimately make programming much harder), but we can still create functional polymorphism and abstraction if we so choose.
Note: We do create “templates” in C. These are not OOP templates, but rather code which is generally applicable to a number of tasks. We can simply use these framework templates to produce new classes quickly.
What exactly does inheritance mean?
Functionally, inheritance ensures that one class contains as its first (and thus, most easily addressed) member another class. All the aspects of the parent class are carried into the child class, so that the child is merely an extended version of the parent. As such, all the parent functions should be able to work with the child class.
We can do the exact same thing in C using type casting.
We know that we can tell a C program to treat data elements as some other kind of data element. When we do this, C “maps out” the space that the casted element should occupy in the original element and treats that space as the cast element.
It looks something like this:
Sure enough, so long as we ensure that the “parent” structure is the first element in the “child” structure, we can use type casting to perform inheritance. It’s really that simple.