Résumé
Cette leçon constitue l’unité initiale du cours et en expose le plan d’études complet. Elle énonce le but du cours, qui est la programmation elle-même et non un langage ; elle explique pourquoi ses fondements sont enseignés en C++, un langage qui ne cache pas la machine, et non dans un langage à la courbe plus accueillante ; elle indique les étudiants auxquels il s’adresse, le peu qu’il tient pour acquis et les cours auxquels il mène ; elle énonce les sept objectifs d’apprentissage globaux et les unités qui contribuent à chacun ; elle décrit ses huit unités de contenu et les leçons qui les composent ; elle estime la charge de travail totale qu’exige le cours, sa répartition par unités, un rythme d’étude suggéré et ses crédits ; elle explique comment ses leçons sont faites et, enfin, elle donne des conseils sur la manière de l’étudier, sur l’usage des assistants d’intelligence artificielle et sur la gestion de versions.
Objectifs d’apprentissage
- Décrire la suite des huit unités du cours et expliquer ce que chacune apporte aux suivantes.
- Énoncer les sept objectifs d’apprentissage globaux du cours et reconnaître quelles unités contribuent à chacun.
- Expliquer à l’aide d’un exemple pourquoi le cours enseigne les fondements de la programmation en C++ et non dans un langage qui cache les types et la mémoire.
- Calculer, à partir des heures estimées par unité, la charge de travail qu’exige le cours ou une partie de celui-ci, et planifier avec elle un rythme d’étude personnel.
But
Le cours enseigne à écrire des programmes corrects à partir de rien : à convertir un problème énoncé en mots en une suite précise d’instructions, à choisir comment représenter les données, à gouverner l’ordre dans lequel s’exécute ce qui est écrit et à organiser le code en fonctions dotées d’une interface claire. Si cela est ainsi, son objet n’est pas un langage, mais la programmation elle-même : concevoir un algorithme, représenter les données, décomposer un problème et raisonner sur la correction et le coût de ce qu’on écrit. C’est pourquoi il s’appelle « Fondements de la programmation », et c’est pourquoi chaque construction de C++ est présentée avec le problème qu’elle résout et avec la manière de raisonner à son sujet, et jamais comme un catalogue de syntaxe.
C++ est donc l’instrument du cours et non sa fin. Or un instrument n’est pas indifférent à ce qu’on apprend avec lui, et le choix de celui-ci, qui semble à première vue le plus difficile, est délibéré ; la section suivante le justifie. Il convient d’en annoncer dès maintenant la conséquence la plus visible : chaque synthèse d’unité contient une section fixe, « Ce qui vaut dans tout langage », qui sépare ce qui a été appris de sa forme en C++ et dit comment il s’exprime, ou ce qui change, dans d’autres langages. Il s’ensuit que l’étudiant qui termine le cours ne saura pas seulement écrire en C++, mais reconnaître dans n’importe quel langage les mêmes idées sous un autre habit.
Pourquoi C++ et non un langage plus accueillant
Il existe des langages à la courbe plus douce, comme Python, dans lesquels un premier programme utile s’écrit en quelques lignes, sans déclarer de types, sans compiler et sans se demander où vit une donnée. Si le but était d’écrire vite des programmes qui fonctionnent, ce serait le chemin. Le but de ce cours, en revanche, est de comprendre ce que fait un programme, et pour cela il convient d’avoir un langage qui ne cache pas la machine. C++ oblige à voir six choses que les langages plus commodes règlent en silence :
- Les types. Chaque donnée est déclarée avec un type, qui fixe quelles valeurs elle peut prendre, combien de place elle occupe et quelles opérations elle admet ; le compilateur rejette ce qui ne concorde pas avant d’exécuter quoi que ce soit.
- La compilation. Écrire, compiler, lier et exécuter sont des moments distincts, et chaque erreur appartient à l’un d’eux ; savoir auquel, c’est la moitié de la corriger.
- La mémoire. Une variable est une case nommée, avec une durée de vie et une portée ; une référence est un autre nom pour la même case, et une référence à une case qui n’existe plus est une erreur réelle.
- Le coût de la copie. Passer un vecteur à une fonction peut le copier en entier ; choisir entre valeur et référence est une décision du programmeur, et non de la machine.
- Les limites des nombres. Un entier de 32 bits ne dépasse pas , et un nombre à virgule flottante ne conserve pas exactement.
- Le comportement indéfini. Ce que la norme ne définit pas (sortir d’un tableau, faire déborder un entier signé, lire une variable non initialisée) ne produit pas une erreur garantie, mais n’importe quoi ; c’est pourquoi le cours compile toujours avec les avertissements et avec les détecteurs (sanitizers).
L’exemple suivant le montre en petit. Le programme calcule les factorielles de à dans une variable de type int. En Python, dont les entiers croissent sans limite, le même calcul afficherait sans broncher ; en C++, le produit de par ne tient pas dans un int, et le détecteur de comportement indéfini, avec lequel le cours compile tous ses exemples, arrête le programme à la ligne exacte :
#include <print>int main(){ int factorial{1}; for (int n{1}; n <= 15; ++n) { factorial = factorial * n; // le produit tient-il dans un int ? std::println("{}! = {}", n, factorial); }}1! = 12! = 23! = 64! = 245! = 1206! = 7207! = 50408! = 403209! = 36288010! = 362880011! = 3991680012! = 479001600main.cpp:7:31: runtime error: signed integer overflow: 479001600 * 13 cannot be represented in type 'int'Le programme s’est terminé avec le code 1.
Si cela est ainsi, le langage a obligé à se poser une question que l’autre aurait cachée : combien tient dans le type choisi et que faire quand cela ne tient pas. Aucune des deux réponses n’est meilleure dans l’abstrait ; ce qui importe, c’est de savoir que la question existe. La leçon sur les entiers et leur représentation y répond, et la section sur le comportement indéfini de chaque synthèse y revient.
Il s’ensuit une asymétrie, qui est la raison de fond du choix. Qui apprend les fondements en C++ comprend ensuite ce que font pour lui les langages plus commodes : le ramasse-miettes, les entiers sans limite, la vérification des indices, la conversion silencieuse des types. Qui apprend d’abord dans l’un d’eux, en revanche, laisse ces couches dans l’ombre, et s’y heurte plus tard, quand un programme réel échoue pour l’une des causes que le langage lui avait cachées. Par conséquent, le chemin que choisit le cours est plus exigeant au début, mais plus transférable : ce qu’on apprend ici vaut dans tout langage, et ce qu’on apprend dans un langage qui le cache ne vaut pas toujours ici.
Or C++ est un grand langage, et un cours de fondements doit résister à la tentation de l’enseigner en entier. Ce premier cours se limite à la programmation structurée (données, expressions, structures de contrôle, séquences, texte et fonctions) dans la norme C++23, avec ses formes modernes : la sortie avec std::println, les séquences avec std::array et std::vector, le texte avec std::string, les algorithmes de la bibliothèque sous leur forme de ranges. Il laisse à Programmation en C++ II ce qui exige cette base : les pointeurs et la mémoire dynamique, les classes avec méthodes, les modèles (templates) personnels, les fonctions anonymes et les exceptions.
Public et connaissances préalables
Le cours s’adresse à qui n’a jamais programmé, quel que soit son parcours d’études et dès la première année : étudiants en informatique qui ont besoin d’un fondement solide avant les cours de structures de données et de systèmes, étudiants en ingénierie et en sciences qui ont besoin de calculer avec un ordinateur sans lui faire aveuglément confiance, et étudiants en mathématiques qui veulent voir leurs algorithmes convertis en programmes qui s’exécutent.
C’est un cours de niveau 1, sans prérequis et autonome. Il ne suppose que deux choses : l’arithmétique scolaire, avec la division entière et son reste, et le maniement élémentaire d’un ordinateur (créer un dossier, enregistrer un fichier, ouvrir un programme). Ce qu’il utilise de la représentation binaire, du raisonnement par cas, de la logique des conditions ou de la récurrence, il l’enseigne à l’endroit où il en a besoin : le système binaire, dans la leçon sur les entiers ; les lois de De Morgan, dans la leçon sur les relations et la logique ; la récurrence, dans la leçon sur la récursivité. Les renvois à d’autres cours du site qui apparaissent dans les leçons sont donc des approfondissements, et non des prérequis.
Où il mène
De ce qui précède suit la place du cours dans le site. Il n’a pas de prérequis et il est l’aboutissement de deux parcours d’études, Informatique et calcul, et Ingénierie, parce que dans les deux la programmation est à la fois outil et objet. Il convient donc qu’il soit l’un des premiers cours de ces parcours, avec LaTeX pour la rédaction académique et Logique mathématique I.
Il a un cours complémentaire, qu’on peut suivre avant, en parallèle ou après. Logique mathématique I étudie comme théorie ce qu’on utilise ici comme pratique : les conditions composées, les tables de vérité et les lois de De Morgan de l’unité 3, et l’idée de démontrer qu’une procédure est correcte, qui prend dans ce cours la forme de l’invariant d’une boucle et de la récurrence sur une récursivité. Algèbre et géométrie I, pour sa part, démontre les résultats sur les nombres que le cours programme : la division euclidienne, l’algorithme d’Euclide, les systèmes de numération et le schéma de Horner. Et LaTeX pour la rédaction académique enseigne, dans sa leçon Écrire en équipe, la gestion de versions avec Git, que ce cours recommande d’utiliser dès le premier programme.
Or le cours laisse délibérément de côté ce qui exige sa base. Sa suite prévue est Programmation en C++ II, qui traitera des pointeurs et de la mémoire dynamique, des classes et des objets, des modèles, des fonctions anonymes, des exceptions et de la bibliothèque standard en profondeur. D’où il suit que le cours n’est pas un premier tronçon d’un manuel de C++, mais un cours complet de fondements, qui peut se clore ici ; qui poursuit, en revanche, trouvera dans le cours II la même discipline appliquée à des programmes dont la taille ne permet plus de s’en passer.
Objectifs d’apprentissage globaux
Les objectifs suivants décrivent ce qu’un étudiant qui achève le cours doit être capable de faire. Chacun est formulé comme une performance observable, de sorte qu’il puisse être vérifié par une tâche concrète ; chaque leçon de contenu énonce à son tour ses propres objectifs, qui servent un ou plusieurs des objectifs globaux, et la leçon de clôture montre dans quelles unités et quelles leçons chacun est travaillé. Les sept objectifs sont numérotés pour pouvoir être cités.
- Objectif 1. Résoudre des problèmes avec des programmes : spécifier ce qui entre et ce qui sort, concevoir un algorithme, le suivre à la main, puis l’écrire, le compiler, l’exécuter et le déboguer, en interprétant les messages et les avertissements du compilateur.
- Objectif 2. Choisir le type adéquat pour chaque donnée et raisonner sur les limites de sa représentation : la plage des entiers, le dépassement de capacité, l’arrondi de la virgule flottante et l’encodage du texte.
- Objectif 3. Construire des expressions correctes, en connaissant la priorité, les conversions de type, les effets de bord et ce que la norme laisse indéfini.
- Objectif 4. Concevoir les structures de contrôle avec des décisions et des boucles correctes, qui se terminent, et justifier leur correction par un invariant.
- Objectif 5. Décomposer un problème en fonctions dotées d’interfaces claires, choisir comment passer les paramètres, et résoudre des problèmes par récursivité.
- Objectif 6. Représenter et traiter des données composées (séquences, tableaux, enregistrements, texte et fichiers) avec les algorithmes de base et avec la bibliothèque standard, en estimant leur coût.
- Objectif 7. Organiser des programmes plus grands en plusieurs fichiers, les spécifier par une précondition et une postcondition, les tester de manière systématique et argumenter leur correction.
Cinq objectifs ont leur unité : le 2, celle des données et des types ; le 3, celle des expressions ; le 4, celle des structures de contrôle ; le 5, celle des fonctions ; et le 6, celle des données composées, qui se prolonge dans celle du texte et des fichiers, où l’objectif 2 reparaît avec l’encodage UTF-8. Les deux autres, en revanche, parcourent le cours entier. L’objectif 1 a pour unité principale la première, qui enseigne le cycle consistant à spécifier, écrire, compiler et exécuter, ainsi que la leçon sur le débogage de la dernière ; mais il s’exerce dans toutes, parce que chaque programme du cours est compilé, exécuté et corrigé. L’objectif 7 a pour unité principale la dernière, celle des programmes plus grands ; mais il commence dans le guide de style de la première, se poursuit dans les constantes de la deuxième, prend forme dans les contrats de la cinquième, qui accompagnent dès lors toute fonction, et dans les invariants de la quatrième et de la sixième. Il s’ensuit que leur réalisation ne peut être attribuée à une seule unité, et qu’il convient de les cultiver tout au long du cours.
Contenus
Le cours s’organise en dix unités : cette présentation, huit unités de contenu et une clôture. Chaque unité de contenu s’ouvre sur une leçon d’introduction, qui présente ses leçons, ce qu’elle tient pour acquis et le problème qu’elle résout, et commence par une évaluation d’entrée ; elle se ferme sur une leçon de synthèse, qui reconstruit la chaîne des concepts de l’unité, sépare ce qui vaut dans tout langage de sa forme en C++, réunit le comportement indéfini de l’unité, reprend dans une évaluation de sortie les compétences de celle d’entrée et propose des défis d’une difficulté supérieure à celle des problèmes des leçons.
Unité 0. Présentation du cours
Elle se compose de cette leçon, qui fixe le but, le choix du langage, les objectifs globaux, les contenus, la charge de travail et la conception du cours.
Unité 1. Premiers pas
Son objectif est de conduire l’étudiant d’un problème énoncé en mots jusqu’à un programme qui compile, s’exécute, lit des données et écrit des résultats, en sachant ce que chaque outil a fait en chemin. Elle commence sans code, parce que l’algorithme précède le programme ; à la fin, l’étudiant sait encore peu de C++, mais il sait comment on travaille avec lui.
- Introduction : premiers pas.
- Du problème à l’algorithme : la spécification des entrées et des sorties, les étapes numérotées, le suivi à la main, la séquence, la décision et la répétition.
- Programmes, compilateurs et C++ : le prétraitement, la compilation et l’édition de liens ; le compilé face à l’interprété ; les normes du langage.
- Installer l’environnement sous Windows, macOS et Linux : les trois guides complets, le compilateur, Visual Studio Code et son extension C++, la vérification et les défaillances fréquentes.
- Compiler, exécuter et configurer : les options du cours, les erreurs et les avertissements, les détecteurs, la tâche de compilation de l’éditeur et l’encodage de la console.
- Anatomie d’un programme : l’inclusion,
main, les instructions, les blocs, les commentaires et le guide de style du cours. - Entrée, sortie et erreurs :
std::cin,std::printlnet les spécifications de format, l’entrée mal formée et la classification des erreurs. - Synthèse et défis : premiers pas.
Unité 2. Données et types
Son objectif est d’enseigner que toute donnée vit en mémoire avec un type qui fixe quelles valeurs elle peut prendre, combien de place elle occupe et quelles opérations elle admet, et qu’un nombre du programme n’est pas un nombre des mathématiques, mais une représentation avec des limites.
- Introduction : données et types.
- Variables et mémoire : la déclaration et l’initialisation par accolades, l’affectation, le suivi de l’état, les noms et la variable non initialisée.
- Les entiers et leur représentation : les bases 2 et 16, la plage de chaque type, le complément à deux et le dépassement de capacité signé et non signé.
- Les nombres à virgule flottante : pourquoi n’est pas exact, l’arrondi accumulé, la comparaison avec une tolérance, l’infini et la valeur non numérique.
- Caractères, booléens, conversions et constantes : le code d’un caractère, les conversions implicites et explicites, le rejet des conversions qui perdent de l’information,
const,constexpretauto. - Synthèse et défis : données et types.
Unité 3. Expressions et opérateurs
Son objectif est d’enseigner à construire des expressions dont la valeur puisse être prédite avec certitude : quelle opération se fait d’abord, de quel type est le résultat, quels effets de bord elle a et quelles expressions sont dépourvues de signification. L’unité n’a encore ni décisions ni boucles : les conditions sont évaluées et affichées.
- Introduction : expressions et opérateurs.
- Arithmétique : le quotient et le reste avec des nombres négatifs, l’arbre d’une expression, la priorité, l’associativité et le type du résultat.
- Relations et logique : les conditions composées, leur négation par De Morgan, l’évaluation en court-circuit et l’erreur de
1 < x < 5. - Affectation, effets de bord et comportement indéfini : la valeur et l’effet d’une expression, les incrémentations, l’ordre d’évaluation et le catalogue de ce que la norme ne définit pas.
- Bits et bibliothèque mathématique : les opérateurs bit à bit, les décalages, les masques,
<cmath>et les constantes de<numbers>. - Synthèse et défis : expressions et opérateurs.
Unité 4. Structures de contrôle
Son objectif est d’enseigner à gouverner l’ordre d’exécution avec des décisions et des boucles, et à raisonner sur leur correction : que chaque cas soit traité, que chaque boucle fasse ce qu’elle doit et qu’elle se termine. Elle réalise ce que la première leçon du cours avait annoncé : la décision et la répétition, désormais écrites et vérifiées.
- Introduction : structures de contrôle.
- Décisions :
ifetelseenchaînés et imbriqués, les cas disjoints et exhaustifs,switchavecenum classet ses pièges. - Boucles :
while,doetfor, la trace d’une boucle, la conversion entre ses formes et les boucles imbriquées. - Concevoir des boucles correctes : les schémas compteur, accumulateur, maximum et sentinelle, l’invariant, la terminaison et un premier contact avec le débogueur.
- Algorithmes numériques : la primalité, l’algorithme d’Euclide, le changement de base, le schéma de Horner et la dichotomie, chacun avec sa justification.
- Synthèse et défis : structures de contrôle.
Unité 5. Fonctions
Son objectif est d’enseigner à décomposer un problème en parties nommées, chacune dotée d’une interface et d’un contrat, et à résoudre des problèmes par récursivité. Elle convertit les longs programmes de l’unité 4 en programmes composés de parties qui se vérifient une à une.
- Introduction : fonctions.
- Fonctions : la déclaration, la définition et l’appel, la pile d’appels, la précondition et la postcondition, et la fonction qui ne renvoie pas de valeur.
- Paramètres et portée : le passage par valeur, par référence et par référence constante, les résultats multiples avec des liaisons structurées, la portée et la durée de stockage des variables.
- Concevoir avec des fonctions : le raffinement successif, l’interface avant le corps, la règle « une fonction, une tâche » et la surcharge.
- Récursivité : le cas de base et le cas récursif, la correction et la terminaison par récurrence, la pile et le coût de la récursivité.
- Synthèse et défis : fonctions.
Unité 6. Données composées
Son objectif est d’enseigner à représenter des collections de données et à les traiter avec les algorithmes de base, écrits à la main ou pris dans la bibliothèque, en estimant combien ils coûtent. Elle mène de la donnée isolée à la collection, et de la question « est-ce correct ? » à la question « combien cela coûte-t-il ? ».
- Introduction : données composées.
- Tableaux de taille fixe :
std::array, le parcours par indices et par intervalle, l’indice dans les limites et les tableaux C en contraste. - Vecteurs :
std::vector, la lecture d’une série de longueur inconnue, l’accumulation, le filtrage et la transformation, la taille et la capacité. - Le coût d’un algorithme : le décompte des opérations dominantes, les ordres de croissance et la mesure du temps.
- Rechercher et trier : la recherche linéaire et la recherche dichotomique, le tri par sélection et le tri par insertion avec leurs invariants, et les algorithmes de la bibliothèque.
- Tableaux à deux dimensions : les tableaux à deux dimensions, leurs parcours par lignes et par colonnes, la transposée, le produit et le tableau dans un vecteur plat.
- Enregistrements :
struct, les initialiseurs désignés, les enregistrements comme paramètres et le tri selon un champ. - Synthèse et défis : données composées.
Unité 7. Texte et fichiers
Son objectif est d’enseigner à manier le texte comme une donnée et à faire qu’un programme lise et écrive des fichiers sans se briser devant une entrée mal formée. À la fin, l’étudiant peut écrire des programmes qui traitent des données réelles, accents et erreurs compris.
- Introduction : texte et fichiers.
- Chaînes de caractères :
std::stringet ses opérations,npos, les vuesstd::string_viewet les chaînes C en contraste. - Le texte n’est pas ASCII : l’encodage UTF-8, les octets et les caractères, et les opérations qui brisent le texte.
- Bien lire :
std::getline, l’état du flux, le rétablissement après une donnée non valide et la validation avecstd::from_chars. - Fichiers texte : ouvrir, lire ligne par ligne, écrire, ajouter, et traiter un fichier de valeurs séparées par des virgules.
- Synthèse et défis : texte et fichiers.
Unité 8. Programmes plus grands
Son objectif est d’enseigner à organiser, vérifier et déboguer des programmes qui ne tiennent plus dans un fichier ni dans la tête. Elle ferme le cercle qu’avait ouvert la première leçon : spécifier, construire, vérifier ; sa synthèse se termine par un projet final qui réunit le cours entier.
- Introduction : programmes plus grands.
- Plusieurs fichiers et bibliothèques personnelles : l’en-tête et l’implémentation, les gardes d’inclusion, la règle de la définition unique et la compilation séparée.
- Tester : les cas de test tirés de la spécification, les tests automatisés,
assertetstatic_assert. - Déboguer : le débogueur de Visual Studio Code, les points d’arrêt, la pile d’appels et une stratégie systématique pour localiser une erreur.
- Synthèse et défis : programmes plus grands.
Unité 9. Clôture du cours
Elle se compose de la leçon Bilan final de Programmation en C++ I, qui rappelle les sept objectifs globaux, montre dans une figure dans quelles unités et quelles leçons chacun est travaillé, réunit par objectif le premier essai des évaluations de sortie et indique où l’étude se poursuit.
Charge de travail et crédits
La charge de travail d’un cours ne se mesure pas aux heures d’exposé, qui n’existent pas dans un cours asynchrone, mais au travail total de l’étudiant : lire, écrire et compiler les exemples, résoudre des problèmes et réviser. L’estimation suit la même méthode dans tous les cours du site : elle compte le temps de lecture du texte de chaque leçon, y compris le code de ses exemples, à cent mots par minute, vingt minutes par problème résolu, trente-cinq par problème proposé, quarante-cinq par défi et douze par évaluation d’entrée ou de sortie, et ajoute une marge d’un dixième pour la révision.
Résultats
Les leçons du cours contiennent au total problèmes résolus, problèmes proposés et défis, ainsi que seize évaluations d’unité et soixante-dix exercices brefs que le site corrige instantanément. La charge estimée par unité, arrondie à l’heure, est la suivante :
- Unité 0, présentation du cours : heure.
- Unité 1, premiers pas : heures.
- Unité 2, données et types : heures.
- Unité 3, expressions et opérateurs : heures.
- Unité 4, structures de contrôle : heures.
- Unité 5, fonctions : heures.
- Unité 6, données composées : heures.
- Unité 7, texte et fichiers : heures.
- Unité 8, programmes plus grands : heures.
- Unité 9, clôture du cours : moins de heure.
Le total est d’environ heures de travail de l’étudiant. Il convient de lire ce chiffre pour ce qu’il est : une estimation pour un étudiant qui écrit et compile tous les exemples et résout tous les problèmes et tous les défis. Qui a déjà programmé dans un autre langage y passera nettement moins de temps dans les premières unités ; qui n’a jamais installé un programme ni lu un message d’erreur peut avoir besoin de davantage, surtout dans l’unité 1, qui est aussi, avec celle des données composées, la plus étendue.
Or ce chiffre est nominal : c’est lui qui fixe les crédits du cours, et il suppose une lecture à rythme constant et trois quarts d’heure par défi. Tant qu’il n’est pas mesuré auprès d’étudiants réels, il convient de l’accompagner d’une fourchette réaliste. Si la lecture avançait à la moitié du rythme supposé, comme c’est le cas quand chaque exemple est tapé à la main, compilé et modifié pour voir ce qui change, et si chaque défi exigeait soixante-quinze minutes au lieu de quarante-cinq, la même méthode donnerait environ heures. Par conséquent, la charge de travail prévisible se situe entre environ et environ heures. La borne supérieure est une estimation prudente, non une mesure ; la fourchette sera corrigée quand on connaîtra le temps qu’y consacrent ceux qui étudient le cours, et d’ici là les crédits sont calculés avec le chiffre nominal.
Rythme suggéré
Pour nommer les rythmes, ce plan suit le même critère que les autres plans du site : le temps plein est celui d’une année universitaire de crédits, environ heures réparties sur une quarantaine de semaines, c’est-à-dire environ heures par semaine, et le mi-temps en est la moitié, environ heures par semaine. Il s’ensuit un rythme recommandé d’un semestre de seize semaines, avec environ heures d’étude par semaine, un peu moins du tiers du temps plein ; avec la fourchette réaliste, de à heures par semaine. Ce semestre peut être partagé avec Logique mathématique I, dont les tables de vérité et l’idée de démonstration se reconnaissent dans les conditions et dans les invariants de ce cours.
Un rythme intensif, d’environ heures par semaine, c’est-à-dire un mi-temps, achève le cours en neuf ou dix semaines. Un rythme plus détendu, d’environ heures par semaine, l’étend sur deux semestres, qui se le partagent presque par moitiés : les unités 0 à 4, de l’algorithme aux structures de contrôle, occupent environ heures, et les unités 5 à 9, des fonctions au projet final, environ . La coupure est naturelle : à la fin de l’unité 4, l’étudiant écrit déjà des programmes complets dans un seul main, et la seconde moitié lui apprend à les organiser.
Crédits
Les crédits de ce site mesurent la charge de travail totale qu’un cours exige de qui l’étudie (la lecture, les problèmes, les défis et la révision), et non les heures de cours. Un crédit équivaut à environ heures de travail, et une année à temps plein, à crédits, c’est-à-dire à environ heures ; c’est le même critère que suit la sous-section précédente pour nommer les rythmes d’étude.
Or, en divisant les heures estimées par , on obtient 6,8, qui s’arrondit à . Si l’on admet pour le crédit une fourchette de à heures de travail, le nombre de crédits du cours est compris entre 6,3 et 7,5 :
Par conséquent, le cours compte crédits, un peu plus du neuvième d’une année à temps plein. Le chiffre n’est pas une négligence : le cours dure ce qu’il doit durer pour enseigner les fondements, et il n’a pas été raccourci pour s’ajuster à un nombre rond de crédits.
Leur utilité est pratique : ils permettent de comparer entre eux la charge de travail des cours du site et de décider combien il convient d’en étudier à la fois sans dépasser le temps disponible. Qui dispose, par exemple, d’un mi-temps pendant un semestre de seize semaines, c’est-à-dire d’environ heures, peut aborder des cours totalisant environ crédits ; à temps plein pendant le même semestre, environ .
Ces crédits sont une mesure propre au site pour planifier l’étude : ils ne correspondent à aucun système officiel de crédits et n’impliquent la reconnaissance académique d’aucun établissement.
Conception du cours
Modalité
Le cours est asynchrone, en ligne et à rythme libre : il n’a ni dates, ni séances en direct, ni échéances, et chaque étudiant avance à la vitesse que lui permettent sa préparation et son temps. Il sera proposé dans les quatorze langues du site. Les identificateurs des programmes sont en anglais et sont les mêmes dans toutes les versions ; les commentaires et les messages qu’affichent les programmes, en revanche, sont dans la langue de la page, de sorte que chaque version se compile et s’exécute avec sa propre sortie. Les leçons indiquent quelles autres leçons il convient d’avoir étudiées auparavant, de sorte que l’ordre recommandé est explicite.
La structure d’une leçon
Toutes les leçons de contenu suivent la même structure. Elles commencent par un résumé et par une liste d’objectifs d’apprentissage propres, formulés comme des performances observables. Vient ensuite l’exposé, dont le noyau est formé par les programmes. Chaque programme complet du cours a été compilé, avant la construction du site, avec la norme C++23, avec tous les avertissements activés et avec les détecteurs d’erreurs de mémoire et de comportement indéfini, puis exécuté ; ce qui est publié sous le code est sa sortie réelle, ou les messages réels du compilateur quand ce qu’on enseigne est une erreur ou un avertissement, ou le rapport réel du détecteur quand le programme commet un comportement indéfini. Si cela est ainsi, aucune sortie du cours n’est écrite à la main : le site vérifie, lors de sa construction, que chaque programme a enregistré la sortie de sa version actuelle, et il ne serait pas publié s’il en manquait une. D’autres figures animent pas à pas ce que le texte raisonne : les cases de la mémoire avec leur nom et leur valeur, la pile d’appels, le parcours d’une boucle, les bits d’un nombre.
Chaque leçon se clôt par trois problèmes résolus, avec leur solution pas à pas, et par trois problèmes proposés, chacun avec une indication repliée et, ensuite, la réponse, parfois accompagnée d’une solution développée qu’il convient d’ouvrir seulement après avoir écrit la sienne. Les problèmes exigent une idée, jamais la répétition mécanique d’un exemple, et n’utilisent que ce qui a été enseigné jusqu’à cette leçon. Chaque leçon comporte en outre deux exercices brefs que le site corrige instantanément : dans l’un, on prédit la sortie exacte d’un programme, et dans l’autre, on complète ou on corrige un fragment. Il convient de savoir ce que fait et ce que ne fait pas cette correction : le site ne compile pas le code de l’étudiant, mais vérifie par des motifs que l’essentiel est présent et que l’erreur visée a disparu. Par conséquent, un exercice réussi sur le site doit encore être compilé et exécuté sur son propre ordinateur, qui est le seul juge définitif.
Les leçons d’introduction et de synthèse ont une structure propre. L’introduction présente l’unité, ses leçons et le fil qui les unit, ce qu’elle tient pour acquis et le problème qu’elle résout, et contient l’évaluation d’entrée. La synthèse reconstruit la chaîne des concepts de l’unité, avec la leçon où s’établit chaque maillon et ce que chacun utilise ; elle contient deux sections fixes, « Ce qui vaut dans tout langage » et « Le comportement indéfini de l’unité », l’évaluation de sortie et six défis (dix dans la dernière unité, dont le projet final se décompose en étapes), chacun avec une indication, une réponse et, quand le raisonnement le mérite, une solution développée repliée.
Le lecteur vocal
Chaque leçon peut s’écouter à voix haute avec le lecteur du site, qui utilise les voix de l’appareil lui-même : le texte est lu tel qu’il est écrit, le code cité dans le texte est prononcé de façon sensée et les blocs de code sont annoncés, sans être lus caractère par caractère. Cela permet de réviser l’exposé loin de l’écran ; les programmes, en revanche, demandent toujours l’éditeur.
Évaluation
Le cours ne note pas : son évaluation est formative et s’appuie sur l’autoévaluation. Les problèmes proposés permettent de vérifier l’atteinte des objectifs de chaque leçon, et les défis, celle des objectifs de chaque unité ; dans tous les cas, le juge ultime est le compilateur, avec ses avertissements et ses détecteurs, parce qu’un programme qui compile sans avertissement, passe ses cas de test et ne réveille aucun détecteur est une preuve qui n’admet pas de discussion, bien que, comme l’enseigne la leçon sur les tests, ce ne soit pas une démonstration.
À cela s’ajoutent les évaluations d’unité. Celle d’entrée, dans l’introduction, est une autoévaluation brève qui mesure le point de départ ; celle de sortie, dans la synthèse et avant les défis, mesure les acquis et se compare à celle d’entrée. Toutes deux se corrigent dans le navigateur lui-même : chaque réponse reçoit une explication, chaque erreur typique une remarque qui l’identifie, et chaque question renvoie à la leçon qu’il convient de réviser. Aucune d’elles ne note ni ne certifie, et leurs résultats ne sont enregistrés que dans le navigateur de l’étudiant ; la validation des compétences acquises se fera plus tard sur une autre plateforme. Le bilan final réunit le premier essai de chaque évaluation de sortie en un profil par objectif global.
Les assistants d’intelligence artificielle
Il existe aujourd’hui des assistants d’intelligence artificielle capables d’écrire en quelques secondes la solution de presque n’importe quel problème de ce cours. Il convient de dire clairement quelle place leur revient, et la raison est la même que celle qui justifie tout le reste : le cours n’entraîne pas à la production de programmes, mais à une capacité, celle de penser un problème jusqu’à le convertir en un programme correct. Si cela est ainsi, déléguer l’exercice à un assistant annule l’exercice, de même que regarder la réponse d’un problème avant de l’essayer le convertit en lecture. Le programme obtenu peut être correct ; qui le remet, en revanche, n’a pas appris à l’écrire, et ne saura pas le faire quand l’assistant se trompera, c’est-à-dire précisément quand on en a le plus besoin.
Il s’ensuit une règle d’usage, et non une interdiction. Un assistant sert à expliquer un message d’erreur qu’on ne comprend pas, à proposer des cas de test auxquels on n’avait pas pensé, à comparer une solution personnelle déjà terminée avec une autre, ou à éclairer une idée de l’exposé sous un autre angle. Il ne sert pas à écrire les exercices, les problèmes ni les défis. Et dans tous les cas vaut un principe qui s’applique à tout code d’autrui, qu’il vienne d’un assistant, d’un forum ou d’un camarade : on le lit, on le teste et on le comprend avant de l’accepter. Un assistant peut produire du code qui compile et semble correct, mais qui commet un comportement indéfini ou utilise une forme ancienne du langage ; l’étudiant qui aura fait le cours saura le reconnaître, et celui qui aura délégué le cours, non.
Comment étudier ce cours
Le cours est conçu pour être étudié dans l’ordre. Chaque unité suppose les précédentes : les expressions s’écrivent avec les types de la deuxième unité, les boucles avec les expressions de la troisième, les fonctions encapsulent les boucles de la quatrième, et les données composées se traitent avec des fonctions ; sauter une leçon oblige, tôt ou tard, à y revenir.
La règle principale est unique : écrire et compiler chaque exemple. La programmation ne s’apprend pas en lisant, mais en écrivant, en compilant et en lisant ce que répond le compilateur. C’est pourquoi l’étudiant installe son propre environnement en suivant la leçon Installer l’environnement sous Windows, macOS et Linux, et reproduit chaque programme dans son Visual Studio Code, avec les options que la leçon Compiler, exécuter et configurer apprend à fixer ; qui utilise un ordinateur sur lequel il ne peut pas installer de programmes dispose, à titre provisoire, d’un compilateur en ligne, que décrit la leçon d’installation. Il convient de taper les exemples à la main au lieu de les copier, parce que la main retient ce que l’œil laisse passer ; il convient de prédire la sortie de chaque programme avant de l’exécuter, parce que la différence entre le prédit et l’obtenu est exactement ce qui reste à apprendre ; et il convient de lire chaque avertissement du compilateur comme une question qui mérite une réponse, et non comme un bruit qu’on tolère. Une fois qu’un programme fonctionne, il convient en outre de le modifier exprès (effacer un point-virgule, changer un type, sortir d’un vecteur) et de lire ce que disent le compilateur ou le détecteur, parce que chaque erreur provoquée est une erreur qu’on reconnaîtra ensuite.
Chaque unité comporte deux points de contrôle. L’évaluation d’entrée, dans l’introduction, est le point de départ : il convient d’y répondre avant d’étudier l’unité, sans rien consulter, et de noter les leçons auxquelles renvoient les erreurs. L’évaluation de sortie, dans la synthèse et avant les défis, est le point de comparaison : on y répond à la fin des leçons, également sans rien consulter, et on la compare à celle d’entrée. Dans les deux, c’est le premier essai qui compte, celui qu’on compare et celui que réunit le profil du bilan final. Dans les problèmes proposés et dans les défis, il convient d’ouvrir l’indication seulement quand l’essai est épuisé, et la réponse seulement à la fin, pour vérifier. Un problème dont on a consulté la réponse avant de compiler la sienne n’a pas été résolu, mais lu.
Gestion de versions
Enfin, il convient de conserver le travail du cours dans un système de gestion de versions dès le premier programme. Git enregistre chaque état d’un dossier de travail, permet de revenir à n’importe lequel d’entre eux et montre ce qui a changé entre deux ; pour qui programme, cela signifie pouvoir expérimenter sans craindre de perdre ce qui fonctionnait, et localiser, en comparant des versions, le changement qui a introduit une erreur, ce qui porte dans l’histoire du programme la dichotomie qu’enseigne la leçon sur le débogage. Le cours n’enseigne pas Git, parce qu’un autre cours du site le fait déjà : la leçon Écrire en équipe, du cours LaTeX pour la rédaction académique, explique le flux de travail minimal, quels fichiers on conserve et lesquels on exclut, et comment on résout un conflit. Ce qui y vaut pour les sources d’un document vaut tout autant pour celles d’un programme ; il suffit d’exclure, au lieu des fichiers auxiliaires de LaTeX, les exécutables et les fichiers objets que la compilation régénère.