Chap 3 – Les pointeurs
Document réservé
Vous consultez actuellement la présentation publique de ce chapitre.
Les documents PDF complets, comprenant les développements théoriques, les exemples détaillés et les exercices, sont disponibles sur demande.
Pour obtenir un accès, contactez-moi via la page Contact en indiquant les domaines qui vous intéressent (C++, ESP-IDF, électronique, etc.).
Annexe B – Les structures fondamentales du langage C++
ANNEXE B – Les structures fondamentales du langage C++
Un bloc est une suite d’instructions comprise entre deux accolades :
Programme 1 – Bloc
Exemple 1 : La portée
Programme 2 – La portée d’un bloc
std::cout << « a = » << a << ’\n’;
std::cout << « b = » << b << ’\n’;
std::cout << « a = » << a << ’\n’;
ne compile pas si on ajoute la ligne
std::cout << « b = » << b << ’\n’; en dessous de la ligne
std::cout << « a = » << a << ’\n’;.
La variable b n’existe qu’entre les accolades où elle a été définie. Lorsque l’exécution quitte ce bloc, la variable est
automatiquement détruite.
Les blocs permettent de regrouper plusieurs instructions. Ils jouent également un rôle fondamental dans la gestion
de la portée des variables et de la durée de vie des objets.
Exemple 2 : Le masquage
Programme 3 – Masquage
std::cout << a << ’\n’;
std::cout << a << ’\n’;
AccueilAnnexe B – Les structures fondamentales du langage C++ La seconde variable ne remplace pas la première. Elle la masque simplement à l’intérieur du bloc. Exemple 3 : La durée de vie Programme 4 – Durée de vie À la fermeture de l’accolade : 1. la mémoire de a est libérée ; 2. l’objet cesse d’exister. En C++, les accolades ne servent pas uniquement à regrouper des instructions. Elles définissent un bloc dans lequel peuvent être déclarées des variables. La portée de ces variables est limitée à ce bloc, et elles sont automatiquement détruites lorsque l’exécution en sort. Cette notion est fondamentale et sera réutilisée tout au long du langage. uniquement à améliorer la présentation du code. Elles définissent la portée des variables et la durée de vie des objets. Programme 5 – Piège 1 ne compile pas car une déclaration n’est pas une instruction simple. Il faut écrire : Programme 6 – Manière juste d’écrire 2. Ce code ne compile pas non plus : Programme 7 – Piège 2 for (int i = 0; i < 10; ++i) car int i est déclaré dans le bloc du for. En C++, la destruction automatique des objets à la sortie d’un bloc est un mécanisme fondamental. C’est lui qui permettra plus tard de comprendre le fonctionnement du RAII, des flux de fichiers (std::ifstream, std::ofstream ), des verrous (std::lock_guard) et des pointeurs intelligents (std::unique_ptr).
AccueilAnnexe B – Les structures fondamentales du langage C++ ✓ Les accolades définissent un bloc. ✓ Un bloc délimite la portée des variables. ✓ Une variable n’existe que dans le bloc où elle est déclarée. ✓ À la fin d’un bloc, les objets locaux sont automatiquement détruits. ✓ Une variable peut masquer une autre variable de même nom dans un bloc intérieur. Les premiers langages de programmation, tels que le FORTRAN, ne possédaient pas de véritables blocs d’instructions. Plus tard, des langages comme Pascal ont introduit les mots-clés begin et end pour délimiter les blocs. Le langage C, puis le C++, ont remplacé ces mots-clés par les accolades { et }, plus compactes et plus faciles à lire lorsque les blocs sont fortement imbriqués. Les structures conditionnelles permettent de sélectionner les instructions qui seront exécutées en fonction de la valeur En C++, la condition d’un test est convertie en une valeur de type bool. Une expression n’a donc pas nécessairement besoin d’être explicitement égale à true ou à false. La forme générale d’un test est la suivante : // Instructions executees si la condition est vraie La condition est évaluée une seule fois. Si elle vaut true, le bloc est exécuté. Dans le cas contraire, il est ignoré. Programme 8 – Test simple int temperature = 18; if (temperature < 20) std::cout << « La temperature est inferieure a 20 degres.\n »; La temperature est inferieure a 20 degres. int temperature = 18; std::cout << « La temperature est inferieure a 20 degres.\n »; donne également la sortie La temperature est inferieure a 20 degres.
AccueilAnnexe B – Les structures fondamentales du langage C++ if (0.000000000000001) std::cout << « Vrai\n »; std::cout << « Faux\n »; std::cout << « Vrai\n »; std::cout << « Faux\n »; En C++, lorsqu’une valeur numérique est utilisée comme condition, elle est convertie implicitement en bool selon une règle très simple : — 0 ou 0.0 donne false — toute autre valeur donne true Lorsqu’une valeur numérique est utilisée comme condition, seule la valeur exactement égale à zéro est convertie en false. Toute autre valeur, même extrêmement petite, est convertie en true. Ainsi, les deux tests suivants donnent des résultats différents : Lorsqu’une condition est une constante connue à la compilation (par exemple if (0.0) ou if (1)), le compilateur ou l’environnement de développement peut détecter qu’une branche ne sera jamais exécutée et émettre un avertissement. Il ne s’agit pas d’une erreur de syntaxe, mais d’une optimisation et d’une aide à la détection d’erreurs logiques.
AccueilAnnexe B – Les structures fondamentales du langage C++ Les premières versions du langage C ne possédaient pas de type booléen. Les tests conditionnels utilisaient directement des valeurs entières : la valeur zéro représentait une condition fausse, tandis que toute valeur non nulle représentait une condition vraie. Le C++ a introduit le type bool, mais a conservé cette conversion implicite afin de rester compatible avec l’immense quantité de code C déjà existante. Les branches else if et else Plusieurs cas peuvent être distingués grâce à une chaîne de tests : std::cout << « Tres bien\n »; else if (note >= 4.0) std::cout << « Suffisant\n »; std::cout << « Insuffisant\n »; Les conditions sont évaluées dans l’ordre. Dès qu’une condition est vraie, le bloc correspondant est exécuté et les branches suivantes sont ignorées. Conversion implicite vers bool Une condition n’est pas obligatoirement le résultat d’une comparaison explicite. De nombreuses valeurs peuvent être converties en bool. Pour les types entiers : — la valeur zéro est convertie en false ; — toute valeur non nulle est convertie en true (voir ci-dessus avec l’exemple if (330) …. std::cout << « n est non nul\n »; std::cout << ∗p << ’\n’; ne donne pas de sortie.
AccueilAnnexe B – Les structures fondamentales du langage C++ Dans un test, l’expression if (p) est équivalente à if (p != nullptr). La seconde écriture est plus explicite, tandis que la première est très courante dans le code C++. Affectation ou comparaison ? L’opérateur = réalise une affectation, tandis que l’opérateur == réalise une comparaison. Ces deux instructions ont des significations très différentes : // Affectation, puis conversion en bool, attention si b=0 alors if (a = b) La seconde écriture est syntaxiquement valide. La valeur de b est affectée à a, puis la nouvelle valeur de a est La confusion entre = et == est une erreur classique. Lorsque l’affectation dans une condition est réellement volontaire, il est préférable de la rendre explicite au moyen de parenthèses supplémentaires. Évaluation paresseuse des opérateurs logiques Les opérateurs logiques && et || utilisent une évaluation dite paresseuse ou en court-circuit. condition1 && condition2 la seconde condition n’est évaluée que si la première est vraie. Dans l’expression condition1 || condition2 la seconde condition n’est évaluée que si la première est fausse. if (p != nullptr && ∗p > 0) std::cout << « Valeur positive\n »; La première condition est fausse. L’expression ∗p > 0 n’est donc jamais évaluée, ce qui évite de déréférencer un
AccueilAnnexe B – Les structures fondamentales du langage C++ ✓ Avec &&, l’évaluation s’arrête dès qu’une condition est fausse. ✓ Avec ||, l’évaluation s’arrête dès qu’une condition est vraie. ✓ L’ordre des conditions peut donc avoir une importance fondamentale. Le langage autorise l’omission des accolades lorsqu’un seul bloc d’instruction suit le test : std::cout << « Positif\n »; Cette écriture est correcte, mais elle devient rapidement dangereuse lorsque le programme est modifié. std::cout << « Positif\n »; Seule la première instruction dépend du test. L’incrémentation de compteur est toujours exécutée, malgré l’indentation trompeuse. Dans cet ouvrage, les accolades seront donc utilisées systématiquement. Le problème du else ambigu Lorsqu’un test est imbriqué sans accolades, un else est toujours associé au if précédent le plus proche qui ne possède pas encore de branche else. std::cout << « a et b sont positifs\n »; std::cout << « b n’est pas positif\n »; Le else est associé au second if, et non au premier. L’utilisation systématique des accolades élimine cette ambiguïté. L’instruction switch Lorsque plusieurs choix dépendent de la valeur entière d’une même expression, l’instruction switch peut être plus lisible qu’une succession de if et de else if. std::cout << « Premier choix\n »; std::cout << « Deuxieme choix\n »; std::cout << « Troisieme choix\n »; std::cout << « Choix inconnu\n »;
AccueilAnnexe B – Les structures fondamentales du langage C++ Sans l’instruction break, l’exécution continue dans les branches suivantes. Ce comportement est appelé fallthrough. std::cout << « Acces niveau 3\n »; // poursuite volontaire std::cout << « Acces niveau 2\n »; // poursuite volontaire std::cout << « Acces niveau 1\n »; std::cout << « Aucun acces\n »; L’absence involontaire de break dans une branche case est une source classique d’erreurs. En C++ 11, lorsqu’une poursuite vers la branche suivante est volontaire, elle doit être clairement signalée par L’expression contrôlée par switch doit être de type entier, énuméré ou convertible vers un type entier. On ne peut pas utiliser directement un double ou une chaîne de caractères comme expression de contrôle. ✓ Une condition est convertie en une valeur de type bool. ✓ Zéro correspond à false ; toute valeur entière non nulle correspond à true. ✓ Un pointeur nul est converti en false. ✓ = réalise une affectation, tandis que == réalise une comparaison. ✓ Les opérateurs && et || utilisent une évaluation en court-circuit. ✓ Les accolades doivent être utilisées systématiquement, même pour une seule instruction. ✓ Dans un switch, l’instruction break empêche généralement l’exécution de la branche suivante. Les boucles permettent de répéter l’exécution d’un même bloc d’instructions tant qu’une condition est satisfaite. Le langage C++ propose trois structures de répétition : — la boucle while ; — la boucle do…while ; Bien que leur syntaxe soit différente, ces trois structures permettent de réaliser les mêmes traitements. Le choix de l’une ou de l’autre dépend essentiellement de la lisibilité du programme.
AccueilAnnexe B – Les structures fondamentales du langage C++ La boucle while exécute un bloc d’instructions tant que sa condition reste vraie. La condition est évaluée avant chaque itération. Si elle est fausse dès le départ, le bloc n’est jamais exécuté. Programme 9 – Compteur de 0 à 4 std::cout << i << ’\n’; Program ended with exit code: 0 La boucle do…while Contrairement à la boucle while, la boucle do…while execute toujours son bloc au moins une fois. std::cout << i << ’\n’; La sortie est naturellement Program ended with exit code: 0 Même si la condition est fausse, le nombre 5 est affiché une fois. La boucle for est particulièrement adaptée lorsque le nombre d’itérations est connu ou lorsqu’un compteur est utilisé. Sa syntaxe générale est :
AccueilAnnexe B – Les structures fondamentales du langage C++ for (initialisation ; condition ; mise_a_jour) Les trois expressions placées entre les parenthèses jouent des rôles différents : — l’initialisation est exécutée une seule fois ; — la condition est évaluée avant chaque itération ; — la mise à jour est exécutée à la fin de chaque tour de boucle. Programme 10 – Affichage des dix premiers entiers for (int i = 0 ; i < 10 ; ++i) std::cout << i << ’\n’; Program ended with exit code: 0 Cette boucle est strictement équivalente à : std::cout << i << ’\n’; dont la sortie est identique, à savoir Program ended with exit code: 0 La boucle for constitue donc essentiellement une écriture plus compacte du même algorithme.
AccueilAnnexe B – Les structures fondamentales du langage C++ Pré-incrémentation et post-incrémentation Deux opérateurs permettent d’incrémenter une variable : Ils produisent le même effet sur la variable, mais ne retournent pas la même valeur. Après l’exécution : i vaut 6 et a vaut également 6. Après l’exécution : i vaut 6 tandis que a vaut 5. Dans une boucle for, la valeur retournée par l’opérateur n’est généralement pas utilisée. Les deux écritures suivantes sont donc équivalentes : for (int i = 0 ; i < 100 ; ++i) for (int i = 0 ; i < 100 ; i++) Remarque 2.1. Avec certaines classes, notamment les itérateurs de la bibliothèque standard, la pré-incrémentation (++it) peut être légèrement plus efficace que la post-incrémentation (it++), car elle évite la création d’une copie temporaire de l’objet. C’est pourquoi la pré-incrémentation est souvent privilégiée dans le code C++ moderne. Pour un type fondamental, on peut considérer que ++i est équivalent à incrémenter d’abord la variable puis à utiliser sa nouvelle valeur. De même, i++ utilise d’abord la valeur actuelle puis effectue l’incrémentation. i = i + 1; puis utiliser la nouvelle valeur de i. Utiliser la valeur actuelle de i, puis exécuter i = i + 1;. Table 1 – Différence entre pré-incrémentation et post-incrémentation. Remarque 2.2. Ce tableau décrit le comportement conceptuel des deux opérateurs. Le compilateur est libre d’optimiser les instructions générées, mais le résultat observable reste toujours celui décrit ci-dessus. Les boucles infinies Une boucle dont la condition reste toujours vraie ne se termine jamais.
AccueilAnnexe B – Les structures fondamentales du langage C++ Les boucles infinies sont utilisées dans certains domaines, notamment pour les serveurs ou les systèmes embarqués, où un programme doit rester actif tant que la machine fonctionne. L’instruction break L’instruction break interrompt immédiatement la boucle la plus interne. L’instruction break L’instruction break interrompt immédiatement la boucle la plus interne. L’instruction continue L’instruction continue interrompt uniquement l’itération en cours. L’exécution reprend directement au début de l’itération suivante. for (int i = 0 ; i < 10 ; ++i) std::cout << i << ’\n’; Le nombre 5 n’est pas affiché. L’instruction goto L’instruction goto permet de transférer directement l’exécution vers une étiquette située ailleurs dans la même fonction. Bien que cette instruction fasse toujours partie du langage C++, son utilisation est aujourd’hui très rare. Dans la plupart des situations, les structures de contrôle (if, for, while, break, continue et return) permettent d’obtenir un code plus lisible et plus facile à maintenir.
AccueilAnnexe B – Les structures fondamentales du langage C++ std::cout << « Le nombre est positif.\n »; std::cout << « Erreur : le nombre est negatif.\n »; Bien que l’instruction goto fasse toujours partie du langage C++, elle est aujourd’hui très rarement utilisée. Dans la quasi-totalité des cas, les structures de contrôle (if, for, while, break, continue et return) permettent d’écrire un code plus clair et plus facile à maintenir. ✓ Les boucles while, do…while et for permettent de répéter un traitement. ✓ La boucle while peut ne jamais être exécutée. ✓ La boucle do…while est exécutée au moins une fois. ✓ La boucle for regroupe l’initialisation, le test et la mise à jour. ✓ Les opérateurs ++i et i++ ne retournent pas la même valeur. ✓ Dans une boucle for, la pré-incrémentation est généralement préférée. ✓ Les instructions break et continue modifient le déroulement normal d’une boucle. ✓ Le goto n’est pas interdit. Si vous ressentez le besoin de l’utiliser, demandez-vous d’abord si votre algorithme ou la structure de votre programme ne pourrait pas être simplifié. Les instructions de contrôle Les structures de contrôle étudiées jusqu’à présent (if, switch, while, do…while et for) déterminent le déroulement normal d’un programme. Le langage C++ fournit également plusieurs instructions permettant de modifier ce déroulement en interrompant une boucle, en quittant une fonction ou en effectuant un saut vers une autre partie du programme. L’instruction break L’instruction break provoque l’arrêt immédiat de la boucle ou du switch le plus interne. L’exécution reprend alors avec la première instruction située après cette structure.
AccueilAnnexe B – Les structures fondamentales du langage C++ Programme 11 – Utilisation de break for (int i = 0; i < 10; ++i) std::cout << i << ’\n’; qui donne comme sortie Program ended with exit code: 0 Remarque 2.3. L’instruction break ne quitte que la structure de contrôle la plus interne. Lorsqu’une boucle est imbriquée dans une autre, seule la boucle courante est interrompue. L’instruction continue L’instruction continue interrompt uniquement l’itération en cours. L’exécution reprend immédiatement à l’itération suivante de la boucle. Programme 12 – Utilisation de continue for (int i = 0; i < 10; ++i) if (i == 5) continue; std::cout << i << ’\n’; Program ended with exit code: 0 Le nombre 5 n’est jamais affiché. Remarque 2.4. Dans une boucle for, l’expression de mise à jour est tout de même exécutée après un continue. Dans une boucle while, il appartient au programmeur de veiller à ce que la variable de contrôle soit correctement mise à jour afin d’éviter une boucle infinie.
AccueilAnnexe B – Les structures fondamentales du langage C++ L’instruction return L’instruction return termine immédiatement l’exécution de la fonction courante 1 . Lorsqu’une valeur est attendue, elle est renvoyée à la fonction appelante. std::cout << « Bonjour\n »; std::cout << « Ce texte ne sera jamais affiche.\n »; Remarque 2.5. L’instruction return met immédiatement fin à l’exécution de la fonction. Toutes les instructions situées après un return ne seront donc jamais exécutées. L’instruction goto L’instruction goto permet d’effectuer un saut direct vers une étiquette située ailleurs dans la même fonction. Programme 13 – Exemple d’utilisation de goto std::cout << « Nombre positif\n »; std::cout << « Erreur\n »; Les premiers langages de programmation, notamment FORTRAN, utilisaient très largement l’instruction goto. Cette pratique conduisait souvent à un code difficile à comprendre, surnommé spaghetti code. À partir des années 1970, la programmation structurée a progressivement remplacé cette approche par des structures comme if, while et for. Aujourd’hui, l’utilisation de goto reste exceptionnelle. 1. Pour les fonctions voyez le paragraphe suivant.
AccueilAnnexe B – Les structures fondamentales du langage C++ ✓ break interrompt immédiatement la boucle ou le switch le plus interne. ✓ continue interrompt uniquement l’itération courante d’une boucle. ✓ return termine immédiatement l’exécution de la fonction courante. ✓ goto effectue un saut direct vers une étiquette située dans la même fonction. ✓ Les instructions de contrôle doivent être utilisées avec modération afin de préserver la lisibilité des ✓ L’instruction goto provoque un saut vers une étiquette (ou label) située ailleurs dans la même fonction. Les commentaires permettent d’ajouter des explications dans un programme sans influencer son exécution. Ils sont ignorés par le compilateur et servent uniquement à améliorer la compréhension du code par le programmeur. Le C++ propose deux types de commentaires. Commentaires sur une ligne Un commentaire commençant par // se poursuit jusqu’à la fin de la ligne. // Age de l’etudiant double pi = 3.14159; // Valeur approchee de Ce type de commentaire est particulièrement adapté pour de courtes explications. Commentaires sur plusieurs lignes Lorsqu’une explication s’étend sur plusieurs lignes, on peut utiliser les délimiteurs /∗ et ∗/. Calcul de la moyenne des notes. Les notes sont supposees comprises Ces commentaires peuvent s’étendre sur plusieurs lignes, mais ils ne peuvent pas être imbriqués. Les commentaires de type /∗ … ∗/ ne peuvent pas être placés à l’intérieur d’un autre commentaire du Pourquoi commenter un programme ? Le but d’un commentaire n’est pas de décrire chaque instruction, mais d’expliquer ce que le code ne peut pas exprimer Par exemple, le commentaire suivant est inutile : En revanche, le commentaire ci-dessous apporte une information précieuse : // Recherche du premier element negatif du tableau. for (int i = 0; i < n; ++i)
AccueilAnnexe B – Les structures fondamentales du langage C++ Le lecteur comprend immédiatement l’objectif de l’algorithme avant même d’en examiner les détails. Avant d’ajouter un commentaire, demandez-vous si un nom de variable ou de fonction plus explicite ne permettrait pas de s’en passer. double vitesseMoyenne; est beaucoup plus explicite que Un bon choix de noms améliore la lisibilité du programme et réduit le besoin de commentaires. Dans les premiers langages de programmation, les programmes étaient souvent peu structurés et les noms de variables très courts. Les commentaires étaient alors indispensables pour comprendre le fonctionnement du Aujourd’hui, les langages modernes permettent d’utiliser des identificateurs explicites et de découper un programme en fonctions courtes. Les commentaires restent importants, mais ils servent surtout à expliquer les choix de conception, les hypothèses ou les algorithmes utilisés. ✓ Les commentaires sont ignorés par le compilateur. ✓ // permet d’écrire un commentaire jusqu’à la fin de la ligne. ✓ /∗ … ∗/ permet d’écrire un commentaire sur plusieurs lignes. ✓ Un bon commentaire explique une intention ou un choix, et non une instruction évidente. ✓ Un code clair et des noms de variables explicites nécessitent généralement peu de commentaires. Les opérateurs permettent d’effectuer des calculs, de comparer des valeurs ou de modifier le contenu des variables. Certains sont hérités directement des mathématiques, tandis que d’autres sont propres au langage C++. Cette section présente les opérateurs les plus couramment utilisés. Les opérateurs plus spécialisés (opérateurs binaires, surcharge d’opérateurs, etc.) seront étudiés dans les chapitres correspondants. Les opérateurs arithmétiques Reste de la division entière
AccueilAnnexe B – Les structures fondamentales du langage C++ cout << a / b << endl; cout << 7.0 / 2 << endl; Lorsque les deux opérandes sont des entiers, la division est entière. La partie décimale est supprimée. Les opérateurs d’affectation // x vaut maintenant 17 // x vaut maintenant 34 Les opérateurs d’incrémentation Les opérateurs d’incrémentation et de décrémentation permettent d’ajouter ou de retrancher une unité. Remarque 2.6. La différence entre ++i et i++ sera étudiée plus loin dans le livre. Les opérateurs de comparaison Les opérateurs de comparaison renvoient une valeur booléenne (true ou false). Plus petit ou égal à Plus grand ou égal à
AccueilAnnexe B – Les structures fondamentales du langage C++ qui réalise une affectation, avec qui effectue une comparaison. Les opérateurs logiques Les opérateurs logiques permettent de combiner plusieurs conditions. if (age >= 18 && permis) Priorité des opérateurs Comme en mathématiques, certains opérateurs sont évalués avant d’autres. Dans les cas les plus courants : 2. Multiplication, division, modulo 3. Addition, soustraction 5. Opérateurs logiques En cas de doute, ajoutez des parenthèses. Elles améliorent souvent la lisibilité du programme. Les opérateurs binaires Le C++ possède également des opérateurs agissant directement sur les bits. Ces opérateurs sont très utilisés en programmation système et dans le développement pour microcontrôleurs (ESP32, STM32, Arduino, etc.).
AccueilAnnexe B – Les structures fondamentales du langage C++ Ils seront étudiés en détail dans un chapitre spécifique.
Accueilcpp-An2- Annexe B – Les structures fondamentales du langage C++
Accueil