clavier ouvert

Blog d'enseignement d'Adrien Foucart.
Toutes les opinions présentées ici n'engagent que moi. Blog garanti sans pub, sans traqueurs, et 100% rédigé par un humain.

🟨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!


  1. Techniquement, on ne fait pas ici la “copie” du contenu des String que l’on fait en C. Le first_name dans Person pointera vers la même adresse que le String dans le scope qui a appelé le constructeur. Mais comme en Java les String sont des constantes, ça ne risque pas de causer un soucis. Si quelque chose essaye de modifier le String par après en dehors de la classe, ça y créera un nouveau String avec une nouvelle adresse, et l’ancienne adresse (retenue par Person) contiendra toujours la même information.↩︎

Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur Mastodon ou par mail (adrien@adfoucart.be)