We assume here that the functions listed above
main()exists and do as their name implies. The simple program just draws a box that represent our button on screen and then draws a string on top of that so the button gets a label/caption. It is obvious that if we want to create more than one button we it is tedious to keep passing almost to same coordinates to draw the string on the button. So we create a new function
draw_button(), which gives us the following code:
However let us say that each time one clicks Button1 we want it to move 5 pixels to down and to the right. We can change as follows:
Now there is a number of problems with this code.
- There is no forced relationship between the drawing of the button and checking for clicks in it. It is possible to change drawing position of button and forget to update the mouse code check code as well.
- If we start making more buttons, say 20 more buttons, which each will move in different direction when clicked, it becomes a nightmare to keep track of where each button is.
As one can see, we can now easily create lots of buttons and move them around without loosing track. But this is not all there is to OOP. So far I have made it look like it is just about collecting all variables in a struct so it is easier to see which variables that belong to each other. To show another important aspect of OOP I will give another code example. Here we are creating an
Arrayobject. The point of this object is that unlike regular C arrays it keeps track of its size, so we can query it in the rest of the program.
Now this is all fine. But lets consider that we want to iterate over the whole array using pointers. So we decide that in order to do this it would be better to change the data structure for the Array. Instead of storing size of array we will store a pointer to the end of the array. So we change the data structure to the following:
Except there is one problem with this. Suddenly our code in
main()is broken. We have to change
a->end - a->begin. That is of course quick for us to do in this example. But what if we had written 10000 lines of code and iterated over the array loads of places? We would have to go through all that source code and made changes! Data encapsulation Data encapsulation is the solution to this problem. And this is one of the cornerstones of OOP. Instead of letting users of our Array object access its data directly we hide the internal representation of the object by requiring the users of it to access its properties through function calls. The code below shows this approach:
Inheritance Another important aspect of OOP in inheritance. This is a mechanism of style of programming that allows one to reuse a lot of code. Of course the code examples I show here are rather small so they don't show fully how much code there is to save having to rewrite. But it doesn't require too much imagination to see that this can be a big benefit when writing larger programs. To illustrate the usage of inheritance I will use geometric primitives. E.g. a point has a location is space. We can imagine functions to move the point in space relative to current position or set it at an absolute position.
Now we want to define a
Circleobject but we do not want to have to rewrite the code for moving the center of the circle since it is basically the same as moving a point. How can we reuse the
Circle? All we need to do is to make it look like for the
point_move()function as if it is dealing with a Point data structure as its first argument. This is actually not as hard as is seems:
If we have a pointer to a circle structure we can now access the x and y coordinates like this:
However this is not very interesting for us since it does not allow us to treat a Circle as a point. What is interesting is that we can do the following:
How is this possible!? The reason why this works has to do with how C deals with structs. When you define a struct, the C compiler will store offset values for each variable in the struct. Here is how it works. When you write
int x = p->xthe C compiler will compile this into machine code that takes the start address of the struct pointed to by p and add a offset value representing the x to this address. This will create a new address which is used to locate the x member variable. Addresses are typically given on int boundaries (32 bit). So in the case of the Point data structure. Let us say that p is located in memory location 11. Then since x is the first variable defined it is located at offest 0, which means it is at 11 too. y on the other hand is the second variable so it has offset 1. Meaning it is on location 11+1=12. p->y would thus be converted to address 12. Bottom line is that offsets are given relative to top of struct definition. Thus if we put Point at the top of the Circle definition, the offset values x and y will still be valid for the Circle struct. Of course the C compiler doesn't know that, so we must trick it by doing a cast to
Point*. Otherwise it will complain that Circle does not have any members named x and y. So to demonstrate inheritance, here is a short program again:
There is of course a lot more to OOP, but several other people on the web have written much more extensive explanations, even books on doing OOP in C. My intention here was just to give an introduction. So you might wonder what is the point of using a dedicated OOP language like C++ or Java? First of all is the syntax sugar. Instead of writing
point_move(p, x, y)you can write
p->move(x, y)in C++. C++ makes p the first argument to move but by putting it in front it makes it clear that p is the object of focus. The other important aspect is that one is not required to prefix the function names with say
point_to avoid clutter in the namespace. C++ makes sure that each function called
move()is associated with a type. And then there is the case of e.g. polymorphism that would just get very ugly looking in C. I do of course not advice anybody to do OO in C. There is no need for that when we have so many nice OOP language to choose from. Doing OOP in C however does make it clear that OOP is a paradigm and not a language feature. To look at it from the other side: One could choose to program procedure oriented in Java or C++ for instance by:
- Declaring member variables public and not use accessor methods (thus breaking the OO principle of data encapsulation and abstraction).
- One could use switch case statements instead of polymorphism.
- Don't do code reuse by using inheritance. Make all methods static
int, float and charare not objects.
forstatements have procedure like semantics. And these are the statements and objects the newbie programmers are exposed to first, thus locking them into a procedure oriented way of thinking early on.