📘 Cours 1 — Introduction à l'informatique
& programmation en C

Des premiers concepts jusqu'au code de la factorielle — tout ce qu'un élève ingénieur devrait savoir pour démarrer.

1. Qu'est-ce que l'informatique ?

Informatique vient de la contraction d'information et d'automatique. Ce n'est pas l'étude des ordinateurs — c'est l'étude de la représentation, du traitement et de la transmission de l'information de manière automatique.

L'informatique n'est pas la science des ordis

Un ordinateur n'est qu'un outil. L'informatique, c'est :

💡 À retenir : l'informatique est la science. Le code est l'expression de cette science. Savoir coder sans comprendre l'algorithmique, c'est savoir écrire sans savoir quoi dire.

Petite chronologie éclair

AnnéeQuoiPourquoi c'est important
~250 av. J.-C.Algorithme d'Euclide (PGCD)Un des plus vieux algorithmes encore utilisés
1843Ada Lovelace écrit le 1er programmePour la machine analytique de Babbage — avant même que la machine existe
1936Turing invente la machine de TuringFonde théoriquement l'informatique. Aussi : il a cassé Enigma.
1945Von Neumann propose l'architecture stockée-programmeLe programme et les données dans la même mémoire — encore utilisé aujourd'hui
1972Dennis Ritchie crée le CLe langage qui a écrit UNIX, Linux, Windows… et votre factorielle
1980–90PC, Internet, WebL'informatique sort des labos. Fin de la préhistoire.
🕰️ L'informatique est une science jeune : Turing avait 24 ans en 1936. Aujourd'hui, un·e étudiant·e en 1re année a accès à plus de puissance de calcul que la NASA en 1969 pour aller sur la Lune. Vous l'utilisez pour regarder des vidéos de chats. C'est beau.

2. Représentation de l'information

Bits, octets, nombres

Un ordinateur ne comprend que deux états : 0 et 1 — c'est le binaire. Chaque 0 ou 1 est un bit (Binary digit). 8 bits forment un octet (byte).

1 bit  → 2 valeurs :  0 ou 1
 8 bits → 256 valeurs : 0 à 255
32 bits → 4 294 967 296 valeurs
64 bits → 18 446 744 073 709 551 616 valeurs
⚡ Bit : unité élémentaire d'information. Deux états possibles.
⚡ Octet : 8 bits. Unité de base pour stocker un caractère (texte).
⚡ Mot machine : taille naturelle de données du processeur (32 ou 64 bits).

Compter en binaire

On compte en base 2 (binaire) comme on compte en base 10 (décimal) :

BinaireDécimalHexadécimal (base 16)
000000x0
000110x1
001020x2
010150x5
1010100xA
1111150xF
1 0000160x10

En hexadécimal (base 16), on utilise 0–9 puis A–F pour représenter les valeurs 10 à 15. C'est très utilisé en programmation pour lire des octets : 0x7F est plus lisible que 01111111.

💡 Les humains comptent en base 10 (10 doigts). Les ordis comptent en base 2 (2 états électriques). Les programmeurs comptent en base 16 (parce que c'est plus court que le binaire et qu'ils aiment se la jouer).

Entiers signés et limites

Pour représenter des nombres négatifs, on utilise le complément à deux : le bit de poids fort (le plus à gauche) indique le signe (1 = négatif).

Avec long long (64 bits signés), la plage est :

−9 223 372 036 854 775 808  à  +9 223 372 036 854 775 807

Notre 20! = 2 432 902 008 176 640 000 tient encore. 21! dépasse, et le C ne vérifie pas — c'est le débordement (overflow). Le résultat devient faux silencieusement.

🔍 Bon réflexe : quand vous manipulez des entiers en C, demandez-vous : « est-ce que mes valeurs peuvent dépasser la plage du type ? » Si oui, il faut protéger le code — ou utiliser un type plus grand.

3. Algorithmique — l'art de résoudre des problèmes

Qu'est-ce qu'un algorithme ?

Algorithme : une suite finie d'instructions non-ambiguës qui, partant d'entrées données, produit une sortie et se termine en temps fini.

Un algorithme doit être :

📌 Origine du mot : Al-Khwārizmī, mathématicien perse du IXe siècle. Son nom a donné « algorithme ». Ses travaux sur l'algèbre aussi. Sacré CV.

Exemple du quotidien : faire un café

// Algorithme : préparer un café (version express)
Données : eau, café moulu, machine
Résultat : une tasse de café potable

1. Remplir le réservoir d'eau
2. Mettre le café dans le filtre
3. Placer la tasse sous la sortie
4. Allumer la machine
5. Attendre que la tasse soit pleine
6. Servir

Si l'une des étapes est ambiguë (« mettre un peu de café »), le résultat varie selon l'exécutant. En programmation, c'est pareil — la machine ne devine pas. Elle exécute exactement ce qui est écrit.

☕ Un algorithme est comme une recette de cuisine : si la première étape est « prenez une vache » au lieu de « sortez le lait du frigo », le temps d'exécution explose. Et si vous écrivez « salez à votre convenance », c'est pas un algorithme, c'est du wishful coding.

Exemple : la recette de la factorielle

Pour calculer n! (produit de 1 à n), on peut écrire :

Entrée : un entier n ≥ 0
Sortie : n! (le produit 1 × 2 × 3 × … × n)

         Si n = 0 → retourner 1
         Sinon :
           résultat = 1
           Pour i de 1 à n :
               résultat = résultat × i
           Retourner résultat

On initialise un accumulateur, on le multiplie par chaque entier, on rend le résultat. C'est une approche itérative — on répète une opération un nombre connu de fois.

Pseudo-code

Le pseudo-code est un langage informel pour décrire un algorithme sans se soucier de la syntaxe d'un vrai langage. On en trouve partout : dans les livres, sur les tableaux blancs, et dans les copies d'étudiants le matin de l'examen.

Pseudo-code
Fonction factorielle(n):
    Si n < 0 alors
        retourner -1
    Fin Si
    res ← 1
    Pour i ← 2 à n faire
        res ← res × i
    Fin Pour
    retourner res
C réel
long long factorielle(int n) {
    if (n < 0) return -1;
    long long res = 1;
    for (int i = 2; i <= n; i++) {
        res *= i;
    }
    return res;
}
📝 Le pseudo-code a un avantage sur le C : personne ne vous dit « t'as oublié un point-virgule ». Il a un inconvénient : ça ne compile pas. Mais c'est un excellent brouillon.

4. Du problème au programme

Les grandes étapes pour transformer un problème en code qui marche :

📋 Problème
📐 Algorithme
📝 Pseudo-code
💻 Code C
  1. Comprendre le problème : quelles sont les entrées ? la sortie attendue ? les cas particuliers ?
  2. Concevoir l'algorithme : quelle méthode mathématique ou logique permet de résoudre le problème ?
  3. Écrire le pseudo-code : formaliser sans se perdre dans la syntaxe.
  4. Implémenter : traduire le pseudo-code dans un langage de programmation.
  5. Tester : vérifier que le résultat est correct — sur des cas normaux, des cas limites, et des cas d'erreur.
🐛 Bug : une erreur dans le programme. Le terme vient d'un vrai insecte (une mite) coincé dans un relais de l'ordinateur Harvard Mark II en 1947. Grace Hopper a noté « First actual case of bug being found. » Depuis, on appelle ça un bug même quand c'est nous qui avons mal codé.
🧭 La différence entre un bon et un mauvais programmeur ? Le bon programmeur passe 30% de son temps à coder et 70% à tester. Le mauvais programmeur aussi, mais il le découvre à 2h du matin la veille de la soutenance.

5. Premiers pas en C

Le C est un langage compilé, impératif, créé en 1972 par Dennis Ritchie chez Bell Labs. Il a écrit UNIX, qui a écrit Linux, qui fait tourner Internet et les téléphones Android. Bref : le C, c'est le socle.

Structure d'un programme

Tout programme C commence par main() :

int main() {
    return 0;
}

Syntaxe : ensemble des règles d'écriture du langage. Comme en français, chaque phrase a une structure. Ici :

Afficher du texte

Pour communiquer avec l'utilisateur, on utilise printf (déclarée dans <stdio.h>) :

#include <stdio.h>

int main() {
    printf("Bonjour les ingenieurs !\n");
    return 0;
}
⚠️ #include : directive qui insère le contenu d'un fichier d'en-tête. Permet d'utiliser des fonctions sans les réécrire.
⚠️ \n : caractère d'échappement qui représente un saut à la ligne (newline). Sans lui, tout reste sur la même ligne.

Variables et types

Une variable est une boîte qui contient une valeur. Le type dit quelle sorte de valeur peut aller dans la boîte :

TypeTailleExempleUsage
int32 bitsint n = 5;Entier signé, −2³¹ à 2³¹−1
long long64 bitslong long x = 120LL;Très grand entier signé
float32 bitsfloat pi = 3.14f;Nombre à virgule (simple précision)
double64 bitsdouble pi = 3.1415926535;Nombre à virgule (double précision)
char8 bitschar c = 'A';Un caractère (lettre, chiffre, symbole)
💡 Convention : long long s'écrit avec deux mots. Pour les constantes littérales, on ajoute LL (ex: 2432902008176640000LL) pour dire au compilateur « oui, je sais que c'est un très grand nombre, merci de ne pas râler ».

printf et les formats

printf utilise des spécificateurs de format pour afficher des variables :

SpécificateurTypeExemple
%dintprintf("%d", n);
%lldlong longprintf("%lld", res);
%fdouble / floatprintf("%f", pi);
%ccharprintf("%c", c);
%schaîne de caractèresprintf("%s", "coucou");
%xint (hexa)printf("%x", 255);ff
printf("factorielle(%d) = %lld\n", n, res);
// Affiche : factorielle(5) = 120
🎯 printf est le couteau suisse de l'affichage en C. Il y a 50 façons de formater un nombre. Vous en utiliserez 4. Les 46 autres, vous les chercherez sur Internet — et c'est normal.

6. Opérateurs et expressions

Les opérateurs permettent de combiner des valeurs pour en produire de nouvelles.

Arithmétique

OpérateurOpérationExempleRésultat
+Addition10 + 313
-Soustraction10 - 37
*Multiplication10 * 330
/Division entière10 / 33 (troncature)
%Modulo (reste)10 % 31
++Incémentationi++i += 1
--Décrémentationi--i -= 1

Attention : la division entre deux entiers donne un entier (troncature, pas d'arrondi). 10 / 3 = 3, pas 3,333…

int a = 10 / 3;     // a = 3 (division entière)
double b = 10.0 / 3; // b = 3.333333 (division réelle)

Les opérateurs abrégés : res *= i équivaut à res = res * i. Ça marche pour +=, -=, /=, %= aussi.

Comparaison

OpérateurSignificationExemple vrai
==Égal à5 == 5
!=Différent de5 != 3
<Plus petit que3 < 5
>Plus grand que5 > 3
<=Plus petit ou égal5 <= 5
>=Plus grand ou égal5 >= 3
🚨 Piège classique : ne pas confondre = (affectation) et == (comparaison). if (n = 5) est toujours vrai (ça met 5 dans n, et 5 est non-nul, donc « vrai »). Le compilateur vous avertira avec -Wall. Activez toujours -Wall.

Logique

Pour combiner des conditions :

OpérateurNomExemple
&&ET logiqueif (n > 0 && n <= 20)
||OU logiqueif (n == 0 || n == 1)
!NON logiqueif (!erreur)
🧠 && et || sont paresseux (short-circuit) : si la première partie suffit à décider, la seconde n'est pas évaluée. C'est la flemme au niveau processeur. Et ça évite des bugs du genre if (ptr != NULL && ptr->value == 42).

7. Structures de contrôle

Un programme linéaire s'arrête vite. Pour faire des choix et répéter des actions, on a les structures de contrôle.

if / else — prendre une décision

Exécute un bloc si la condition est vraie, un autre sinon :

if (n < 0) {
    return -1;   // pas de factorielle pour les negatifs
} else {
    // continuer le calcul
}

Condition : toute expression qui vaut 0 (faux) ou ≠ 0 (vrai). En C, il n'y a pas de type booléen dédié avant C99 (stdbool.h apporte bool). Avant, on utilise int.

💡 Style : on peut omettre les accolades pour une seule instruction, mais ne le faites pas. if (x) y = 1; z = 2; — la deuxième ligne n'est pas dans le if. Mettez toujours { }, même pour une ligne. Votre futur vous remerciera.

for — répéter un nombre connu de fois

La boucle for est parfaite quand on sait à l'avance combien de fois répéter :

// Structure : for (initialisation ; condition ; incrément)
for (int i = 2; i <= n; i++) {
    res *= i;
}

Les trois parties :

  1. Initialisation : int i = 2 — exécutée une fois au début
  2. Condition : i <= n — testée avant chaque tour ; si fausse, on sort
  3. Incrément : i++ — exécuté à la fin de chaque tour

Variantes

// Parcourir un tableau d'éléments
for (int i = 0; i < nb_tests; i++) { ... }

// Compter à rebours
for (int i = n; i >= 1; i--) { ... }

// Pas de 2
for (int i = 0; i < 100; i += 2) { ... }

while — répéter tant qu'une condition est vraie

Quand on ne sait pas à l'avance combien de tours, on utilise while :

while (condition) {
    // corps
}

// Exemple : tant que l'utilisateur n'a pas donné une valeur valide
int n;
printf("Entrez n (0-20) : ");
scanf("%d", &n);
while (n < 0 || n > 20) {
    printf("Valeur invalide. Recommencez : ");
    scanf("%d", &n);
}

Notre factorielle utilise for car on connaît exactement le nombre d'itérations (n−1 multiplications).

🔁 La différence entre for et while ? for c'est « je sais combien de fois » (tour de manège). while c'est « je ne sais pas quand ça s'arrête » (attente du bus). Les deux peuvent faire les mêmes choses, mais choisir le bon rend le code plus lisible.

8. Fonctions — écrire son propre outillage

Pourquoi découper ?

Une fonction est une boîte noire qui prend des paramètres en entrée et produit une valeur de retour. Avantages :

// Sans fonction : tout dans main
int main() {
    int n = 5;
    long long res = 1;
    for (int i = 2; i <= n; i++) res *= i;
    printf("%lld\n", res);
    return 0;
}

// Avec fonction : factorielle est réutilisable, testable
long long factorielle(int n) {
    long long res = 1;
    for (int i = 2; i <= n; i++) res *= i;
    return res;
}

int main() {
    printf("%lld\n", factorielle(5));
    return 0;
}

Syntaxe et vocabulaire

type_retour nom_de_la_fonction(type_param1 nom1, type_param2 nom2) {
    // corps
    return valeur;  // doit correspondre au type_retour
}
TermeDéfinitionNotre exemple
SignatureNom + paramètres de la fonctionfactorielle(int n)
Paramètre (formel)Variable reçue par la fonctionn
Argument (réel)Valeur passée lors de l'appel5 dans factorielle(5)
Valeur de retourRésultat renvoyé par return120
Variable localeVariable déclarée dans la fonctionres, i

Portée des variables

Une variable déclarée à l'intérieur d'une fonction n'existe que dans cette fonction. On dit que sa portée (scope) est locale.

int factorielle(int n) {
    long long res = 1;   // locale : n'existe que ici
    for (int i = 2; i <= n; i++) {
        res *= i;         // i aussi est local à la boucle (C99)
    }
    return res;
} // res et n disparaissent ici

int main() {
    printf("%d", n);    // ERREUR : n n'existe pas ici
    return 0;
}
🚪 La portée des variables, c'est comme les portes dans une maison : vous pouvez voir ce qu'il y a dans votre salon, mais pas dans la chambre du voisin. Sauf si vous ouvrez la porte (variable globale), mais c'est mal vu — comme entrer sans frapper.

9. Construire factorielle — étape par étape

Version 1 : juste la fonction

Commençons par l'essentiel. On veut une fonction qui calcule n! :

long long factorielle(int n) {
    if (n < 0) return -1;     // protection : pas de factorielle négative
    long long res = 1;       // accumulateur : 0! = 1, on part de 1
    for (int i = 2; i <= n; i++) {  // on commence à 2 (×1 ne change rien)
        res *= i;            // accumulateur : res = res × i
    }
    return res;              // retour du résultat
}
EntréeDéroulementSortie
n = 0la boucle ne s'exécute pas (2 ≤ 0 faux)1
n = 1la boucle ne s'exécute pas (2 ≤ 1 faux)1
n = 5res = 1×2×3×4×5120
n = −3return -1 immédiat−1
💡 Pourquoi commencer à 2 ? On pourrait commencer à 1 (1×1×2×3…), mais multiplier par 1 est inutile. On économise un tour de boucle. C'est un micro-optimisation, mais c'est aussi plus correct mathématiquement : on construit le produit des entiers de 1 à n, la multiplication par 1 étant neutre.

Version 2 : avec tests

Une fonction, ça se teste. On ajoute un main() avec une batterie de cas :

int main() {
    struct {
        int n;
        long long attendu;
    } tests[] = {
        {0, 1},
        {1, 1},
        {2, 2},
        {3, 6},
        {5, 120},
        {10, 3628800},
        {12, 479001600},
        {20, 2432902008176640000LL},
    };
    int nb_tests = sizeof(tests) / sizeof(tests[0]);
    int passes = 0, echoues = 0;

    for (int i = 0; i < nb_tests; i++) {
        long long res = factorielle(tests[i].n);
        if (res == tests[i].attendu) {
            printf("OK   factorielle(%d) = %lld\n", tests[i].n, res);
            passes++;
        } else {
            printf("FAIL factorielle(%d) = %lld (attendu %lld)\n",
                   tests[i].n, res, tests[i].attendu);
            echoues++;
        }
    }

    printf("\n%d passes, %d echoues sur %d tests\n", passes, echoues, nb_tests);
    return echoues > 0 ? 1 : 0;
}

Quelques nouveautés dans ce code :

🧪 8 tests, c'est peu pour un logiciel de production. Mais pour une fonction de 5 lignes, c'est suffisant — les cas 0, 1, la croissance, et le maximum 64-bit. Si vous ajoutez une variante récursive, vous repasserez les mêmes tests. C'est ça, la régression !

Variations

Voici d'autres façons d'écrire la même chose — chacune avec ses avantages et inconvénients :

// Version récursive (élégante, risquée pour n grand)
long long factorielle_rec(int n) {
    if (n < 0) return -1;
    if (n <= 1) return 1;
    return n * factorielle_rec(n - 1);
}

// Version avec while (équivalente, style différent)
long long factorielle_while(int n) {
    if (n < 0) return -1;
    long long res = 1;
    int i = 2;
    while (i <= n) {
        res *= i;
        i++;
    }
    return res;
}

// Version décroissante (moins efficace, mais instructive)
long long factorielle_desc(int n) {
    if (n < 0) return -1;
    long long res = 1;
    for (int i = n; i > 1; i--) {
        res *= i;
    }
    return res;
}

La version itérative (notre version) est la meilleure pour ce cas : O(n) en temps, O(1) en mémoire, pas de risque de débordement de pile, et le compilateur l'optimise très bien (elle est courte).

🔄 La récursion, c'est comme acheter un meuble IKEA : vous répétez la même opération jusqu'à ce qu'il n'y ait plus de pièces. Élégant sur le papier, frustrant dans la pratique parce que la boîte d'origine contenait les mêmes pièces en fait. Mais tout le monde adore les meubles suédois.

10. Tester son code

Un programme non testé est un programme qui a tort — mais qui ne le sait pas encore. Tester permet de :

Quoi tester ?

Type de testExempleNotre programme
Cas nominalUne entrée normalefactorielle(5) → 120
Cas limiteAux bornes du domainefactorielle(0) → 1, factorielle(20) → 20!
Cas d'erreurEntrée invalidefactorielle(-1) → -1
Cas de dépassementValeur qui fait planterfactorielle(21) → overflow silencieux (non testé ici)

Notre batterie de 8 tests couvre les cas nominaux (5, 10, 12) et les cas limites (0, 1, 20). Le cas d'erreur (n négatif) n'est pas testé automatiquement — on pourrait l'ajouter.

🔍 Pour aller plus loin : en génie logiciel, on écrit les tests avant le code (TDD — Test Driven Development). On commence par un test qui échoue, on écrit le code pour le faire passer, on refactorise. C'est comme faire la liste des courses avant d'aller au supermarché : on achète moins de chips.
🐛 Un jour, votre programme aura un bug. Tôt ou tard. La question n'est pas « est-ce qu'il y a un bug ? » mais « est-ce que le test va le trouver avant l'utilisateur ? ». Spoiler : l'utilisateur le trouve toujours.

11. La chaîne de compilation

Comment passe-t-on du code C lisible… au binaire que le processeur exécute ? Voici les étapes, visibles avec notre Makefile :

📄 .c
🔧 Préprocesseur
⚙️ Compilateur
📝 Assembleur
🔗 Éditeur de liens
💾 Exécutable
ÉtapeFait quoi ?CommandeFichier produit
1. PréprocesseurRemplace #include, #define, etc.gcc -E— (code C développé)
2. CompilationTraduit le C en assembleurgcc -S.s (assembleur)
3. AssemblageTraduit l'assembleur en code machine (obj)gcc -c ou as.o (objet)
4. Édition de liensAssemble les .o + bibliothèques → exécutablegcc (sans -c)factorielle

En pratique avec notre Makefile

# 1. Générer l'assembleur (.s)
gcc -Wall -Wextra -std=c99 -S -o factorielle.s factorielle.c

# 2. Assembler le .s en exécutable
gcc -Wall -Wextra -std=c99 -o factorielle-asm factorielle.s

# 3. Tout d'un coup
gcc -Wall -Wextra -std=c99 -o factorielle factorielle.c

Le fichier factorielle.s est un fichier texte en assembleur (AT&T, le style par défaut chez GCC). Chaque ligne correspond à une instruction processeur : movq, addl, imulq, etc.

⚡ Assembleur : représentation textuelle du code machine. Chaque instruction assembleur correspond exactement à une instruction du processeur. Contrairement au C, il n'y a pas « d'abstraction ». C'est le plus proche du processeur sans écrire du binaire à la main.
⚙️ La chaîne de compilation, c'est comme une usine à saucisses : vous mettez du C frais d'un côté, des bibliothèques bio, et de l'autre côté sort un binaire prêt à consommer. Entre les deux, il y a des étapes, des traitements, et parfois des avertissements (warnings) — c'est le contrôle qualité. Activez -Wall comme vous lavez les légumes.

12. Pour aller plus loin

Ce cours pose les fondations. Les documents suivants approfondissent chaque aspect avec le même style et la même rigueur :

DocContenuPublic
📐 factorielle-doc.html Tout sur le programme factorielle : code, tests, overflow, variantes Ceux qui veulent les détails de l'implémentation
📗 asm-doc.html Les instructions assembleur une par une, avec humour Ceux qui veulent voir ce que le compilateur a fabriqué
🧠 processor-doc.html Anatomie du processeur : ALU, registres, cache, pipeline… Ceux qui veulent comprendre ce qui se passe sous le capot

Quelques conseils pour la suite

🎓 Félicitations ! Vous avez survécu au premier cours. Vous savez ce qu'est un algorithme, un bit, une variable, une boucle, une fonction, un compilateur, et pourquoi votre café refroidit pendant que vous lisez ce document. Le monde de l'informatique s'ouvre à vous — bon courage, amusez-vous bien, et n'oubliez pas le point-virgule.