The next step to polymorphism is to add "methods", or functions that act to the specific object. This step also requires us to add a "constructor" -function, which in C is a function that allocate memory for the structure and initializes all necessary variables.
Let's say we want to add a "ToString" -function to our shape-object. The function should print out the type of shape our object is. If it is a rectangle, the function prints "Rectangle".. etc.
It is not possible to write functions inside structures, but what can be done is to store function pointers inside the structures (all pointers are the same size, no matter where, or to what, they point).
This is how a function pointer is declared:
C:
// step 1) The name of the variable is "toString"
toString;
// 2) To make this a pointer, add the '*', the parenthesis are needed to make sure that things are declared in the right order.
(*toString); // Now the variable is a pointer.
// 3) To make it a function pointer we add parenthesis to denote a function call (void). The "void" tells the compiler that the function does not take in any parameters.
(*toString)(void);
// 4) Last thing to add is the return value of the function. Since our function does not return anything, the final form of the function-pointer is
void (*toString)(void);
Now, we can update the base structure to hold this function pointer. We add it as the first variable. (Because usually there is a list of functions (methods), and it is good practice to make the list as the first element)
Our structures are:
C:
struct shape {
void (*toString)(void);
int x;
int y;
};
struct rectangle {
struct shape base;
int width;
int height;
};
struct circle {
struct shape base;
int radius;
};
Now we need to write the "toString" functions for each object. There is no way around it.. well, you could have the same function for all of them, but that is not very usefull. When inheriting from some base "class" you usually override some functions. This means writing a function specific for that object.
Here are the functions:
C:
// toString for shape -base structure
void toStringShape(void){
print("Shape"); // we assume that there is some print-function for us.
}
// toString for rectangle
void toStringRectangle(void){
print("Rectangle");
}
// toString for circle
void toStringCircle(void){
print("Circle");
}
Constructing these objects is now more complicated since we added object-specific methods. We need Constructors to put them together for us.
C:
// Constructor for rectangle object. It allocates memory for the structure and returns a pointer to it.
struct rectangle* new_rectangle() {
struct rectangle* rect = (struct rectangle*)malloc(sizeof(struct rectangle)); // allocate memory for the structure
rect->base.toString = toStringRectangle; // assign the address of the function "toStringRectangle()" to the "toString" function pointer
rect->base.x = 10;
rect->base.y = 15;
rect->width = 40;
rect->height = 60;
return rect;
}
// Constructor for circle object.
struct circle* new_circle() {
struct circle* circ = (struct circle*)malloc(sizeof(struct circle));
circ->base.toString = toStringCircle;
circ->base.x = 0;
circ->base.y = 0;
circ->radius = 5;
return circ;
}
This all may seem complicated, but when we put this all to use, the benefits are clear (hopefully)
Lets declare an array of shapes:
C:
struct shape* shapes[5];
shapes[0] = new_circle();
shapes[1] = new_rectangle();
shapes[2] = new_rectangle();
shapes[3] = new_circle();
shapes[4] = new_rectangle();
// Now, we can "print" all shapes using a for-loop
for(int i=0; i<5; i++) {
shapes[i]->toString();
}
/* This should print out:
Circle
Rectangle
Rectangle
Circle
Rectangle
*/