1) Good job. Read the caveat below.
2) It matters if you pass a pointer to a Worker to the setName() function, because the setName() function expects a pointer to a Person. If you move stuff around in the structure then the functions will fail, because they have no idea that you've done that.
3) Yes, inheritance without polymorphism is pointless.
4) I'm lost here. The code you posted hasn't any void pointers.
Caveats. Polymorphism works because an object knows what kind of thing it is, so you can
override a method to perform correctly for the given object type. This is only possible when the object contains a "virtual table" -- a list of pointers to functions appropriate to that given object. And it also means that a function that expects one kind of thing (a pointer to a base class) can properly operate on another kind of thing (a pointer to a derived class).
Here is a simple example:
An "animal" can "speak". This is our base class. (It should be abstract, but that's beyond the scope of this discussion.)
A "dog" speaks by saying "woof".
A "cat" speaks by saying "meow".
A function that gets a list of animals (some dogs, some cats) can tell each animal to "speak". The function doesn't have any clue what kind of animal each one is, only that it is a type of (or derivation of) an animal. But each animal correctly speaks.
Here it is in C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
#include <iostream>
using namespace std;
struct animal
{
virtual void speak() const = 0;
};
struct dog: public animal
{
virtual void speak() const { cout << "woof!\n"; }
};
struct cat: public animal
{
virtual void speak() const { cout << "meow.\n"; }
};
int main()
{
// our list of pointers to animals
animal* animals[ 4 ] = {
new dog,
new cat,
new dog,
new dog
};
// apply some polymorphic behavior here
// (each animal says the correct thing)
for (int n = 0; n < 4; n++)
animals[ n ]->speak();
// don't forget to clean up
for (int n = 0; n < 4; n++)
delete animals[ n ];
return 0;
}
|
woof!
meow.
woof!
woof! |
A lot of things are going on behind the scenes there. In C, there isn't any "behind the scenes" so we need to do it ourselves. Lets give it a go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
#include <stdio.h>
#include <stdlib.h>
/* A simple cheat for readability. Caveats apply. */
typedef enum { false, true } bool;
/* ------------------------------------------------------- **
* First, we need our "virtual table" of
* functions used by descendant objects.
* Notice how the function takes a void*
* as argument. This is important!
*/
struct animal_vtable
{
void (*p_speak)( void* );
/* any other virtual base class functions go here */
};
/* ...and descendant classes need their virtual tables too */
struct dog_vtable
{
struct animal_vtable animal_vtable;
/* virtual functions introduced in the dog class go here */
};
struct cat_vtable
{
struct animal_vtable animal_vtable;
/* virtual functions introduced in the cat class go here */
};
/* ------------------------------------------------------- **
* Here we introduce the classes.
* In C++, the virtual table is a "hidden" member of a
* class, but in C it cannot be hidden. Make sure to list
* it FIRST!
*/
struct animal
{
struct animal_vtable* vtable;
int age;
};
struct dog
{
struct animal animal;
bool drools;
}
dog;
struct cat
{
struct animal animal;
bool sheds;
}
cat;
/* ------------------------------------------------------- **
* Now we can declare the polymorphic "speak" function
*/
void animal_speak( void* animal )
{
((struct animal*)animal)->vtable->p_speak( animal );
}
/* And while we are at it, the individual speak functions */
void dog_speak( void* dog )
{
puts( "woof!" );
}
void cat_speak( void* cat )
{
puts( "meow." );
}
/* ------------------------------------------------------- **
* Here are our vtables.
*/
struct animal_vtable animal_vtable = { NULL };
struct dog_vtable dog_vtable = { { &dog_speak } };
struct cat_vtable cat_vtable = { { &cat_speak } };
/* ------------------------------------------------------- **
* And we need the required constructors for each class type.
* Notice how the constructor does NOT call calloc() -- that
* is the job of the allocator.
* Also notice how the constructor for derived types call
* the constructor for the parent type!
*/
struct animal* new_animal( void* space )
{
struct animal* animal = space;
animal->vtable = &animal_vtable;
animal->age = 0;
return animal;
}
struct animal* new_dog( void* space )
{
struct dog* dog = (struct dog*) new_animal( space );
dog->animal.vtable = (struct animal_vtable*) &dog_vtable;
dog->drools = true;
return (struct animal*) dog;
}
struct animal* new_cat( void* space )
{
struct cat* cat = (struct cat*) new_animal( space );
cat->animal.vtable = (struct animal_vtable*) &cat_vtable;
cat->sheds = false; /* not until older... */
return (struct animal*) cat;
}
/* ------------------------------------------------------- **
* This is our replacement for C++'s "new" function, which
* does two things: it allocates and zeros space for the
* new object, and then applies the objects constructor to
* that space.
* In C, of course, our allocator is calloc() and the
* constructor must be spliced together by the preprocessor.
*/
#define new( type ) new_ ## type ( calloc( 1, sizeof( struct type ) ) )
/* Similar considerations must be made for destructors, if
* you use them, except that destructors must be a virtual
* function!
*/
/* --------------------------- */
int main()
{
int n;
struct animal* animals[ 4 ];
animals[ 0 ] = new( dog );
animals[ 1 ] = new( cat );
animals[ 2 ] = new( dog );
animals[ 3 ] = new( dog );
for (n = 0; n < 4; n++)
animal_speak( animals[ n ] );
for (n = 0; n < 4; n++)
free( animals[ n ] );
return 0;
}
|
woof!
meow.
woof!
woof! |
This is a
very simplified version for you, but I think that it covers all the basics, at least enough for you to move forward.
Remember that there is a lot of safety built in to using C++ classes. In C, all bets are off because you are doing it yourself!
Even that simple thing at the top
typedef enum { false, true } bool;
is
dangerous, because it only make things
look like they do in C++, but it is not an actual type. Hence, you cannot say something like
if (my_bool == true)
because it will fail (sizeof( my_bool ) * 8 - 1) times out of (sizeof( my_bool ) * 8). If you don't understand
why, then you should probably not be messing with it.
Everything else here is more of the same danger.
BTW, this is not the
only way to do this kind of thing. There are other methods, some much more advanced. Remember, this is a
simple example to get you started. The more you mess around with it, the better an understanding you will get of it, and the better you will see the limitations of doing OOP in C.
Hope this helps.