🟨2026.04.01
C
Orienté Objet | 1
Le C n’est généralement pas un langage associé à la programmation orientée objet. Quand j’ai appris le C++, on me l’a initialement un peu présenté comme “du C mais avec des objets”, et c’est comme ça que j’ai généralement regardé ces langages (que j’utilise très peu) jusqu’à présent.
Mais en “jouant” à résoudre les vieux puzzles Advent of Code en C (voir mes notes à ce sujet!), je me suis rendu compte que je commençais à utiliser naturellement des principes “orienté objet” dans mes résolutions. On a parfois je pense (en tout cas moi!) un peu tendance à réduire “orienté objet” à “on peut déclarer des classes”. Mais on peut avoir des classes et des objets sans faire de la programmation orientée objet (beaucoup de projets Python en sont un bon exemple!), et on peut faire de la programmation orientée objet sans classes. Car au-delà de la syntaxe, c’est surtout dans la façon d’organiser et de penser son code que les concepts orientés objets apparaissent.
Et après tout, beaucoup de langages “orientés objets” sont écris en C, donc c’est nécessairement possible d’en faire en revenant à la source!
Essayons.
Une classe Person
Commençons par un classique: une classe Person,
qui contiendrait un nom et un prénom. Nous n’avons pas de
“classes” en C, mais on peut déclarer des
structures:
struct Person {
char* first_name;
char* last_name;
};Ici, on a donc bien une Person qui possède un
first_name et un last_name. Si je veux
l’utiliser dans mon code, je pourrais faire quelque chose comme
ceci:
struct Person* p = malloc(sizeof(struct Person));
p->first_name = "Adrien";
p->last_name = "Foucart";
printf("%s %s\n", p->first_name, p->last_name);
free(p);Avec malloc, j’alloue dans ma mémoire l’espace
pour mes deux pointeurs, que je fais pointer vers des chaînes de
caractère.
Dans une logique “orientée objet”, cependant, je ne veux pas que ces détails d’implémentation de ma classe (comme le nom des attributs) soient connus. Je voudrais idéalement ne manipuler que des méthodes “publiques”.
On va donc commencer par créer deux fichiers:
person.c et person.h. Dans
person.h, nous allons mettre nos déclarations
publiques:
// person.h
typedef struct Person Person;
Person* Person_make(char* first_name, char* last_name);
void Person_destroy(Person* p);
void Person_print(Person* p);Je prévois ici trois méthodes: un constructeur, un
destructeur, et une méthode pour afficher le contenu de l’objet.
Avec typedef struct Person Person, on permet de ne
pas retaper struct Person à chaque fois en lui
donnant un “alias” Person.
L’implémentation, maintenant, va se trouver dans
person.c, et sera invisible pour les utilisateurs
de person.h:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "person.h"
struct Person {
char* first_name;
char* last_name;
};
Person* Person_make(char* first_name, char* last_name){
Person* p = malloc(sizeof(Person));
p->first_name = malloc(strlen(first_name)+1);
p->last_name = malloc(strlen(last_name)+1);
strcpy(p->first_name, first_name);
strcpy(p->last_name, last_name);
return p;
}
void Person_destroy(Person* p) {
free(p->first_name);
free(p->last_name);
free(p);
}
void Person_print(Person* p) {
printf("[Person]: %s %s\n", p->first_name, p->last_name);
}Dans le constructeur, on alloue de l’espace pour les chaînes
de caractères des noms et prénoms, que l’on copie. On ne veut
pas juste copier les pointeurs, sinon celui qui appelle
Person_make pourrait ensuite changer la valeur de
p->first_name, ou faire un free
dessus. Dans le destructeur, on fait le ménage.
Pour utiliser tout ceci, écrivons un main.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "person.h"
int main(){
printf("Start program\n");
Person* p = Person_make("Adrien", "Foucart");
Person_print(p);
Person_destroy(p);
printf("End program\n");
return 0;
}Ce main n’a plus aucune visibilité sur ce que
contient Person. Si j’essaie d’ajouter
printf("%s\n", p->first_name);, j’ai une erreur
de compilation, car person.h ne contient pas de
détail sur les “attributs” de Person. Je ne peux les utiliser
qu’à travers ses méthodes.
Autrement dit: Person se comporte maintenant
comme un “objet” qui, par exemple en Java, s’écrirait1:
public class Person {
private String first_name;
private String last_name;
public Person(String first_name, String last_name){
this.first_name = first_name;
this.last_name = last_name;
}
public void print(){
System.out.println("[Person]: " + first_name + " " + last_name);
}
}On ne va pas encore très loin dans le concept, mais je dirais que ça compte, comme premier pas. Pour la suite… on verra dans les épisodes suivants!
Techniquement, on ne fait pas ici la “copie” du contenu des
Stringque l’on fait enC. Lefirst_namedansPersonpointera vers la même adresse que leStringdans le scope qui a appelé le constructeur. Mais comme en Java lesStringsont des constantes, ça ne risque pas de causer un soucis. Si quelque chose essaye de modifier leStringpar après en dehors de la classe, ça y créera un nouveauStringavec une nouvelle adresse, et l’ancienne adresse (retenue parPerson) contiendra toujours la même information.↩︎