Classes in C are most often simulated by structs combined with function pointers. Non-virtual functions can be passed alongside a pointer to the struct, like so:
int obj_compare_funct(Obj *a, Obj *b);
int result = compare_two_objects(obj1, obj2, obj_compare_func);
But the real fun starts when you embed the pointers in the struct; this means objects of the same overall "class" can have different "methods". The biggest syntactic downside is that the function pointed to does not automatically know for which object it is being called. So the object needs to be passed as well, which makes for a bit more typing than is normally desirable. For instance:
/***** In the animal.h header file. *****/
typedef struct Animal {
char *name;
void (* speak)(Animal *this); /* The speak "method" */
} Animal;
/* Constructors for various animal types. Implementation detail: set the animal's speak method to the appropriate one for that animal type. */
extern Animal *make_feline(char *name);
extern Animal *make_rodent(char *name);
/***** Somewhere in zoo.c, which #includes animal.h. *****/
Animal *cat = make_feline("Tom");
Animal *mouse = make_rodent("Jerry");
cat->speak(cat); /* Print "Tom says meow!" */
mouse->speak(mouse); /* Print "Jerry says squeak!" */
This example is a bit looser than the inheritance model provided by languages such as Java -- an Animal instance can have any behaviour at all, rather than one of a specific set of behaviours depending on its subclass. To make things a bit stricter, the methods are usually combined into a struct called a vtable (virtual function table). One vtable is pre-made for each subtype, and the appropriate one pointed to from the instance.
Note that none of this directly helps you have different fields for each subtype -- that's trickier (especially syntactically) and can be done either by the trick of casting an object to its first member, e.g.:
/* Can be treated as an Animal if you cast its pointer. */
typedef struct Cat { Animal super; int num_fleas; } Cat;
Or using opaque pointers, e.g.
typedef struct Animal { char *name; void *species_specific_data; } Animal;
Where the extra fields would be hidden away behind that void *
pointer, and accessible through the methods particular to that species.