Outre le style impératif, il existe d'autres styles de programmation, comme le style fonctionnel et le style à objets. Bien que ces différents styles puissent être employés, avec plus ou moins d'élégance, dans n'importe quel langage, certains langages ont été spécialement conçus pour faciliter la programmation dans un style spécifique. Ainsi, le style à objets est-il particulièrement adapté aux langages C++, Eiffel ou Java. Certaines caractéristiques sémantiques de ces langages, (comme la modularité, l'encapsulation, l'extension, le sous-typage, la liaison tardive) qui sont liées à ce style de programmation, peuvent être plus ou moins bien simulées en C.
Voici un exemple de ce qu'on souhaiterait implémenter en C. On veut modéliser plusieurs classes d'objets : les points, les cercles et les rectangles. Tous ces objets ont une surface, la méthode de calcul de cette surface dépendant de la classe de l'objet. Un point du plan est défini par ses deux coordonnées, un cercle est défini par son centre et son rayon, un rectangle est défini par ses points supérieur gauche et inférieur droit. On veut pouvoir déclarer une variable d'un type Objet, l'initialiser avec une valeur qui représente un point, un cercle ou bien un rectangle, et appeler une même fonction surface avec comme argument cette variable :
Objet *c = ... /* un cercle */
Objet *r = ... /* un rectangle */
double sc = surface(c);
double sr = surface(r);
Une première solution consiste à définir une fonction surface qui réalise le branchement vers plusieurs méthodes de calcul à l'aide d'un switch :
double surface(Objet *o) {
switch(o->classe) {
case POINT:
return 0;
case CERCLE: {
double r = ((Cercle *)o)->rayon;
return PI * r * r;
}
case RECTANGLE: ...
}
}
Ceci suppose que le type Objet est une structure ayant un champ classe d'un type énuméré :
typedef enum {POINT, RECTANGLE, CERCLE} Classe;
typedef struct { Classe classe; } Objet;
Ceci suppose également qu'une valeur de type Objet* peut être convertie en une valeur de type Cercle*, le type Cercle ayant un champ rayon. On adoptera donc les définitions de type suivantes :
typedef struct {
Classe classe;
double x,y;
} Point;
typedef struct Cercle{
Classe classe;
Point *centre;
double rayon;
} Cercle;
En supposant l'existence de constructeurs appropriés, on pourra appeler la fonction surface comme souhaité :
Objet *c = (Objet *)newCercle(newPoint(0,0), 1);
double sc = surface(c);
Ceci fonctionne grâce à une propriété de la sémantique des struct de C, dont les champs sont alloués dans l'ordre où ils sont déclarés. Ainsi, le premier champ des types Objet et Cercle étant du même type Classe, on peut convertir l'adresse d'un Cercle en l'adresse d'un Objet (dans l'initialisation de la variable c) et continuer à accéder à son champ classe. Inversement, on convertit l'adresse d'un Objet en l'adresse d'un Cercle (dans le corps de la fonction surface), pour accéder au champ rayon d'un Objet qui est en réalité un Cercle.
Cette première solution n'est pas très satisfaisante. En effet, si l'on parvient à appliquer une même fonction, surface, à des objets de différentes classes, c'est au prix d'une centralisation des différentes méthodes de calcul dans la définition de cette fonction : ceci n'est pas modulaire, ce qui est pourtant l'une des caractéristiques importantes du style à objets.