Programmation C++/Version imprimable
Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Programmation_C%2B%2B
Avant propos
Ce livre est destiné à présenter les différents aspects du langage de programmation C++. L'objectif est d'en faire un livre référence sur les différents aspects de ce langage, en étant le plus exhaustif possible. Nous essayerons de proposer pour chaque concept un voire plusieurs exemples illustratifs complets.
Introduction
Quelques repères historiques
modifierÀ l'origine, un ordinateur ne comprenait que le langage binaire : un programme était constitué d'une suite de 0 et de 1. Le programmeur devait lui-même traduire son programme sous cette forme. En 1950, Alan Turing et Maurice V. Wilkes de l'université de Cambridge branchent un clavier à un ordinateur, il est dorénavant possible de rentrer des mots associés au langage machine. Par exemple : mov, load, sub… Ces mots ne sont pas compris par l’ordinateur qui ne comprend que le langage binaire. Il faut alors un « assembleur » qui transforme le code en langage binaire.
La nécessité de créer un langage de haut niveau se fait sentir, le FORTRAN (FORmula TRANslator, traducteur de formules) est créé en 1957. Il est utilisé pour des programmes mathématiques et scientifiques.
En 1970, Ken Thompson, créa un nouveau langage : Le B, descendant du BCPL (Basic Combined Programming Language, créé en 1967 par Martin Richards). Son but était de créer un langage simple, malheureusement, son langage fut trop simple et trop dépendant de l’architecture utilisée…
En 1971 Dennis Ritchie commence à mettre au point le successeur du B, le C. Le résultat est convaincant : Le C est totalement portable (il peut fonctionner sur tous les types de machines et de systèmes), il est de bas niveau (il peut créer du code aussi rapide que de l’assembleur) et il permet de traiter des problèmes de haut niveau. Le C permet de quasiment tout faire, du driver au jeu.
Le C devient très vite populaire, tout le monde veut créer sa version. Beaucoup de compilateurs qui voient le jour sont incompatibles entre eux et la portabilité est perdue. Il est décidé qu’il faut un standard pour le C. L'ANSI (American National Standards Institute) s’en charge en 1983. La plupart des compilateurs se conforment à ce standard. Un programme écrit en C ANSI est compatible avec tous les compilateurs.
Il manque la programmation orientée objet au C. C’est pourquoi Bjarne Stroustrup, des laboratoires Bell, crée le C++, dont le développement a commencé au début de années 1980. Il construit donc le C++ sur la base du C. C++ est capable de compiler un programme C, et garde donc une forte compatibilité avec le C.
Les langages C et C++ sont les langages les plus utilisés dans le monde de la programmation.
Présentation du C++
modifierLe C++ est un langage multiparadigme. Il supporte essentiellement les paradigmes suivants :
- programmation procédurale : il reprend essentiellement les concepts du langage C, notamment la notion de fonction (une procédure étant une fonction avec un retour de type 'void') ;
- programmation structurée : il reprend la notion struct du langage C. Cette notion est considérée en C++ aussi comme des classes dont l'accès par défaut est public ;
- programmation orientée-objet : il implémente la notion de classe (dont l'accès par défaut est privé), d'encapsulation grâce aux restrictions d'accès (publique, protégé, privé), d'héritage (simple ou multiple) à l'aide du mécanisme de dérivation, d'abstraction grâce aux classes de base abstraites pures (on peut parler d'interface bien que les héritages multiples sur une interface C++ lèvent des difficultés montrant que leur implémentation est plus proche d'une classe abstraite que d'une vraie interface comme on peut en trouver en Java) ou non, de polymorphisme dynamique (ou au runtime) grâce aux fonctions membres virtuelles ;
- programmation générique ou méta-programmation : il introduit les templates ou modèles générique de code qui permettent de créer automatiquement des fonctions ou des classes à partir d'un ou plusieurs paramètres.
- programmation 'lambda-closure' : le C++ 11 introduit la notion de fermeture.
Outre ces grands paradigmes, C++ implémente la notion de typage strict, de constance, de polymorphisme statique (ou à la compilation) grâce à la surcharge et aux fonctions génériques, de références (une alternative aux pointeurs bien plus robuste et bien moins dangereuse) et permet également la surcharge d'opérateurs et de simuler les mixins grâce au mécanisme de dérivation multiple. Il est largement compatible avec le langage C, ce qui est à la fois une richesse et un problème. En effet, le langage C est actuellement (en 2006) un langage largement utilisé pour écrire des systèmes d'exploitation (Windows, Linux, Mac OS). Le C++ peut donc très naturellement avoir un accès direct au système d'exploitation dans le cadre d'une programmation de bas niveau, tout en permettant une programmation de haut niveau en exploitant toute la richesse des concepts orientés objet. Le problème levé par ce lien filial étant que les développeurs ayant appris le C avant le C++ utilisent les techniques C là où le C++ possède des ajouts moins dangereux (par exemple, les références). Ce type de code est surnommé « C with classes ».
Les apports du C++ par rapport au C
modifierLe C++ a apporté par rapport au langage C les notions suivantes :
- les concepts orientés objet (encapsulation, héritage) ;
- les références ;
- la vérification stricte des types ;
- les valeurs par défaut des paramètres de fonctions ;
- la surcharge de fonctions (plusieurs fonctions portant le même nom se distinguent par le nombre et/ou le type de leurs paramètres) ;
- la surcharge des opérateurs (pour utiliser les opérateurs avec les objets) ;
- les templates de classes et de fonctions ;
- les constantes typées ;
- la possibilité de déclaration de variables entre deux instructions d'un même bloc.
Compilation
Définition de la compilation
modifierLa compilation consiste en une série d'étapes de transformation du code source en du code machine exécutable sur un processeur cible.
Le langage C++ fait partie des langages compilés : le fichier exécutable est produit à partir de fichiers sources par un compilateur.
Contrairement aux langages interprétés où il faut un logiciel interprétant le source, le fichier exécutable est produit pour une machine donnée : il est directement exécuté par le processeur. L'exécution est donc plus rapide.
Phases de la compilation
modifierLa compilation passe par différentes phases, produisant ou non des fichiers intermédiaires :
- préprocessing : Le code source original est transformé en code source brut. Les commentaires sont enlevés et les directives de compilation commençant par
#
sont d'abord traités pour obtenir le code source brut ; - compilation en fichier objet : les fichiers de code source brut sont transformés en un fichier dit objet, c'est-à-dire un fichier contenant du code machine ainsi que toutes les informations nécessaires pour l'étape suivante (édition des liens). Généralement, ces fichiers portent l'extension
.obj
ou.o
; - édition de liens : dans cette phase, l'éditeur de liens (linker) s'occupe d'assembler les fichiers objet en une entité exécutable et doit pour ce faire résoudre toutes les adresses non encore résolues, tant des mémoires adressées que des appels de fonction. L'entité exécutable est généralement soit un exécutable, soit une bibliothèque dynamique (DLLs sous Windows et toutes les variantes, tels que objet COM, OCX, etc, et les .so sous Linux).
Les compilateurs sont capables de générer des bibliothèques statiques, qui sont en quelques sortes le rassemblement d'un ensemble de fichiers objet au sein d'un même fichier. Dans ce cas, la phase d'édition de liens n'a pas eu lieu.
Cette découpe en phases permet de compiler séparément les bibliothèques en fichiers objets, et l'application et évite donc de tout re-compiler, ce qui prendrait beaucoup de temps pour les applications ayant un code source important.
Les fichiers sources
modifierLes fichiers sources d'un programme C++ portent souvent l'extension .cpp, .cxx, .cc, parfois .C et sont des fichiers textes lisibles par le programmeur.
Les fichiers d'en-tête
modifierLes fichiers "entêtes" ("headers" en anglais), traditionnellement d'extension .h
ou .hpp
(mais la plupart des entêtes systèmes du C++ standard n'ont plus d'extension du tout), contiennent généralement les prototypes de différentes fonctions, structures et classes. Ces prototypes proviennent :
- des bibliothèques standards du C++ : tout compilateur C++ doit fournir ces fichiers ainsi que les fichiers objets contenant l'implémentation des bibliothèques standards (souvent liées par défaut) ;
- de bibliothèques non standards fournis par l'éditeur du compilateur ou de l'environnement de développement ;
- de bibliothèques non standards (gratuites ou payantes) que le programmeur s'est procuré : citons par exemple la bibliothèque permettant d'accéder à une base de données mySQL. Lorsqu'une bibliothèque non standard est utilisée et que celle-ci englobe de nombreuses fonctionnalités (interface graphiques, accès à une base de données, surcouche système, communications réseaux, etc.), on parle parfois de framework. Citons par exemple Qt et WxWidgets dans cette catégorie. Un framework apparait parfois comme une véritable surcouche du système d'exploitation.
Les fichiers .cpp
modifierLes fichiers .cpp
(parfois .c
ou .cc
) contiennent la définition (l'implémentation) des différentes fonctions et méthodes définies dans les fichiers d'en-tête. La compilation des fichiers .cpp
produit dans un premier temps des fichiers objets (extension .obj
ou .o
en général).
À noter que ces fichiers .cpp
utilisent les fichiers d'en-tête. Ils les appellent en utilisant la syntaxe #include "nomdefichier"
(nomdefichier comprenant l'extension, donc, souvent ".h"). Comme indiqué ci-dessus, ces fichiers d'en-tête seront donc inclus complètement par le préprocesseur à l'intérieur du code source brut.
L'édition de liens (linking)
modifierL'édition de liens est la phase finale de la compilation qui va rassembler tous les fichiers objets afin de former un fichier exécutable. Les fichiers objets proviennent :
- de la compilation de fichiers .cpp ;
- de la bibliothèque standard (ceux-ci sont souvent liés automatiquement) ;
- de bibliothèques ou framework extérieurs. Il faut dans ce cas explicitement dire à l'éditeur de liens quels sont les fichiers qu'il doit lier.
Les projets
modifierLes makefiles
modifierAfin de rendre possible le développement d'applications, les fichiers sources sont organisés sous forme de projet. Le C++ ayant été conçu sur les bases du C, il s'est ensuivi une gestion assez équivalente des projets. Avant l'avènement des environnements intégrés tels qu'on les connaît aujourd'hui, bon nombre de projets étaient construits sous forme de "makefile"s pris en charge par un outil spécifique make. De nos jours, cette technique est encore fortement utilisée dans les environnements UNIX et Linux. Il existe des alternatives plus modernes à make tels que CMake, automake, SCons, Boost.Build (bjam)…
Les environnements de développement intégrés
modifierLes environnements de développement intégrés (EDI en français et IDE en anglais) sont des outils complets permettant de développer dans un certain langage de programmation. Ils contiennent en général :
- un compilateur ;
- un éditeur avec mise en évidence de la syntaxe (syntax highlighting) ;
- des outils facilitant la gestion d'un projet :
- outil pour ajouter/supprimer/déplacer des sources,
- outil pour paramétrer la compilation et l'édition de liens,
- outil pour créer des modes de compilation (typiquement debug/release) et tous les paramètres y afférent ;
- des bibliothèques non standard censées aider le programmeur. Parfois, il s'agit d'un véritable framework ;
- des outils pour permettre le lancement de l'application ;
- des outils pour déboguer l'application ;
- des outils pour créer des interfaces graphiques ;
- …
Quelques exemples
modifierMulti-plateformes
modifier- Eclipse, gratuit et Open Source, avec le plugin CDT, permet le développement en C/C++
- Code::Blocks, gratuit et Open Source (EDI complet fournit avec gcc mais utilisable avec d'autres compilateurs).
- CodeLite, gratuit et Open Source de base, seule exception : les plugins développés en annexe pour cet EDI peuvent être de n'importe quelle licence, y compris propriétaire et fermée.
- Qt Creator, gratuit et Open Source, dédié au framework Qt (mais n'impose pas son usage).
Sous windows
modifier- Microsoft Visual Studio
- Borland C++ Builder
- Devcpp (disponible gratuitement sous Windows)
- CLion de Jetbrains
Sous Linux
modifier- Anjuta
- Kdevelop
- g++ (Commun à tout système Unix, ligne de commande)
- clang (Plus rapide que GCC mais toutefois compatible, ligne de commande)
Sous MacOS X
modifier- Apple Xcode (livré avec Mac OS X, utilise le compilateur clang).
- Metrowerks CodeWarrior.
- g++ (commun à tout système Unix, ligne de commande)
Un premier programme
Après ces quelques introductions, nous allons désormais pouvoir commencer à apprendre le C++.
Le fichier source
modifierTapez le fichier suivant dans un éditeur de texte :
#include <iostream>
using namespace std;
int main()
{
cout << "BONJOUR" << endl;
return 0;
}
En général, un fichier source C++ utilise l'extension .cpp
.
On trouve également l'extension .cc
.
Le principal est de choisir l'extension une fois pour toute, pour la cohérence des fichiers.
Explications
modifierLa directive #include
modifier
La directive de compilation #include <iostream>
permet d'inclure les prototypes des différentes classes contenues dans la bibliothèque standard iostream. Cette bibliothèque contient la définition de cout
qui permet entre autre d'afficher des messages à l'écran.
L'espace de nommage standard
modifierUn espace de nommage peut être vu comme un ensemble d'identifiants C++ (types, classes, variables etc.). cout fait partie de l'espace de nommage std. Pour parler de l'objet prédéfini cout de l'espace de nommage std, on peut écrire std::cout. Cette notation est assez lourde car elle parsème le code de std::. Pour cela on a écrit using namespace std qui précise que, par défaut, la recherche s'effectuera aussi dans l'espace de nommage std. On pourra donc alors écrire tout simplement cout pour parler de std::cout. On dit que std est un espace de nom (namespace en anglais).
La fonction main
modifierTout programme en C++ commence par l'exécution de la fonction main. Il se termine lorsque la fonction main est terminée. La fonction main peut être vue comme le point d'entrée de tout programme en C++. Cette fonction renvoie un entier, très souvent 0, qui permet d'indiquer au système d'exploitation que l'application s'est terminée normalement.
L'objet cout
modifierIl permet d'envoyer des caractères vers le flux de sortie standard du programme, c'est-à-dire l'écran pour ce programme (sa fenêtre console). Il permet donc d'afficher des messages à l'écran. En utilisant l'opérateur <<
, on peut écrire une chaîne de caractères à l'écran. L'instruction cout << "BONJOUR";
affiche donc le message BONJOUR à l'écran.
return 0
modifierCette instruction (facultative ici) indique que la fonction main
est terminée et que tout s'est bien passé.
Nous verrons plus loin ce que veut dire exactement l'instruction return
, mais même si elle est facultative il est fortement recommandé de la mettre, que ce soit par simple souci de conformité ou du fait que votre programme est censé renvoyer une valeur à la fin de son exécution.
Exécution
modifierSi on compile et exécute ce programme, le message BONJOUR s'affiche à l'écran.
Il est possible que vous ne voyez qu’une fenêtre noire "flasher" si vous êtes sous Windows . Pour résoudre ce problème, ajoutez avant le "return 0" l’instruction :
system("pause");
Les commentaires
Tout bon programme a des fichiers sources bien commentés pour expliquer comment cela fonctionne et pourquoi certains choix ont été faits. Ce qui évite une perte de temps lorsque le code source est repris et modifié, soit par un autre développeur, soit par l'auteur qui ne se souvient pas forcément de son projet s'il n'y a pas touché depuis longtemps.
Bloc de commentaire
modifierUn bloc de commentaire est délimité par les signes slash-étoile /*
et étoile-slash */
comme en Java et en C#. Exemple :
/*
Un commentaire explicatif
sur plusieurs lignes...
*/
Les blocs ne peuvent être imbriqués car dès que le compilateur trouve slash-étoile /*
, il recherche la première occurrence d'étoile-slash */
terminant le commentaire.
/* : début du commentaire
/* : ignoré
fin du commentaire : */
erreur ici car le commentaire est fini : */
Commentaire de fin de ligne
modifierUn commentaire de fin de ligne débute par un double slash //
et se termine au prochain retour à la ligne. Exemple :
x++; // augmenter x de 1
Le commentaire ne doit pas paraphraser le code, mais expliquer le rôle de cette partie du code, comme par exemple :
x++; // décaler le point d'un pixel vers la droite
Astuce : La majorité des éditeurs évolués (Visual Studio, Borland C++, Eclipse ...) utilisent ce type de commentaire pour les commandes commenter/décommenter le groupe de lignes sélectionnées.
Le préprocesseur
Avant de compiler le programme, il est possible d'effectuer certaines modifications sur le code source. Le programme effectuant ces modifications s'appelle le préprocesseur. Les commandes destinées au préprocesseur commencent toutes par # en début de ligne.
Inclusion de fichiers
modifierPour inclure un fichier à un certain endroit dans le fichier source, on écrit :
#include "nom_du_fichier"
Le contenu du fichier nom_du_fichier est alors inséré dans le fichier source.
Le nom du fichier peut être écrit entre guillemets "nom_du_fichier"
ou entre chevrons <nom_du_fichier>
. Dans le premier cas, cela signifie que le fichier se trouve dans le même dossier que le fichier source, tandis que dans le deuxième cas, il s'agit d'un fichier se situant dans un endroit différent (ce fichier pouvant être fournit par le compilateur ou une librairie externe par exemple).
Exemple :
#include <iostream>
Le fichier C++ standard iostream est inclus à cet endroit-là dans le code. Il contient la définition de certains objets standards notamment cin et cout.
#define, #undef
modifierLa directive #define
permet de remplacer toutes les occurrences d'un certain mot par un autre. Par exemple :
#define N 1143
Sur cet exemple toutes les occurrences de N seront remplacées par 1143
. Cela est parfois utilisé pour définir des constantes. On préférera toutefois utiliser le mot-clé const
.
On peut très bien ne pas fixer de valeur et écrire :
#define PLATEFORME_INTEL
La variable de compilation PLATEFORME_INTEL
est ici définie. Combiné à #ifdef
, on pourra compiler ou non certaines parties du code à certains endroits du programme.
De la même façon que l'on peut définir une variable, on peut arrêter une définition en utilisant #undef
. Son utilisation est rare, mais peut servir à ne plus définir une variable de compilation. Par exemple:
#undef PLATEFORME_INTEL
#ifdef, #ifndef, #if, #endif et #else
modifierPrésentation
modifierToutes ces directives permettent la compilation conditionnelle.
C'est-à-dire que la partie du code comprise entre la directive conditionnelle (#ifdef
, #ifndef
ou #if
) et la fin du bloc signalée par la directive #endif
n'est compilée que si la condition est remplie.
- La directive
#ifdef
permet de compiler toute une série de lignes du programme si une variable de compilation a précédemment été définie (par la directive#define
). La directive#endif
indique la fin de la partie de code conditionnelle. La partie du programme compilée sera toute la partie comprise entre le#ifdef
et le prochain#endif
.
- La directive
#ifndef
permet de compiler un bout de programme si une variable de compilation n'est pas définie. C'est donc l'inverse de#ifdef
. La fin de la partie à inclure est déterminée également par#endif
.
- La directive
#if
permet de tester qu'une expression est vraie. Cette expression ne peut utiliser que des constantes (éventuellement définies par une directive#define
), et la fonctiondefined
permettant de tester si une variable de compilation existe.
Il faut noter que les directives #ifdef et #ifndef, bien que très largement utilisées, sont considérées comme dépréciées ("deprecated"). On préférera donc la syntaxe :
#if defined(MA_VARIABLE)
à #ifdef MA_VARIABLE
et
#if !defined(MA_VARIABLE)
à #ifndef MA_VARIABLE
.
- Chacune de ces conditions peut être accompagné d'une directive
#else
qui permet d'inclure un bout de programme si la condition n'est pas vérifiée.
Il ne faut pas abuser de ces directives et elles sont surtout utilisées :
- pour gérer des problèmes de portabilité.
- au début des fichiers d'en-tête pour éviter une double compilation.
- dans les fichiers d'en-tête de DLL sous Windows.
Exemples
modifierExemple 1
modifier#include <iostream>
using namespace std;
#define FRENCH
int main()
{
#ifdef FRENCH
cout << "BONJOUR";
#else
cout << "HELLO";
#endif
return 0;
}
Dans ce programme, il suffit d'effacer #define FRENCH
et de recompiler le programme pour passer d'une version française à une version anglaise. Ceci pourrait être utile si le programme comporte 10 000 lignes (ce qui est faible pour un programme réel). Bien évidemment, il existe bien d'autres façons de gérer le multilinguisme en C++.
Exemple 2
modifierCet exemple montre l'utilisation d'expression dans les définitions de valeurs, et les précautions à prendre pour les expressions.
// Définir la taille d'un tableau contenant le prix :
// - de 5 variétés d'orange
#define N_ORANGES 5
// - de 3 variétés de pommes
#define N_POMMES 3
#define N_TOTAL_FRUITS N_ORANGES+N_POMMES
double prix_fruits[N_TOTAL_FRUITS];
#if N_TOTAL_FRUITS > 7
// ... plus de 7 variétés de fruits
#else
// ... moins de 7 ou égale à 7 variétés de fruits
#endif
cout << "Total de fruits : " << (N_TOTAL_FRUITS) << endl;
// 8
cout << "Double total de fruits : " << (N_TOTAL_FRUITS * 2) << endl;
// <!> 11 au lieu de 16 car l'opérateur * est prioritaire sur +
// dans l'expression étendue 5 + 3 * 2
// --> Utilisez des parenthèses :
// - autour du nom de la macro
cout << "Double total de fruits : " << ((N_TOTAL_FRUITS) * 2) << endl;
// 16
// - mieux : dans la définition
#define N_TOTAL_FRUITS_MIEUX (N_ORANGES+N_POMMES)
cout << "Double total de fruits : " << (N_TOTAL_FRUITS_MIEUX * 2) << endl;
// 16
Exemple 3
modifierFichier toto.h
#ifndef TOTO_H
#define TOTO_H
... écrire ici les prototypes ...
#endif
Le problème :
Imaginons qu'un fichier header toto.h contienne le prototype d'une certaine classe ou d'une fonction. Imaginons que le programme contiennent 3 autres fichiers headers nommés A.h, B.h et C.h qui ont tous les 3 besoin des prototypes inclus dans toto.h, ces 3 fichiers vont commencer par #include"toto.h". Imaginons également que C.h a besoin des prototypes inclus dans A.h et B.h. C.h va donc commence par #include"A.h" et #include"B.h". Le problème est que le fichier toto.h va être inclus plusieurs fois. Le compilateur va alors refuser de compiler le programme en indiquant que plusieurs prototypes d'une même fonction sont inclus.
Solution :
Pour résoudre l'inclusion multiple de fichier headers (inévitable), on va faire commencer le fichier header par #ifndef TOTO_H et il se termine par #endif. Si la variable de compilation TOTO_H n'est pas définie, alors le header sera inclus, sinon, il sera tout simplement vide. Juste après #ifndef TOTO_H, nous allons écrire #define TOTO_H définissant justement cette variable de compilation TOTO_H.
La première fois que le header sera inclus, TOTO_H n'est pas défini, le header normal sera donc inclus. #define TOTO_H définira alors la variable de compilation TOTO_H. La deuxième fois que ce même header sera inclus, et les fois suivantes, TOTO_H sera défini et par conséquent, le header sera vide. les prototypes n'auront donc été inclus qu'une seule fois. Le tour est joué. Il faut donc faire commencer systèmatiquement (c'est tout du moins conseillé) tous les fichiers header par les 2 lignes #ifndef ... et #define ... et les faire se terminer par #endif.
Autre solution
modifierToutefois, il existe une autre solution au problème précédent en utilisant la directive suivante en début de fichier .h
:
#pragma once
Cette directive indique au compilateur d'ignorer le fichier s'il a déjà été "visité", et ne fonctionne qu'avec certains compilateurs :
- Visual C++
- CodeWarrior
- GCC for Darwin
Les macros
modifierPrésentation
modifierLes macros sont des #define
particulier parce qu'ils contiennent des paramètres. Ainsi si vous écrivez :
#define AFFICHE(x) cout << x << endl;
Alors vous pouvez écrire AFFICHE("BONJOUR")
et le préprocesseur modifiera cette ligne et la transformera en cout << "BONJOUR" << endl;
. Il y aura substitution de x
par "BONJOUR"
. Il ne faut pas abuser des macros et très souvent l'utilisation de fonctions, notamment les fonctions inline, est préférable.
Exemple
modifier#include <iostream>
using namespace std;
#define AFFICHER(x) cout << x << endl;
int main()
{
AFFICHER("BONJOUR");
return 0;
}
Bonnes pratiques
modifierAfin d'utiliser correctement les macros, il est préférable de les afficher clairement et de les rendre suffisamment flexible à différentes utilisations.
Si la macros est constituée de plusieurs instructions séparés par des ;
, il est préférable d'écrire la macro sur plusieurs lignes afin d'accroître sa lisibilité. Pour indiquer à une macro que sa définition continue sur la ligne suivante, il suffit d'indiquer un antislash ('\') en dernier caractère le la ligne.
#define AFFICHER(x) \
cout << x; \
cout << endl;
L'utilisation la plus courante des macros est de ne pas mettre de ;
à la fin de celle-ci, mais de le mettre dans le code, là où elle est utilisée. En effet, on peut prévoir qu'une macro soit utilisable en tant qu'instruction simple, ou en tant que condition ou paramètre de fonction où l'on ne doit pas mettre de ;
. Pour les macros qui ne retournent rien (comme la macro AFFICHER dans l'exemple précédent), le placement du ;
n'est pas un problème car elles ne retournent rien et ne seront jamais utilisées dans une condition ou un appel de fonction.
#include <iostream>
using namespace std;
#define DIVISER(x, y) ((x) / (y))
int main()
{
int valeur = DIVISER(5, 3);
if (DIVISER(8, 2) == 4)
cout << DIVISER(1.0, 5.0) << endl;
return 0;
}
Il est fortement conseillé de toujours utiliser un paramètre de macro avec des parenthèses autour. Si l'on reprend l'exemple précédent sans parenthèses :
#include <iostream>
using namespace std;
#define DIVISER(x, y) x / y
int main()
{
int valeur = DIVISER(5, 3);
if (DIVISER(4 + 4, 2) == 4)
cout << DIVISER(1.0, 5.0) << endl;
return 0;
}
Ici, le résultat obtenu n'est pas forcement celui désiré. DIVISER(4 + 4, 2)
sera traduit après la précompilation par 4 + 4 / 2
. Ceci donne pour valeur 4 + 2, soit 6. Ajouter un maximum de parenthèses permet de s'assurer de la validité de la macro sous plusieurs utilisations différentes. Ainsi, dans l'exemple précédent, une utilisation de parenthèses dans la macro (#define DIVISER(x, y) ((x) / (y))
), aurait traduit DIVISER(4 + 4, 2)
en ((4 + 4) / (2))
. Ceci aurait donné comme valeur 8 / 2 = 4, la valeur attendue.
Les types de base et les déclarations
Déclarations, types et identificateurs
modifierLes variables
modifierComme la plupart des langages de programmation, le C++ utilise la notion de variable. Une variable peut être vue comme une zone de la mémoire qui comprend une certaine valeur.
Les types en C++ et les systèmes de représentation
modifierLe langage C++ impose un mécanisme de type pour indiquer la nature des données contenues dans une variable. Ainsi un double
permettra de stocker un réel et un int
permettra de stocker un entier. Par contre, il ne définit pas de système de représentation pour représenter ces variables. Ainsi, le standard ne spécifie pas comment on représente un double sous la forme d'une suite de bits. Le système de représentation utilisé peut donc varier entre deux ordinateurs ou en fonction du compilateur utilisé. Cette particularité peut parfois poser de graves problèmes de portabilité d'un programme.
Les déclarations
modifierToute variable en C++ doit être déclarée : la déclaration indique l'identificateur de la variable (son nom) et sa nature (son type).
Syntaxe :
type identificateur;
Exemple :
int a;
Cette déclaration définit une variable d'identificateur a qui contient un entier de type int.
Identificateurs valides
modifierUn identificateur est une suite de caractères (pouvant être majuscules ou minuscules), de chiffres ou d'underscores (underscore ou "tiret bas" est le caractère _
). Cette suite ne peut pas commencer par un chiffre. Un identificateur ne peut contenir ni espace, ni tiret -
(utilisé pour l'opération de soustraction).
Les types entiers
modifierLe langage C++ possède plusieurs types de base pour désigner un entier.
- int : contient un entier de taille normale, positif ou négatif.
- short int : contient un entier de petite taille, positif ou négatif.
- long int : contient un entier de grande taille (32 bits), positif ou négatif.
- long long int : contient un entier de plus grande taille (64 bits), positif ou négatif.
- unsigned int : contient un entier de taille normale, positif ou nul.
- unsigned short int : contient un entier de petite taille, positif ou nul.
- unsigned long int : contient un entier de grande taille (32 bits), positif ou nul.
- unsigned long long int : contient un entier de plus grande taille (64 bits), positif ou nul.
La longueur d'un long int , d'un int et d'un short int n'est pas spécifié par le langage. Plus un entier est représenté sur un grand nombre de bits, plus il pourra être grand. Ainsi, il est usuel de représenter un int sur 32 bits : il peut alors représenter n'importe quel entier entre -231 et 231-1. Le langage impose juste que la taille d'un long int doit être supérieure ou égale à celle d'un int et que la taille d'un int doit être supérieure ou égale à celle d'un short int !
Le système de représentation utilisé non plus. En général, la base 2 est utilisée pour les types unsigned int, unsigned long int et unsigned short int et le complément à 2 est utilisé pour les types int, long int et short int. Ce n'est toutefois nullement obligatoire.
Interprétation des constantes entières
- si une constante commence par 0x, elle sera interprétée comme une valeur en hexadécimal (base 16).
- si une constante commence par 0 suivi d'un chiffre, elle sera interprétée comme valeur en octal (base 8).
- dans le cas contraire, elle sera interprétée comme étant en base 10.
Exemples :
98 représente 98 en base 10.
0x62 représente 98 en hexadécimal (6*16+2).
0142 représente 98 en octal (1*64+4*8+2).
- si une constante entière se termine par un U, elle sera interprétée comme étant un unsigned.
- si une constante entière se termine par un L, elle sera interprétée comme un long.
- si une constante entière se termine par LL, elle sera interprétée comme un long long.
Exemples
- 78U représente le unsigned int valant 78.
- 78L représente le long int valant 78.
- 78LL représente le long long int valant 78.
- 78ULL représente le unsigned long long int valant 78.
Les types réels
modifierPour représenter un réel, il existe 3 types de base :
- float (simple précision)
- double (double précision)
- long double (précision étendue)
Le langage ne précise pas ni le système de représentation, ni la précision de ces différents formats. Le type long double est juste censé être plus précis que le double, lui-même plus précis que le float.
Il est toutefois usuel (mais non obligatoire) de représenter le float sur 32 bits dans le format IEEE 754 simple précision et le double sur 64 bits dans le format IEEE 754 double précision.
Interprétation des constantes réelles
- On peut écrire un réel sous la forme 1.87 ou .56 ou 8. ou 7.6e10 ou 5.4e-3 ou encore 10.3E+2.
- Une constante qui se termine par un f ou un F sera interprétée comme un float.
- Une constante qui se termine par un l ou un L sera interprétée comme un long double.
Exemples :
- 3.65L représente le long double valant 3.65.
- 3.65F représente le float valant 3.65.
Les caractères
modifierLe caractère est l'élément de base de tout texte et donc sans doute de toute pensée. Les changements de l'informatique ont conduit à différentes approches pour représenter un caractère sur cinq, sept, huit, seize, dix-sept ou trente-deux bits.
Aujourd'hui, les deux types incontournables sont le type char hérité du C, et ceux relatifs à Unicode.
Ce chapitre traite des types de bases utiles pour représenter des caractères. Toutefois les caractères sont rarement utilisés seuls, surtout dans les applications Unicode. À ce sujet, vous trouverez dans la suite de cet ouvrage les chapitres suivants :
- Programmation C++/La_librairie_standard#Les chaînes de caractères
- Programmation C++/Les_tableaux#Les tableaux de caractères
Le type char
modifierIl s'agit du type historique pour représenter un caractère. Bien que le type char fasse penser à un caractère (character en anglais), il désigne souvent de facto un octet, qui peut être signé ou non signé suivant le compilateur.
C'est l'un des concepts que le C++ a repris du langage C.
Aucun système de représentation n'est imposé pour les caractères et on utilise en général des dérivés (8 bits) du code ASCII (qui est un code sept bits).
Un caractère doit juste être codé sur au moins 8 bits : le standard C++ reprend les mêmes propriétés requises que le langage C dont le standard pour les types entiers (dont fait partie le type char
) ne spécifie pas explicitement la taille en octets mais l'intervalle à supporter au minimum.
Le type char
signé doit supporter les valeurs entre -127 et +127 [1], et le type char
non signé doit supporter les valeurs entre 0 et 255.
La variante signée requiert donc le support de 255 valeurs au lieu de 256, car un nombre signé peut être représenté de plusieurs façons[2] pouvant impliquer deux représentations possibles pour la valeur zéro (-0 et +0) ; ceci n'est pas le cas de la représentation en complément à 2 permettant de supporter un intervalle de -128 à +127.
Usuellement, le type char est exactement 8 bits, ce qui fait que c'est le seul type utilisé pour représenter un octet, créant ainsi une confusion entre caractère et octet. Le chapitre suivant s'intéresse au huitième bit.
Exemple :
char c;
c = 'E';
Le caractère E est transféré dans la variable c de type char .
On peut transférer un char dans un int pour récupérer son codage.
Ainsi, on peut écrire :
int a;
char b;
b = 'W';
a = b;
On récupère alors dans a le codage du caractère 'W'. On récupérera donc en général le code ASCII de 'W' dans la variable a.
Aujourd'hui, il est désuet de considérer que l'on code tous caractères sur un seul char. Le type char reste cependant incontournable car il est souvent utilisé pour désigner un octet.
Transformation de majuscule en minuscule
modifier#include<iostream>
using namespace std;
int main()
{
char a, b;
cout<<"Tapez un caractere : "; cin>>a;
if (a>='A' && a<='Z') {
cout<<"Vous avez tapé une majuscule."<<endl;
b = a + ('a'-'A');
cout<<"La minuscule correspondante est "<< b <<endl;
}
else if (a>='a' && a<='z') {
cout<<"Vous avez tapé une minuscule."<<endl;
b = a + ('A'-'a');
cout<<"La majuscule correspondante est "<< b <<endl;
}
else cout<<"Vous n'avez pas tapé une lettre."<<endl;
return 0;
}
- Explications
- On demande à l'utilisateur de taper un caractère dans une variable a.
- Si l'utilisateur a tapé une majuscule, on affiche la minuscule correspondante.
- Si l'utilisateur a tapé une minuscule, on affiche la majuscule correspondante.
- Exécution 1
Tapez un caractère : H
Vous avez tapé une majuscule.
La minuscule correspondante est h.
- Exécution 2
Tapez un caractère : w
Vous avez tapé une minuscule.
La majuscule correspondante est W.
- Exécution 3
Tapez un caractère : 9
Vous n'avez pas tapé une lettre.
Les types signed char et unsigned char
modifierLorsqu'on transfère un char dans un int, peut-on récupérer une valeur négative ? La réponse est oui si on utilise le type signed char et non si on utilise le type unsigned char. Ces types peuvent être utile lorsqu’on manipule des caractères non ASCII.
Pour les données de type char, lorsque ni signed ni unsigned ne sont précisés, le choix entre les deux est fait par le compilateur. Dans tous les cas à l'époque où seuls les codages ASCII et autres codages ISO-646 étaient utilisés cela n'avait pas d'importance.
Aujourd'hui cependant, quasiment tous les codages de caractères utilisent a minima huit bits. C'est notamment le cas d'UTF-8. L'éventuel bit de signe doit donc être considéré pour permettre la portabilité du logiciel.
Les types char16_t
and char32_t
modifier
À partir de C++11 (C++ norme de 2011) trois types de chaînes de caractères sont prise en charge: UTF-8, UTF-16, et UTF-32. Le type char
conserve ses unités de codage de huit bits pour le codage des caractères Unicode via UTF-8, les nouveaux types char16_t
et char32_t
sont des unités de codage de seize ou trente-deux bits pour le codage des caractères Unicode via UTF-16 ou UTF-32.
Ces types sont standard à partir de C++2011 mais n'existent pas sur des compilateurs plus anciens, ni même sur les compilateurs C-2011.
Le type wchar_t
modifierCe type de caractère n'existe qu'avec les compilateurs supportant l'Unicode (jeu de caractère international standard couvrant les langues du monde entier). Ces caractères sont stockés sur 2 octets ou 4.
Les valeurs constantes de caractère (entre simple quote) ou de chaîne de caractères (entre double quote) doivent alors être précédées du caractère L
.
Exemple :
wchar_t a = L'é'; // caractère 'é' unicode(16 ou 32 bits);
wchar_t[] chaine = L"Bonjour, monde !"; // chaîne de caractère unicode
Ce type présente le problème de ne pas être standard: certaines implémentations n'offrent que 16 bits soit une portion limitée des caractères Unicode.
API exceptionnellement Unicode
modifierCertaines API C++ telle que Visual C++ sous Windows sont paramétrables par une option dite unicode ou non. Pour cela elles se basent sur le type de caractèreTCHAR
que le compilateur interprète (en fait remplace) par char
ou wchar_t
selon l'option Unicode. Dans ce cas, les valeurs constantes de chaînes et de caractères doivent être encadrées par la macro _T
. Cette macro peut alors faire précéder les constantes d'un caractère L
ou non.
Exemple :
TCHAR a = _T('é'); // caractère 'é' unicode ou huit bits;
TCHAR[] chaine = _T("Bonjour, monde !"); // chaîne de caractère unicode ou ascii (huit bits)
Les booléens
modifierLe C++ utilise le type bool pour représenter une variable booléenne. Un booléen ne peut prendre que 2 valeurs : true
ou false
. On peut affecter à un booléen le résultat d'une condition, ou l'une des deux constantes citées précédemment.
Exemple :
bool a;
int c;
c = 89;
a = (c > 87);
// a reçoit alors la valeur true.
L'opérateur const
modifierL'opérateur const
placé devant une déclaration de variable décrit celle-ci comme constante; elle ne peut être changée.
Exemple :
int x = 2;
const int y = 2;
x += 1; // x recevra la valeur 3
y += 1; // Cette opération est interdite par un compilateur C++ conforme
Dans cet exemple, nous déclarons deux variables x et y, chacune avec la même valeur. La première variable (x) peut être modifiée, ce qui n'est pas le cas de la seconde (y) qui est déclarée const. Le compilateur refusera toute opération qui tenterait d'en modifier son contenu (opérations d'assignation et d'incrémentation =, +=, -=, *=, /=, >>=, <<=, ++).
Il en va de même pour les objets. Un objet déclaré const ne pourra pas être modifié, c'est-à-dire que le compilateur refusera l'invocation d'une méthode non const sur cet objet.
Exemple :
class X {
public:
X() : valeur_(0) {}
explicit X(int valeur) : valeur_(valeur) {}
void annule() { this->valeur_ = 0; }
void init(int valeur) { this->valeur_ = valeur; }
int valeur() const { return this->valeur_; }
private:
int valeur_;
};
X x(2)
const X y(5);
x.annule(); // Ok
y.annule(); // Erreur de compilation.
Dans cet exemple, deux objets x et y ont été déclarés. Il est possible d'invoquer n'importe quelle méthode (const ou non) sur l'objet x, par contre seules les méthodes const peuvent être invoquées sur l'objet y puisqu'il est déclaré const.
L'opérateur sizeof
modifierL'opérateur sizeof
permet de savoir le nombre d'octets qu'occupe en RAM une certaine variable.
On peut écrire sizeof(a) pour savoir le nombre d'octets occupé par la variable a. On peut aussi écrire, sizeof(int) pour connaître le nombre d'octets occupés par une variable de type int. Cet opérateur est très utile lorsqu'on veut résoudre des problèmes de portabilité d'un programme.
Plus précisément sizeof(char)
vaut toujours 1, par définition. Sur la plupart des architectures un char
est codé par huit bits, soit un octet. Programmation C/Types de base#Caractères
Définir un alias de type
modifierL'instruction typedef
permet de définir un alias pour un type de données. Ceci permet dans certains cas de raccourcir le code, et dans tous les cas c'est l'occasion de donner un nom plus explicite à un type.
Ceci favorise une meilleur lecture du code.
La syntaxe de typedef
est exactement la même que celle de la déclaration d'une variable, excepté que l'instruction commence par typedef
et qu'aucune variable n'est réservée en mémoire, mais un alias du type est créé.
Exemple:
typedef unsigned long data_size;
data_size readData(char* buffer, data_size buffer_size);
On s'aperçoit plus facilement que la fonction retourne le nombre d'octets lus que dans la déclaration sans typedef
:
unsigned long readData(char* buffer, unsigned long buffer_size);
Voir aussi
modifierRéférences
modifier- ↑ Standard ISO/IEC 9899:TC3 - http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
- ↑ Les entiers signés peuvent être représentés par signe et valeur absolue, en complément à un, en complément à deux, ...
Les types de base et les déclarations/Exercices
Exercices
modifierEXERCICE 1
modifierÉcrire un programme qui demande à l'utilisateur de taper la largeur et la longueur d'un champ et qui en affiche le périmètre et la surface.
#include<iostream>
using namespace std;
int main()
{
double largeur, longueur, surface, perimetre;
cout << "Tapez la largeur du champ : "; cin >> largeur;
cout << "Tapez la longueur du champ : "; cin >> longueur;
surface = largeur * longueur;
perimetre = 2 * (largeur + longueur);
cout << "La surface vaut : " << surface << endl;
cout << "Le périmètre vaut : " << perimetre << endl;
return 0;
}
EXERCICE 2
modifierÉcrire un programme qui demande à l'utilisateur de taper 5 entiers et qui affiche leur moyenne. Le programme ne devra utiliser que 2 variables.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- Calcul d'une moyenne.
- Utilisation des types
int
etdouble
. - L'utilisation de
cin
et decout
. - L'affectation.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int entree; double moy=0;
cout << "Tapez la valeur numero 1 : "; cin>>entree; moy = moy + entree;
cout << "Tapez la valeur numero 2 : "; cin>>entree; moy = moy + entree;
cout << "Tapez la valeur numero 3 : "; cin>>entree; moy = moy + entree;
cout << "Tapez la valeur numero 4 : "; cin>>entree; moy = moy + entree;
cout << "Tapez la valeur numero 5 : "; cin>>entree; moy = moy + entree;
moy = moy / 5;
cout << "La moyenne vaut : " << moy << endl;
return 0;
}
EXERCICE 3
modifierÉcrire un programme qui demande à l’utilisateur de saisir 2 entiers A et B, qui échange le contenu des variables A et B puis qui affiche A et B.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - L'affectation.
- Un "algorithme" rudimentaire : échanger le contenu de 2 variables.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int a, b, temp;
cout << "donner la valeur de a : "; cin >> a;
cout << "donner la valeur de b : "; cin >> b;
temp = a;
a = b;
b = temp;
cout << "La valeur de a est " << a << endl;
cout << "La valeur de b est " << b << endl;
return 0;
}
EXERCICE 4
modifierÉcrire un programme qui demande à l'utilisateur de taper le prix HT d'un kilo de tomates, le nombre de kilos de tomates achetés, le taux de TVA (Exemple 5.5, 19.6,...). Le programme affiche alors le prix TTC des marchandises.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- Le choix d'identificateurs pertinents et explicites.
- L'utilisation de
cin
et decout
. - L'affectation.
- Modélisation d'un problème "économique".
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
double prixht, poids, tva, total;
cout << "Tapez le prix HT d'un kilo de tomates : "; cin>>prixht;
cout << "Combien de kilos avez-vous achetes : "; cin>>poids;
cout << "Quel est le taux de TVA : "; cin>>tva;
// totalTTC = totalHT + totalTVA
// totalTVA = totalHT * tva/100
// totalTTC = totalHT + totalHT * tva/100
// totalTTC = totalHT * (1 + tva/100)
total = (1 + tva / 100) * prixht * poids;
cout << "Le prix TTC est : " << total << endl;
return 0;
}
EXERCICE 5
modifierÉcrire un programme qui demande à l’utilisateur de saisir les coordonnées de deux points du plan A et B et qui affiche la distance entre A et B.
Indication 1 : on pourra utiliser le théorème de Pythagore.
Indication 2 : dans le fichier include cmath
, il y a une fonction sqrt
qui calcule la racine carrée.
Exemple d’utilisation : x = sqrt(y)
x et y doivent être des double
.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de cin et de cout.
- L'affectation.
- Utilisation de variables explicites.
- Utilisation de la bibliothèque math.h.
- Modélisation d'un problème issu de la géométrie sous forme informatique.
Voici le fichier source :
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
double XA,YA,XB,YB, dx,dy, distance;
cout << "Tapez l'abscisse de A : "; cin >>XA;
cout << "Tapez l'ordonnée de A : "; cin >>YA;
cout << "Tapez l'abscisse de B : "; cin >>XB;
cout << "Tapez l'ordonnée de B : "; cin >>YB;
dx = XA-XB;
dy = YA-YB;
distance = sqrt(dx*dx + dy*dy);
cout << "La distance AB est : " << distance << endl;
return 0;
}
Fonction racine carré : sqrt(nombre)
Fonction carré : pow(nombre, 2)
Les résultats renvoyés par des deux fonctions précédentes sont de type double
.
Les opérations de base
Le langage C++ possède un grand nombre d'opérateurs de base effectuant entre autre des opérations arithmétiques et capables de travailler sur les entiers, les réels, etc... Nous allons présenter ici ces principaux opérateurs.
L'affectation
modifierSyntaxe
modifieridentificateur=expression
Sémantique
modifierOn commence par évaluer l'expression et on met le résultat dans la variable identificateur.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a, b, s;
cout << "Tapez la valeur de a : "; cin >> a;
cout << "Tapez la valeur de b : "; cin >> b;
s = a + b; // affecter le résultat de l'addition à la variable s
cout << "La somme a+b vaut : " << s << endl;
return 0;
}
Exécution
modifierTapez la valeur de a : 45 Tapez la valeur de b : 67 La somme a+b vaut : 112
Explications
modifierDans ce programme, on déclare 3 variables a, b et s. On demande à l'utilisateur du programme de taper la valeur de a puis la valeur de b. cout sert à l'affichage à l'écran et cin à la saisie au clavier. Le programme calcule ensuite dans la variable s la somme a+b. On affiche finalement la valeur de s.
Affectation à la déclaration
modifierL'affectation à la déclaration d'une variable est appelée "déclaration avec initialisation", car est un cas particulier de l'affectation (en particulier avec les objets).
Exemple :
int a = 57;
Affectation en série
modifierLe résultat d'une même expression peut être assigné à plusieurs variables sans la réévaluer, en une seule instruction :
identificateur2=identificateur1=expression
Par exemple :
a = b = 38 + t;
En fait, l'affectation retourne une valeur : celle affectée à la variable. L'exemple précédent est donc équivalent à :
a = (b = 38 + t);
L'opération se déroule de la manière suivante :
- Le résultat de
38+t
est calculé ; - Il est affecté à la variable
b
; - Il est retourné par l'opérateur d'affectation ;
- Cette valeur retournée est affectée à la variable
a
.
Remarque importante
modifierPour effectuer un test de comparaison, par exemple comparer a à 53, il ne faut pas écrire if(a=53) mais if(a==53) en utilisant 2 fois le symbole =. Une erreur classique !
Opérations arithmétiques
modifiersur les entiers
modifierOn peut effectuer les opérations arithmétiques usuelles sur les entiers en utilisant les opérateurs +, -, / et *. Il faut juste avoir en tête que la division sur les entiers effectue une troncature (la partie décimale est perdue). Le modulo s'obtient en utilisant l'opérateur %. Ainsi a%b désigne le reste de la division de a par b. On peut utiliser les parenthèses pour fixer l'ordre d'évaluation des expressions. On peut aussi utiliser l'opérateur ++ pour incrémenter une variable de 1. L'opérateur --, quant à lui, décrémente une variable de 1.
sur les réels
modifierSur les réels, on utilise les opérateurs +, -, * et / pour effectuer les 4 opérations de base. Il faut avoir en tête que toute opération sur les réels est entâchée d'une minuscule erreur de calcul : il s'agit d'un arrondi sur le dernier bit. Si on effectue de nombreux calculs, cette erreur peut s'amplifier et devenir extrêmement grande.
Les fonctions mathématiques habituelles sont présentes dans la bibliothèque standard <cmath>.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a, b, M;
cout << "Tapez la valeur de a : "; cin >> a;
cout << "Tapez la valeur de b : "; cin >> b;
M = (a+3) * (6+a) + (b-5) * 2;
cout << "M vaut " << M << endl;
return 0;
}
Exécution :
modifierTapez la valeur de a : 1 Tapez la valeur de b : 2 M vaut 22
Opérations binaires
modifierLes opérations présentées dans cette section opèrent au niveau des bits.
Les décalages
modifierLes décalages permettent de décaler vers la droite ou vers la gauche toute la représentation en binaire d'une valeur d'un certain nombre de bits.
Syntaxe | Sémantique |
---|---|
valeur << decalage
|
Décale la valeur de decalage bits vers la gauche.
|
valeur >> decalage
|
Décale la valeur de decalage bits vers la droite.
|
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a = 5;
int b;
b = a << 3;
cout<<"b vaut : "<<b<<endl;
b = (a+1) << 3;
cout<<"b vaut : "<<b<<endl;
return 0;
}
Exécution
modifierb vaut 40 b vaut 48
Le ET binaire
modifierSyntaxe
modifierexp1 & exp2
Sémantique
modifierLe résultat est obtenu en faisant un ET logique sur chaque bit de la représentation de exp1 avec le bit correspondant de la représentation de exp2. Le ET logique a pour résultat 1 si les deux bits correspondants en entrée sont à 1 (pour 1-1), et sinon 0 (pour 0-1, 1-0 ou 0-0).
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a = 53;
int b = 167;
int c = a & b;
cout << "c vaut " << c << endl;
return 0;
}
Exécution
modifierc vaut 37
Explications
modifier53 s'écrit en binaire 0000 0000 0000 0000 0000 0000 0011 0101 167 s'écrit en binaire 0000 0000 0000 0000 0000 0000 1010 0111 ET binaire 0000 0000 0000 0000 0000 0000 0010 0101 ==> le résultat vaut 37 en décimal
Le OU binaire
modifierSyntaxe
modifierexp1 | exp2
Sémantique
modifierLe résultat est obtenu en faisant un OU logique sur chaque bit de la représentation de exp1 avec le bit correspondant de la représentation de exp2. Le OU logique a pour résultat 1 si l'un des deux bits correspondant en entrée est à 1 (pour 1-1, 1-0 ou 0-1), et sinon 0 (pour 0-0).
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a = 53;
int b = 167;
int c = a | b;
cout << "c vaut " << c << endl;
return 0;
}
Exécution
modifierc vaut 183
Explications
modifier53 s'écrit en binaire 0000 0000 0000 0000 0000 0000 0011 0101 167 s'écrit en binaire 0000 0000 0000 0000 0000 0000 1010 0111 OU binaire 0000 0000 0000 0000 0000 0000 1011 0111 ==> le résultat vaut 183 en décimal
Le OU exclusif binaire
modifierSyntaxe
modifierexp1 ^ exp2
Sémantique
modifierLe résultat est obtenu est faisant un OU exclusif sur chaque bit de la représentation de exp1 avec le bit correspondant de la représentation de exp2. Le OU exclusif logique a pour résultat 1 si les deux bits correspondant en entrée sont différents (pour 1-0 ou 0-1), et sinon 0 (pour 1-1 ou 0-0).
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a = 53;
int b = 167;
int c = a ^ b;
cout << "c vaut " << c << endl;
return 0;
}
Exécution
modifierc vaut 146
Explications
modifier53 s'écrit en binaire 0000 0000 0000 00000 0000 0000 0011 0101 167 s'écrit en binaire 0000 0000 0000 00000 0000 0000 1010 0111 OU exclusif binaire 0000 0000 0000 00000 0000 0000 1001 0010 ==> le résultat vaut 146 en décimal
Le NON binaire
modifierSyntaxe
modifier~exp
Sémantique
modifierLe résultat est obtenu en inversant tous les bits de la représentation de exp. Le 1 devient 0 et vice versa.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a = 53;
int b = ~a;
cout << "b vaut " << b << endl;
return 0;
}
Exécution
modifierb vaut -54
Explications
modifier53 s'écrit en binaire 0000 0000 0000 0000 0000 0000 0011 0101 NON binaire 1111 1111 1111 1111 1111 1111 1100 1010 ==> le résultat vaut -54 en décimal (signed int) ou 4294967242 (unsigned int)
Opérations booléennes
modifierComparaisons usuelles
modifierLes comparaissons usuelles s'effectuent selon la syntaxe a symbole b, symbole désignant le test effectué :
- Le symbole
>
désigne le test strictement supérieur à. - Le symbole
>=
désigne le test supérieur ou égal à. - Le symbole
<
désigne le test strictement inférieur à. - Le symbole
<=
désigne le test inférieur ou égal à. - Le symbole
==
désigne le test d'égalité. - Le symbole
!=
désigne le test différent.
Le ET logique
modifierSyntaxe
modifiercondition1 && condition2
Sémantique
modifierLe ET logique est vrai si à la fois les condition1 et condition2 sont vraies . Il est faux dans le cas contraire.
Attention : condition2 est évalué uniquement si condition1 a la valeur true
(différente de zéro). Cette évaluation élimine l’évaluation inutile de condition2 puisque l’expression estfalse de par le fait que condition1 est false. Il s’agit d' une évaluation de court-circuit .
Le OU logique
modifierSyntaxe
modifiercondition1 || condition2
Sémantique
modifierLe OU logique est vrai si au moins une des 2 conditions condition1 ou condition2 est vraie . Il est faux dans le cas contraire.
Attention : Le conditions2 est évalué uniquement si condition1 a la valeur false.
. Il s’agit là aussi d' une évaluation de court-circuit .
Le NON logique
modifierSyntaxe
modifier!(condition)
Sémantique
modifierLe NON logique inverse la valeur de condition: si condition vaut true
le résultat vaut false
. Si condition vaut false
le résultat vaut true
.
Exemples d'utilisation des opérateurs booléens
modifierbool b;
int u = 18;
b = !( (u>20 || (u<0)));
b
vaut true
.
bool b = ! false ; // -> b vaut true
b = ! true ; // -> b vaut false
Affectation avec opérateur
modifierIl existe toute une gamme d'opérateurs permettant d'effectuer une opération (une addition par exemple) avec le contenu d'une variable et de mettre le résultat dans cette même variable. Ainsi il sera plus pratique d'écrire b += a ;
que d'écrire b = b + a ;
.
Une opération du type
a opérateur= expression ;
équivaut à :
a = a opérateur (expression) ;
L'opérateur +=
modifierSyntaxe
modifieridentificateur+=expression
Sémantique
modifierCet opérateur ajoute à la variable identificateur la valeur de expression et stocke le résultat dans la variable identificateur.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int a = 80;
int b = 70;
b += a;
cout << "La valeur de b est : " << b << endl;
return 0;
}
Exécution
modifierLa valeur de b est 150
Autres opérateurs
modifierSur le même modèle, on peut utiliser les opérateurs suivants : -=, *=, /=, %=, >>=,<<=, &=, |= et ^=.
Priorité des opérateurs
modifierComme en mathématique, tous les opérateurs n'ont pas la même priorité.
Par exemple, l'expression 1+2*3
retournera la valeur 7
car l'opérateur *
a une plus grande priorité et est évalué avant l'opérateur +
. L'expression est donc équivalent à 1+(2*3)
.
Les parenthèses permettent de modifier les priorités en encadrant ce qu'il faut évaluer avant. Ainsi l'expression (1+2)*3
retournera la valeur 9
.
La liste complète des opérateurs du C++
modifierLa liste ci-dessous présente les différents opérateurs du C++ avec leur associativité dans l'ordre de leur priorité (du premier évalué au dernier). Les opérateurs situés dans le même bloc ont la même priorité. Les opérateurs en rouge ne peuvent être surchargés.
Opérateurs | Description | Associativité |
::
|
Sélection d'un membre d'un espace de nom, ou d'un membre statique d'une classe | de gauche à droite |
()
|
Parenthèses pour évaluer en priorité Tableau | |
++ --
|
Incrémentation post ou pré-fixée Opérateur moins unaire |
de droite à gauche |
.*
|
Déréférencement d'un pointeur de membre d'un objet Déréférencement d'un pointeur de membre d'un objet pointé |
de gauche à droite |
* / %
|
Multiplication, division, et modulo (reste d'une division) | |
+ -
|
Addition et soustraction | |
<< >>
|
Décalage de bits vers la droite ou vers la gauche | |
< <=
|
Comparaison “ inférieur strictement ” et “ inférieur ou égal ” Comparaison “ supérieur strictement ” et “ supérieur ou égal ” | |
== !=
|
Condition “ égal ” et “ différent ” | |
&
|
ET logique bit à bit | |
^
|
OU exclusif bit à bit | |
|
|
OU logique bit à bit | |
&&
|
ET logique booléen | |
||
|
OU logique booléen | |
c?t:f
|
Opérateur ternaire de condition | de droite à gauche |
=
|
Affectation Affectation avec somme ou soustraction | |
,
|
Séquence d'expressions | de gauche à droite |
Les entrées-sorties
Classes de gestion des flux
modifierLes entrées et sorties sont gérées par deux classes définies dans le fichier d'en-tête <iostream>
:
ostream
(Output stream) permet d'écrire des données vers la console, un fichier, ... Cette classe surdéfinit l'opérateur<<
.istream
(Input stream) permet de lire des données à partir de la console, d'un fichier, ... Cette classe surdéfinit l'opérateur>>
.
Flux standards
modifierTrois instances de ces classes représentent les flux standards :
cout
écrit vers la sortie standard,cerr
écrit vers la sortie d'erreur,clog
écrit vers la sortie technique,cin
lit à partir de l'entrée standard (jusqu'au premier espace exclu, éventuellement). Demander un nombre et y entrer des lettres provoque une erreur.getline
lit à partir de l'entrée standard (tout).
Ces objets sont définis dans l'espace de nom std
.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int n;
cout << "Entrez un nombre positif : ";
cin >> n;
if (n<0) cerr << "Erreur: Le nombre " << n
<< " n'est pas positif " << endl;
else cout << "Vous avez entré " << n << endl;
return 0;
}
Autres types de flux
modifierLes instances des classes dérivées des classes istream
et ostream
sont également manipulés avec les opérateurs <<
et >>
.
Cependant, il ne faut pas oublier de les fermer en appelant la méthode close()
.
Note: Les noms de fichiers sont codés sur 8 bits sous Linux/Unix et sur 16 bits sur Windows, ce qui peut induire des problèmes de portabilité, le cas échéant.
Flux de fichier
modifierLa classe ifstream
permet de lire à partir d'un fichier.
Le constructeur a la syntaxe suivante :
ifstream(const char* filename, openmode mode=in)
Le paramètre mode
peut être une combinaison des valeurs suivantes :
app
- (append) Placer le curseur à la fin du fichier avant écriture.
ate
- (at end) Placer le curseur à la fin du fichier.
binary
- Ouvrir en mode binaire plutôt que texte.
in
- Autoriser la lecture.
out
- Autoriser l'écriture.
trunc
- (truncate) Tronquer le fichier à une taille nulle.
Exemple 1 : lire un entier depuis un fichier
ifstream fichier("test.txt");
int a;
fichier >> a; // lire un entier
cout << "A = " << a;
fichier.close();
Exemple 2 : afficher tous les caractères d'un fichier
ifstream fichier("test.txt");
while (fichier.good())
cout << (char) fichier.get();
fichier.close();
La classe ofstream
permet d'écrire vers un fichier. Son constructeur a une syntaxe similaire :
ofstream(const char* filename, openmode mode=out|trunc)
Exemple :
ofstream fichier("test.txt");
fichier << setw(10) << a << endl;
fichier.close();
La classe fstream
dérive de la classe iostream
permettant à la fois la lecture et l'écriture.
Cette dernière (iostream
) dérive donc à la fois de la classe ostream
et de la classe istream
.
Son constructeur a la syntaxe suivante :
fstream(const char* filename, openmode mode=in|out)
Exemple :
fstream fichier("test.txt");
fichier << setw(10) << a << endl;
fichier.seekg(0, ios_base::beg);
fichier >> b;
fichier.close();
Flux de chaîne de caractères
modifierCes flux permettent d'écrire pour produire une chaîne de caractères, ou de lire à partir d'une chaîne de caractères.
La classe istringstream
dérivée de istream
permet de lire à partir d'une chaîne de caractères, et possède deux constructeurs :
istringstream ( openmode mode = in );
istringstream ( const string & str, openmode mode = in );
Exemple :
int n, val;
string stringvalues;
stringvalues = "125 320 512 750 333";
istringstream iss (stringvalues, istringstream::in);
for (n = 0; n < 5; n++)
{
iss >> val;
cout << val << endl;
}
La classe ostringstream
dérivée de ostream
permet d'écrire pour créer une chaîne de caractères, et possède également deux constructeurs :
ostringstream ( openmode mode = out );
ostringstream ( const string & str, openmode mode = out );
Le second permet de spécifier le début de la chaîne de caractères produite.
La méthode str()
retourne la chaîne de caractères produite.
Exemple :
ostringstream oss (ostringstream::out);
int a = 100;
oss << "Test d'écriture a=" << a << "\n";
cout << oss.str();
La classe stringstream
dérivée de iostream
permet d'écrire et lire, et possède deux constructeurs :
stringstream ( openmode mode = in | out );
stringstream ( const string & str, openmode mode = in | out );
Exemple :
int n, val;
stringstream ss (stringstream::in | stringstream::out);
// écriture
ss << "120 42 377 6 5 2000";
// lecture
for (int n = 0; n < 6; n++)
{
ss >> val;
cout << val << endl;
}
Manipulateurs
modifierLe fichier d'en-tête <iomanip>
définit des manipulateurs de flux tels que endl
, hex
.
Ces manipulateurs modifient la façon d'écrire ou lire les données qui suivent celui-ci.
Manipulateur endl
modifier
Ce manipulateur écrit un retour à la ligne dans le flux, quel qu'il soit (\r\n
pour Windows, \n
pour Unix/Linux, \r
pour Mac, ...).
Il est donc conseillé de l'utiliser au lieu du/des caractère(s) correspondant(s), si la portabilité de votre application joue un rôle important.
Exemple:
cout << "Une première ligne" << endl << "Une deuxième ligne" << endl;
N.B.: Certains compilateurs C++ (notamment Visual C++) ne supporte pas que le manipulateur endl
soit suivi d'autres données à écrire. Dans ce cas, il faut écrire les données suivantes dans une nouvelle instruction :
cout << "Une première ligne" << endl;
cout << "Une deuxième ligne" << endl;
Manipulateur hex
modifier
Ce manipulateur indique que les prochains entiers sont à lire ou écrire en base hexadécimale.
Manipulateur dec
modifier
Ce manipulateur indique que les prochains entiers sont à lire ou écrire en base décimale.
Manipulateur setbase(base)
modifier
Les 2 manipulateurs précédents sont des alias de celui-ci, qui permet de spécifier la base des prochains entiers à lire ou écrire.
Exemple :
int a = 200; // 200 en décimal
cout << "Valeur de a en base 16 = " << setbase(16) << a << endl;
// affiche: Valeur de a en base 16 = C8
cout << "Valeur de a en base 10 = " << setbase(10) << a << endl;
// affiche: Valeur de a en base 10 = 200
cout << "Valeur de a en base 8 = " << setbase(8) << a << endl;
// affiche: Valeur de a en base 8 = 310
Manipulateur setw(width)
modifier
Ce manipulateur indique que les prochaines données doivent être écrites sur le nombre de caractères indiqué, en ajoutant des caractères espaces avant.
Exemple :
int a = 11;
cout << "Valeur de a = " << setw(5) << a << endl;
Ce code affiche :
Valeur de a = 11
Manipulateur setfill(char)
modifier
Ce manipulateur modifie le caractère utilisé pour compléter les données utilisant le manipulateur setw
.
Exemple :
int a = 11;
cout << "Valeur de a = " << setfill('x') << setw(5) << a << endl;
Ce code affiche :
Valeur de a = xxx11
Manipulateur setprecision(digits)
modifier
Ce manipulateur spécifie que les prochains nombres à virgule flottante doivent être écrits avec la précision donnée. La précision donne le nombre maximum de chiffres à écrire (avant et après la virgule).
Exemple :
double f = 3.14159;
cout << setprecision (5) << f << endl;
cout << setprecision (9) << f << endl;
Ce code affiche :
3.1416 3.14159
Manipulateurs setiosflags
et resetiosflags
modifier
Le manipulateur setiosflags
(resp. resetiosflags
) active (resp. désactive) des options de format des données.
Ces deux manipulateurs possèdent un argument dont le type est défini par l'énumération ios_base::fmtflags
.
Cet argument peut être :
ios_base::boolalpha
- Écrire/lire les données de type
bool
sous forme textuelle, càdtrue
oufalse
. ios_base::oct
- Écrire/lire les entiers en base octale (base 8).
ios_base::dec
- Écrire/lire les entiers en base décimale (base 10).
ios_base::hex
- Écrire/lire les entiers en base hexadécimale (base 16).
ios_base::showbase
- Faire précéder les entiers par leur base.
ios_base::showpos
- Faire précéder les nombres positifs du signe plus (
+
). ios_base::showpoint
- Toujours écrire la virgule des nombres réels.
ios_base::fixed
- Écrire les nombres réels avec une virgule fixe.
ios_base::scientific
- Écrire les nombres réels sous forme scientifique.
ios_base::left
- Aligner les donnés à gauche (setw).
ios_base::right
- Aligner les donnés à droite (setw).
ios_base::internal
- Aligner les donnés en remplissant à une position interne (setw).
ios_base::skipws
- Ignorer les caractères blancs avant de lire les données.
ios_base::unitbuf
- Vider le buffer de sortie à chaque écriture.
ios_base::uppercase
- Écrire les données en majuscules.
Les pointeurs
Introduction
modifierUne variable correspond à un emplacement en mémoire (adresse) où se trouve une valeur. Toute variable a permet d'accéder :
- à sa valeur en lecture et en écriture :
int a;
a = 10; // écriture de la valeur de a
cout << "A vaut " << a ; // lecture de la valeur de a
- à son adresse en lecture seulement car l'adresse (l'emplacement mémoire) est choisie par le système :
cout << "L'adresse de A est " << &a ; // lecture de l'adresse de a
Un pointeur désigne un type particulier de variable dont la valeur est une adresse. Un pointeur permet donc de contourner la restriction sur le choix de l'adresse d'une variable, et permet essentiellement d'utiliser la mémoire allouée dynamiquement.
Il est utilisé lorsque l'on veut manipuler les données stockées à cette adresse. C'est donc un moyen indirect de construire et de manipuler des données souvent très complexes.
Déclaration
modifiertype* identificateur;
La variable identificateur est un pointeur vers une valeur de type type.
L'opérateur &
modifierC'est l'opérateur d'indirection. Il permet d'obtenir l'adresse d'une variable, c'est-à-dire un pointeur vers cette variable.
&identificateur // permet d'obtenir l'adresse mémoire de la variable identificateur
Il renvoie en réalité une adresse mémoire, l'adresse où est stockée physiquement la variable identificateur.
L'opérateur *
modifier*variable
C'est l'opérateur de déréférencement. Il permet d'obtenir et donc de manipuler les données pointées par la variable variable. Ainsi *pointeur
permet d'accéder à la valeur pointée par pointeur en lecture et en écriture.
Comparaison avec une variable classique
modifierint a;
int* pA;
pA = &a; // l'adresse de a est stockée dans pA
écriture de la valeur de a |
a = 10;
|
*pA = 10;
|
lecture de la valeur de a |
cout << "A vaut " << a ;
|
cout << "A vaut " << *pA ;
|
lecture de l'adresse de a |
cout << "L'adresse de A est " << &a ;
|
cout << "L'adresse de A est " << pA ;
|
Le pointeur pA
peut par la suite pointer l'adresse d'une autre variable, où bien pointer l'adresse d'un bloc de mémoire alloué dynamiquement.
Exemple de programme
modifier#include <iostream>
using namespace std;
int main()
{
int a, b, c;
int *x, *y;
a = 98;
x = &a;
c = *x + 5;
y = &b;
*y = a + 10;
cout << "La variable b vaut : " << b << endl;
cout << "La variable c vaut : " << c << endl;
return 0;
}
Exécution
modifierLa variable b vaut 108 La variable c vaut 103
Explications
modifier- Dans ce programme, on déclare 3 variables a, b et c. On déclare ensuite 2 pointeurs vers des entiers x et y.
- a est initialisé à 98.
x=&a;
permet de mettre dans x l'adresse de a. x est désormais un pointeur vers a.*x
est la variable pointée par x, c'est-à-dire a, qui vaut donc 98 après évaluation.
c=*x+5;
permet donc de transférer 98+5 donc 103 dans la variable c.
y=&b;
permet de mettre dans la variable y l'adresse de la variable b. y est désormais un pointeur vers b.
- a+10 vaut 98+10 donc 108.
*y=a+10;
permet de transférer dans la variable pointée par y la valeur de a+10, c'est-à-dire 108. On stocke donc 108 dans b, de manière indirecte via le pointeur y.- on affiche ensuite les valeurs de b et c c'est-à-dire respectivement 108 et 103.
Opérations arithmétiques sur les pointeurs
modifierHormis l'opérateur de déréférencement, les pointeurs peuvent être utilisés avec l'opérateur d'addition ( + ). L'addition d'un pointeur avec une valeur entière permet d'avancer ou reculer le pointeur du nombre d'éléments indiqué.
Exemple 1
modifierchar* ptr="Pointeur"; // ptr pointe le premier caractère de la chaîne de caractères
cout << ptr << endl; // affiche "Pointeur"
ptr = ptr+3;
cout << ptr << endl; // affiche "nteur"
cout << ++ptr << endl; // affiche "teur"
cout << --ptr << endl; // affiche "nteur"
Comme l'exemple précédent le montre, il est également possible d'utiliser les opérateurs d'incrémentation et de décrémentation.
Exemple 2
modifierint premiers[] = { 2, 3, 5, 7, 11, 13, 17 };
int* ptr = premiers; // pointe le premier élément du tableau
cout << hex << ptr << endl; // affiche l'adresse pointée (par exemple 01C23004)
cout << *ptr << endl; // affiche "2"
cout << *(ptr+5) << endl; // affiche "13"
ptr=&premiers[3]; // pointe le 4e élément (index 3)
cout << hex << ptr << endl; // affiche l'adresse pointée (par exemple 01C23018)
cout << *ptr << endl; // affiche "7"
cout << *(ptr-1) << endl; // affiche "5"
Dans l'exemple 2, la différence d'adresse est de 20 octets (5 * sizeof(int)
).
Cela montre que l'adresse contenue dans le pointeur est toujours incrémentée ou décrémentée d'un multiple de la taille d'un élément (sizeof *ptr
).
Opération utilisant deux pointeurs
modifierLa seule opération valable (ayant un sens) utilisant deux pointeurs de même type est la soustraction ( -
) donnant le nombre d'éléments entre les deux adresses.
Elle n'a de sens que si les deux pointeurs pointent dans le même tableau d'éléments.
Exemple :
char str[]="Message où rechercher des caractères.";
char *p1 = strchr(str,'a'); // recherche le caractère 'a' dans str
char *p2 = strchr(str,'è'); // recherche le caractère 'è' dans str
cout << "Nombre de caractères de 'a' à 'è' = " << (p2-p1) << endl;
// affiche : Nombre de caractères de 'a' à 'è' = 28
Pointeur constant et pointeur vers valeur constante
modifierIl ne faut pas confondre un pointeur constant (qui ne peut pointer ailleurs) avec un pointeur vers une valeur constante (l'adresse contenue dans le pointeur peut être modifiée mais pas la valeur pointée).
Dans les 2 cas le mot clé const
est utilisé, mais à 2 endroits différents dans le type de la variable.
Pointeur vers une valeur constante
modifierLe mot-clé const
placé avant le type du pointeur permet d'empêcher la modification de la valeur pointée.
Exemple:
const char* msg = "essai constant";
*msg = 'E'; // <- Interdit, la valeur pointée ne peut être modifiée
msg = "Test"; // OK -> le pointeur contient l'adresse de "Test"
Pointeur constant
modifierLe mot-clé const
placé entre le type du pointeur et le nom de la variable permet d'empêcher la modification du pointeur lui-même (l'adresse).
Exemple:
char* const msg = "essai constant";
msg = "Test"; // <- Interdit (erreur de compilation),
// ne peut pointer la chaîne "Test"
*msg = 'E'; // OK -> "Essai constant"
Pointeur constant vers une valeur constante
modifierLe mot-clé const
apparaissant aux 2 endroits empêche à la fois la modification du pointeur lui-même et celle de la valeur pointée.
Exemple:
const char* const msg = "essai constant";
msg = "Test"; // <- Interdit (erreur de compilation),
// ne peut pointer la chaîne "Test"
*msg = 'E'; // <- Interdit, la valeur pointée ne peut être modifiée
Position du mot clé const
modifier
Une méthode simple a été proposée par Dan Sacks pour déterminer si c'est la valeur pointée ou le pointeur lui-même qui est constant. Elle se résume en une seule phrase mais n'a jamais été intégrée dans les compilateurs :
"const
s'applique toujours à ce qui le précède".
Par conséquent, une déclaration ne commencera jamais par const
qui ne serait précédé de rien et qui ne pourrait donc s'appliquer à rien.
En effet, les deux déclarations suivantes sont strictement équivalentes en C++ :
const char * msg; // déclaration habituelle char const * msg; // déclaration Sacks
Cette méthode peut s'avérer précieuse dans le cas de déclarations plus complexes :
int const ** const *ptr; // ptr est un pointeur vers un pointeur constant de pointeur d'entier constant : // *ptr et ***ptr ne peuvent être modifiés
Voir aussi
modifier
Les pointeurs/Exercices
Exercices
modifierExercice 1
modifierÉcrire une fonction swap qui a comme paramètres deux pointeurs vers des entiers et qui échange le contenu des deux entiers pointés. Tester cette fonction en écrivant un programme qui échange le contenu de deux entiers a et b en appelant cette fonction.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation des pointeurs.
- Voici le fichier source :
#include<iostream>
using namespace std;
int swap(int *pa, int *pb)
{
int tmp;
tmp = *pa;
*pa = *pb;
*pb = tmp;
return 0;
}
int main()
{
int a, b;
cout << "Saisissez un entier : ";
cin >> a;
cout << "Saisissez un autre entier : ";
cin >> b;
cout << "Vous avez saisi : " << a << " " << b << endl;
swap(&a, &b);
cout << "Après swap : " << a << " " << b << endl;
return 0;
}
Exercice 2
modifierÉcrire une fonction qui a comme paramètres un tableau d'entiers de taille quelconque, la taille du tableau, et 2 pointeurs vers des entiers min et max. La fonction doit renvoyer dans les entiers pointés par min et max respectivement les plus petits et les plus grands entiers du tableau.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation des pointeurs.
- Voici le fichier source :
#include<iostream>
using namespace std;
int minmax(int *p, int n, int *pmin, int *pmax)
{
int *i(0);
*pmin=*p;
*pmax=*p;
for (i=p ; i<p+n ; i++)
{
if (*i < *pmin) *pmin = *i;
if (*i > *pmax) *pmax = *i;
}
return 0;
}
int main()
{
int tn[] = { 12, 23, 36, 5, 46, 9, 25 };
int min, max, r;
r = minmax(tn, sizeof(tn)/sizeof(int), &min, &max);
cout << "Min, Max : " << min << " " << max << endl;
return 0;
}
Exercice 3
modifierÉcrire une fonction qui a en paramètre une chaîne de caractères et qui renvoie par un return le nombre d'occurrences de la lettre 'A'. Cette fonction devra parcourir la chaîne en utilisant un pointeur. Tester cette fonction.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation des pointeurs.
- Voici le fichier source :
#include<iostream>
using namespace std;
int nba(char *p)
{
int n=0;
while (*p != '\0')
{
if (*p =='A') n++;
p++;
}
return n;
}
int main()
{
char ss[50];
cout << "Saisissez une chaine de caractères : " << endl;
cin.getline(ss, 50);
cout << "Vous avez saisi : " << ss << endl;
cout << "La chaine comporte " << nba(ss) << " A." << endl;
return 0;
}
Exercice 4
modifierÉcrire une fonction qui a comme prototype copy(char * ch1, char * & ch2). Cette fonction a comme paramètre en entrée une chaîne de caractères ch1 et une référence vers un pointeur ch2. Avant l'appel ch2 est un pointeur non initialisé. Après l'appel, ch2 pointe vers un nouveau tableau de char qui contient une copie de la chaîne ch1. Ce nouveau tableau de char aura la taille minimale nécessaire. Tester cette fonction.
Exercice 5
modifierRéécrire la fonction du 5 et le programme principal mais cette fois-ci le pointeur ch2 sera non plus passé par référence mais par pointeur. La fonction copy aura donc comme prototype :
copy(char * ch1, char * * ch2)
Exercices 6
modifierOn veut écrire un programme qui permet de gérer une liste de notes grâce à un tableau de taille variable. Une note sera définie par un nom (chaîne de 9 caractères utiles), un prénom (chaîne de 9 caractères utiles) et une valeur (un réel). Notre structure listeNotes (qui contient une liste de notes) sera caractérisée par un pointeur vers une note qui sera en fait un tableau de taille variable de notes, un entier nbmax qui sera la taille réelle du tableau, par un entier nb qui sera le nombre d'éléments contenus dans la liste et un entier inc qui sera la taille initiale du tableau. Au départ, le tableau fait inc cases et la liste et vide. On peut alors rajouter des éléments dans la liste. Lorsque le tableau devient trop petit, on augmente la taille du tableau de inc cases. Pour cela , il faudra réallouer de la place pour un nouveau tableau de notes plus grand que l'ancien. Il faudra ensuite copier les notes de l'ancien tableau dans le nouveau, détruire l'ancien tableau et faire pointer le tableau de notes de notre vers notre nouveau tableau. Notre tableau verra donc sa taille augmenter de inc cases à la fois au fur et à mesure des besoins. Lorsqu'on supprimera des notes dans le tableau et dès qu'il y aura inc cases de vide dans le tableau on réduira de la même manière la taille du tableau. Notre nouvelle structure listeNotes aura une gestion de la mémoire nettement meilleure qu'avec un tableau statique.
Notre liste de notes sera gérée par le menu suivant :
1. Ajouter une note. 2. Afficher une liste de notes. 3. Supprimer une note en tapant son nom et son prénom. 4. Afficher la moyenne des notes. 5. Quitter.
Il faudra veiller à ce qu'il n'y ait pas 2 notes avec le même nom et le même prénom dans la liste et il faudra trier la liste de notes d'abord par rapport au nom, ensuite par rapport au prénom au fur et à mesure des ajouts et des suppressions de notes. On veillera à bien décomposer ce problème en différents modules et à mener une réflexion sur les fonctions nécessaires dans chaque module.
Les références
Présentation des références
modifierUne référence peut être vue comme un alias d'une variable. C'est-à-dire qu'utiliser la variable, ou une référence à cette variable est équivalent. Ce qui signifie que l'on peut modifier le contenu de la variable en utilisant une référence.
Une référence ne peut être initialisée qu'une seule fois : à la déclaration. Toute autre affectation modifie en fait la variable référencée. Une référence ne peut donc référencer qu'une seule variable tout au long de sa durée de vie.
Déclaration
modifierLa déclaration d'une variable de type référence doit inclure son initialisation :
type& identificateur=variable; // Syntaxe d'initialisation des variables
// ou (strictement équivalent)
type& identificateur(variable); // Syntaxe d'initialisation des objets
Un paramètre de méthode ou de fonction de type référence est initialisé lors de l'appel à celle-ci :
type_retour nomFonctionOuMethode(type& identificateur)
{
// ...
}
// ...
nomFonctionOuMethode(variable);
Sémantique
modifierLa variable identificateur
est une référence vers la variable variable
. La variable variable
doit être de type type
.
Exemple de programme
modifier#include <iostream>
using namespace std;
int main()
{
int a = 98,
b = 78,
c;
int &x = a;
c = x + 5; // équivaut à : c = a + 5;
int &y = b;
y = a + 10; // équivaut à : b = a + 10;
cout << "La variable b vaut : " << b << endl;
cout << "La variable c vaut : " << c << endl;
return 0;
}
Exécution
modifierLa variable b vaut : 108 La variable c vaut : 103
Explications
modifier- Dans ce programme, on définit 3 variables entières
a
,b
etc
et on initialisea
à98
etb
à78
. int &x=a;
permet de déclarer une référencex
vers la variablea
.x+5
vaut donc la même chose quea+5
donc103
.c=x+5;
permet donc de transférer103
dans la variablec
.int &y=b;
permet de déclarer une référencey
vers la variableb
.a+10
vaut98+10
donc108
.y=a+10;
permet de transférer108
dans la variableb
.- on affiche ensuite
b
etc
c'est-à-dire respectivement108
et103
.
Pourquoi utiliser une référence ?
modifierC'est la question qui peut se poser en regardant l'exemple ci-dessous, où il serait plus clair d'utiliser directement des variables.
Les références sont principalement utilisées pour passer des paramètres aux fonctions. Voir le chapitre sur les fonctions, section « passage de paramètres par référence ».
Les références constantes sont également utilisées pour référencer des résultats de retour de fonctions afin d'éviter les copies. C'est particulièrement indiqué dans le cas d'objets retournés par des fonctions. Dans ce cas, la valeur ou objet temporaire retourné a une durée de vie aussi longue que la référence.
Exemple :
class Retour
{
public:
void g() const {}
};
Retour f() { return Retour(); }
int main(int argc, char *argv[])
{
const Retour &retour = f();
retour.g();
return 0;
}
Les références et leur lien avec les pointeurs
modifierUne référence est un pointeur que l'on ne peut pas réaffecter (car le compilateur l'interdit), qui se déréférence automatiquement (à l'inverse d'un pointeur pour lequel on doit utiliser l'opérateur d'indirection), et dont à l'inverse d'un pointeur on ne peut connaître l'adresse car le compilateur ne le permet pas. En effet, si v est une référence alors &v donnera l'adresse de l'objet référencé par v, et non l'adresse de la case mémoire où est stockée la référence.
Exercices
modifierExercice 1
modifierFaites une fonction dont la déclaration sera void échanger(int & a, int & b)
qui devra échanger les deux valeurs.
void échanger(int & a, int & b)
{
int c = a;
a = b;
b = c;
}
Exercice 2
modifierFaites une fonction pour calculer la factorielle d'un nombre. Sa déclaration sera int fact(int & n)
. La fonction sera récursive et la valeur de retour sera n. En cas de problèmes, consulter l'Aide 1.
La factorielle (notée "!") est une fonction mathématique.
Voici quelques exemple :
4! = 4 x 3 x 2 x 1 = 24
3! = 3 x 2 x 1 = 6
2! = 2 x 1 = 2
1! = 1
Notez que :
4! = 4 x 3!
3! = 3 x 2!
2! = 2 x 1! = 2 x 1
D'où :
!n = n x !(n-1) si n > 1
Voici un exemple de fonction récursive qui ne répond pas à la consigne d'avoir une déclaration int factorielle (int & n) et qui par conséquent ne peut être qualifiée de solution à l'exercice 2 :
#include <iostream>
using namespace std;
int factorielle(int n)
{
if(n == 1) return 1;
return n * factorielle(n-1);
}
int main(void)
{
int y = factorielle(2);
cout << "résultat : " << y << endl;
}
Une autre solution est (mais la fonction retourne factoriel n, pas n) :
#include <iostream>
using namespace std;
int factorielle(int& n)
{
if(n == 1) return 1;
n--;
return (n+1) * factorielle(n);
}
int main(void)
{
int n = 2;
int y = factorielle(n);
cout << "résultat : " << y << endl;
}
Une autre solution :
#include <iostream>
int fact(int & n)
{
if (n == 0)
{
n = 1;
return 0;
}
else if (n == 1) return 1;
else
{
int t = n-1;
int retVal = fact(t);
n = t * n;
return n/t;
}
}
main()
{
int v = 4;
int res = fact(v);
std::cout << res << " et sa factorielle :" << v << std::endl;
}
Tests
modifierTest 1
modifierIndiquez si la syntaxe est correcte ou non.
Cas 1
modifierint b = n;
int & ref = b;
Cas 2
modifierint x = 5;
int & var = x;
Cas 3
modifierint n = 2;
int & ref = n;
if (*(ref) == 2) ref++; //ceci provoque une erreur car ref n'est pas un pointeur
Cas 4
modifier#include <iostream>
using namespace std;
void afficher_par_reference(int & a)
{
cout << a << endl;
}
Cas 5
modifierint b = 2;
int ref& = b;
Solution
modifier- vrai
- vrai
- faux
- vrai
- faux
Test 2
modifierDans ces exemples, trouvez ce que le programme va afficher.
Cas 1
modifier#include <iostream>
using namespace std;
int main()
{
int b = 2;
int a = 4;
int & ref1 = b;
int & ref2 = a;
ref2 += ref1;
ref1 -= ref2;
cout << ref2 << " " << ref1 << endl;
}
6 -4
Les tableaux
Les tableaux à une dimension
modifierLes tableaux sont des structures de données constituées d'un certain nombre d'éléments de même type. On peut accéder directement à un élément du tableau en indiquant son indice entre crochets (indice de 0 à nombre_d_éléments-1).
Les tableaux statiques
modifierSyntaxe
modifiertype identificateur[taille];
Sémantique
modifieridentificateur
est un tableau de taille
éléments de type type
. taille
est obligatoirement une valeur constante. La taille du tableau est donc figée une fois pour toute et ne peut pas être modifiée en cours d'exécution.
Pour désigner la i-ième case de tableau, on écrit
identificateur[i]
où i
est un entier quelconque. Les cases sont numérotées à partir de 0 : les valeurs possibles de i vont donc de 0
à taille-1
.
Pointeurs et tableaux
modifierIl existe un lien entre les pointeurs et les tableaux : identificateur est en fait un pointeur constant vers un élément de type type, pointant le premier élément du tableau, en d'autres termes le nom d'un tableau est un pointeur constant sur le premier élément du tableau.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int i;
int const tailleTableau(10); //taille du tableau
int tableau[tailleTableau]; //déclaration du tableau
for (int i(0); i<tailleTableau; i++ )
{
tableau[i]=i*i;
cout<<"Le tableau ["<<i<<"] contient la valeur "<<tableau[i]<<endl;
}
return 0;
}
Exécution
modifierLe tableau [0] contient la valeur 0
Le tableau [1] contient la valeur 1
Le tableau [2] contient la valeur 4
Le tableau [3] contient la valeur 9
Le tableau [4] contient la valeur 16
Le tableau [5] contient la valeur 25
Le tableau [6] contient la valeur 36
Le tableau [7] contient la valeur 49
Le tableau [8] contient la valeur 64
Le tableau [9] contient la valeur 81
Les tableaux dynamiques
modifierUn tableau dynamique est un tableau dont le nombre de cases peut varier au cours de l'exécution du programme. Il permet d'ajuster la taille du tableau au besoin du programmeur.
L'opérateur new
modifierSyntaxe :
pointeur=new type[taille];
L'opérateur new[]
permet d'allouer une zone mémoire pouvant stocker taille éléments de type type, et retourne l'adresse de cette zone. Le paramètre taille est un entier qui peut être quelconque (variable, constante, expression). new
renverra un pointeur vers un type. La variable pointeur est donc du type type *. Les cases du tableau seront numérotées de 0 à taille-1 et on y accédera comme un tableau statique. S'il n'y a pas assez de mémoire disponible, new
renvoie le pointeur NULL
.
L'opérateur delete[]
modifier
Syntaxe :
delete[] pointeur;
Cette utilisation de delete[]
permet de détruire un tableau précédemment alloué grâce à new[]
.
N'oubliez surtout pas les crochets juste après delete
, sinon le tableau ne sera pas correctement libéré.
Le programmeur en C++ doit gérer la destruction effective des tableaux qu'il a créé dynamiquement.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int i, taille;
cout << "Tapez la valeur de taille : ";
cin >> taille;
int *t;
t = new int[taille];
for (i = 0; i < taille; i++)
t[i] = i * i;
for (i = 0; i < taille; i++)
cout << t[i] << endl;
delete[] t;
return 0;
}
Exécution 1
modifierTapez la valeur de taille : 4 0
1
4
9
Exécution 2
modifierTapez la valeur de taille : 6 0 1 4 9 16 25
Tableaux multidimensionnels
modifierUn tableau peut avoir plus d'une dimension (matrice 2D, matrice 3D, ..). Par contre, on ne peut pas utiliser l'expression suivante pour l'allocation dynamique de tableaux à plusieurs dimensions:
pointeur=new type[taille1][taille2]...;
L'allocation dynamique de tableaux à deux dimensions est expliquée ci-dessous.
Les vectors
modifierLes vectors (vecteur en français) sont un type de tableaux dynamiques très puissants qui suivent la syntaxe suivante :
vector<type> nom(taille);
Par exemple, un tableau dynamique constitué de 5 entiers et nommé « tableau_nombre_entiers » sera défini de la sorte :
vector<int> tableau_nombres_entiers(5);
Mais on peut bien sûr définir un tableau qui ne comporte aucune taille, ce qui est bien utile pour un tableau... dynamique !
vector<int> tableau_nombres;
Pour accéder aux valeurs d'un vector, on procède la même manière que pour tous les tableaux, par exemple :
tableau_nombres_entiers[0] = 0;
tableau_nombres_entiers[1] = 2;
...
Mais on peut également affecter à tout le vector une même valeur en l'indiquant juste après la taille avec une virgule. Exemple :
#include <iostream>
#include <string> //ATTENTION : PENSER À INCLURE "STRING"
using namespace std;
vector<int> tableau_nombre_entiers(5, 10); //Ce tableau est composé de 5 nombres entiers qui sont tous égaux à 10
vector<string> tableau_lettres(4, "bonjour !"); //Ce tableau est composé de 4 chaînes de caratères qui sont égales à « bonjour ! »
Cela signifie que tableau_nombre_entiers[0]=tableau_nombre_entiers[1]=tableau_nombre_entiers[2]=tableau_nombre_entiers[3]=tableau_nombre_entiers[4]=10 ; et de même tableau_lettres[0]=tableau_lettres[1]=tableau_lettres[2]=tableau_lettres[3]="bonjour !".
Comme on le sait, les vectors sont des tableaux dynamiques, ainsi peuvent-ils s'agrandir ou se rétrécir. Pour ajouter une case supplémentaire au tableau, il suffit de faire appel à la fonction push_back() :
vector<int> tableau_entiers;
tableau_entiers.push_back(7); //On ajoute une première case au tableau qui comporte le nombre 7
tableau_entiers.push_back(18); //On ajoute une deuxième case au tableau qui comporte le nombre 18
...
À l'inverse, pour supprimer une case, on utilise la fonction pop_back() :
vector<int> tableau_entiers(10); //Un tableau dynamique d'entiers à 10 cases
tableau_entiers.pop_back(); //Plus que 9 cases
tableau_entiers.pop_back(); //Plus que 8 cases
...
Enfin, pour récupérer la taille du vector, on utilise la fonction size() :
vector<int> tableau_entiers(5); //Un tableau de 5 entiers
int taille = tableau_entiers.size(); //'taille' a pour valeur 5
Parcours d'un tableau
modifierLe parcours des éléments d'un tableau se fait généralement avec une boucle for
(on peut aussi utiliser while
ou do...while
).
Exemple :
int nombres[10] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
for (int i = 0; i < 10; i++) // i va de 0 à 9 inclus
cout << "nombres [ " << i << " ] = " << nombres[i] << endl;
Les éléments d'un tableau sont stockés dans des zones contigües. Il est donc possible d'utiliser un pointeur pour parcourir un tableau :
int nombres[10] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
int *p = nombres;
for (int i = 0; i < 10; i++, p++)
cout << "Nombre : " << *p << endl;
Initialement le pointeur p
pointe le premier élément du tableau.
L'instruction p++
dans la boucle for
incrémente le pointeur, c'est-à-dire qu'il passe à l'élément suivant. L'adresse contenue dans le pointeur n'augmente pas de 1, mais de la taille de l'élément pointé (ici int
, soit 4 octets en général).
Les tableaux à deux dimensions
modifierStatiques
modifier- Syntaxe
type identificateur[taille_i][taille_j];
- Sémantique :
identificateur est un tableau de taille_i sur taille_j cases. Pour chaque case i choisie, il y a taille_j cases disponibles. Les taille_i et taille_j ne peuvent changer au cours du programme. Pour le faire, il faut utiliser les tableaux dynamiques.
Exemple
modifier#include <iostream>
using namespace std;
int main() {
int t[10][5];
for (int i = 0; i < 10; i++)
for (int j = 0; j < 5; j++)
t[i][j] = i * j;
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 5; ++j){
cout << t[i][j] << " ";
}
cout << endl;
}
return 0;
}
Exécution
modifier0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16 0 5 10 15 20 0 6 12 18 24 0 7 14 21 28 0 8 16 24 32 0 9 18 27 36
Dynamiques
modifierExemple
modifier#include <iostream>
using std::cout;
using std::cin;
int **t;
int nColonnes;
int nLignes;
void Free_Tab(); // Libérer l'espace mémoire;
int main(){
cout << "Nombre de colonnes : "; cin >> nColonnes;
cout << "Nombre de lignes : "; cin >> nLignes;
/* Allocation dynamique */
t = new int* [ nLignes ];
for (int i=0; i < nLignes; i++)
t[i] = new int[ nColonnes ];
/* Initialisation */
for (int i=0; i < nLignes; i++)
for (int j=0; j < nColonnes; j++)
t[i][j] = i*j;
/* Affichage */
for (int i=0; i < nLignes; i++) {
for (int j=0; j < nColonnes; j++)
cout << t[i][j] << " ";
cout << std::endl;
}
Free_Tab();
system("pause>nul");
return 0;
}
void Free_Tab(){
for (int i=0; i < nLignes; i++)
delete[] t[i];
delete[] t;
}
Exécution 1
modifierNombre de colonnes : 6 Nombre de lignes : 3 0 0 0 0 0 0 0 1 2 3 4 5 0 2 4 6 8 10
Exécution 2
modifierTapez la valeur de taille_i : 8 Tapez la valeur de taille_j : 2 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7
Les tableaux de caractères
modifierUn tableau de caractères constitue une chaîne de caractères. Exemple avec des caractères de huit bits:
char chaine[20] = "BONJOUR";
Les tableaux de char
modifier
Un tableau de char
constitue une chaîne de caractères.
Exemple :
char chaine[20] = "BONJOUR";
La variable chaine
peut stocker jusqu'à 20 caractères, et contient les caractères suivants :
'B','O','N','J','O','U','R','\0'
Le dernier caractère est nul pour indiquer la fin de la chaîne de caractères. Il est donc important de prévoir son stockage dans le tableau. Dans cet exemple, 8 caractères sont donc nécessaires.
L'exemple précédent équivaut à :
char chaine[20] = {'B','O','N','J','O','U','R','\0'};
Si la chaîne est initialisée à la déclaration, on peut également laisser le compilateur détecter le nombre de caractères nécessaires, en omettant la dimension :
char chaine[] = "BONJOUR"; // tableau de 8 caractères
Dans ce cas la variable ne pourra stocker que des chaînes de taille inférieure ou égale à celle d'initialisation.
Les fonctions de manipulation de chaîne de caractères sont les mêmes que dans le langage C. Elles portent un nom commençant par str
(String) :
int strlen(const char* source)
- Retourne la longueur de la chaîne
source
(sans le caractère nul final). void strcpy(char* dest, const char* source)
- Copie la chaîne
source
dans le tableau pointé pardest
.
Les tableaux de char16_t
and char32_t
modifier
À partir de C++11 (C++ norme de 2011) trois types de chaînes de caractères sont prise en charge: UTF-8, UTF-16, et UTF-32. Le type char
conserve ses unités de codage de huit bits pour le codage des caractères Unicode via UTF-8, les nouveaux types char16_t
et char32_t
sont des unités de codage de seize ou trente-deux bits pour le codage des caractères Unicode via UTF-16 ou UTF-32.
Ces types sont standard à partir de C++2011 mais n'existent pas sur des compilateurs plus anciens, ni même sur les compilateur C-2011.
Il est également possible de les initialiser avec des littéraux, selon le cas:
u8"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."
Les tableaux de wchar_t
modifier
Les compilateurs moderne permettent de faire des chaînes de wchar_t
(16 bits ou 32 bits) au lieu de char
(8 bits). Ceci a été fait pour permettre de représenter une plus grande partie des caractères Unicode, même si on peut représenter l'ensemble de ces caractères en UTF-8.
Dans ce cas, il faut précéder les caractères par L
:
wchar_t chaine[20] = L"BONJOUR";
Equivaut à :
wchar_t chaine[20] = {L'B',L'O',L'N',L'J',L'O',L'U',L'R',L'\0'};
Les fonctions permettant leur manipulation portent un nom similaire, excepté que str
doit être remplacé par wcs
(Wide Character String) :
int wcslen(const wchar_t* source)
wcscpy(wchar_t* dest, const wchar_t* source)
Portabilité
modifierCertains environnements de développement (Visual C++ en particulier) considèrent Unicode comme une option de compilation.
Cette option de compilation définit alors le type TCHAR
comme char
(option legacy) ou wchar_t
(option unicode), selon l'option Unicode. Les constantes sont alors à encadrer par la macro _T(x)
qui ajoutera L
si nécessaire :
TCHAR chaine1[20] = _T("BONJOUR");
TCHAR chaine2[20] =
{_T('B'),_T('O'),_T('N'),_T('J'),_T('O'),_T('U'),_T('R'),_T('\0')};
Le nom des fonctions de manipulation commencent par _tcs
:
int _tcslen(const TCHAR* source)
_tcscpy(TCHAR* dest, const TCHAR* source)
Voir aussi
modifier
Les tableaux/Exercices
Exercices
modifierEXERCICE 1
modifierÉcrire une fonction qui a en paramètres une chaîne de caractères (paramètre en entrée) et un entier e (paramètre en sortie).
Cette fonction renvoie un booléen.
La fonction renvoie true
si la chaîne de caractères est un entier écrit sous la forme d'une suite de chiffres qui ne commence pas par 0, elle renvoie false
sinon.
Si la chaîne est correcte, la fonction renvoie dans e la valeur de cet entier.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- La validation d'une chaîne de caractères.
- Transformation d'une chaîne vers un autre type.
- Voici le fichier source :
#include <iostream>
using namespace std;
bool valide_entier(char t[],int &e)
{
bool valide=true;
int i=0;
e=0;
if (t[0]<'1' || t[0]>'9') valide = false;
while (valide && t[i]!='\0')
{
if (t[i]>='0' && t[i]<='9') { e = 10*e + (t[i]-'0'); i++; }
else valide = false;
}
return valide;
}
int main()
{
int a;
char t[20];
do {
cout << "Tapez une chaine :"; cin >> t;
} while (!valide_entier(t,a));
cout << "L'entier vaut :" << a << endl;
return 0;
}
EXERCICE 2
modifierÉcrire une fonction qui a en paramètre une chaîne de caractères (paramètre en entrée et en sortie) et qui transforme toutes les minuscules de la chaîne en majuscules.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- Transformation d'une chaîne de caractères.
- Majuscules et minuscules.
- Voici le fichier source :
#include <iostream>
using namespace std;
void minmaj(char t[])
{
int i = 0;
while (t[i] != '\0')
{
if (t[i] >= 'a' && t[i] <= 'z')
t[i] = t[i] + ('A'-'a');
i++;
}
}
int main()
{
char a[50];
cout << "Tapez une chaine svp :"; cin >> a;
minmaj(a);
cout << "La chaine finale est :" << a << endl;
return 0;
}
EXERCICE 3
modifierÉcrire une fonction qui a en paramètre une chaîne de caractères (paramètre en entrée et en sortie) et qui supprime toutes les voyelles.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- Transformation d'une chaîne de caractères.
- Voici le fichier source :
#include <iostream>
#include <cstring>
using namespace std;
void supvoy(char t[], int nb)
{
int i=0,j;
while(t[i]!='\0')
{
if(t[i]=='a' || t[i]=='A') {nb--; for (j=i; j<nb; j++) t[j]=t[j+1]; t[nb]='\0'; i--;}
if(t[i]=='e' || t[i]=='E') {nb--; for (j=i; j<nb; j++) t[j]=t[j+1]; t[nb]='\0'; i--;}
if(t[i]=='i' || t[i]=='I') {nb--; for (j=i; j<nb; j++) t[j]=t[j+1]; t[nb]='\0'; i--;}
if(t[i]=='o' || t[i]=='O') {nb--; for (j=i; j<nb; j++) t[j]=t[j+1]; t[nb]='\0'; i--;}
if(t[i]=='u' || t[i]=='U') {nb--; for (j=i; j<nb; j++) t[j]=t[j+1]; t[nb]='\0'; i--;}
if(t[i]=='y' || t[i]=='Y') {nb--; for (j=i; j<nb; j++) t[j]=t[j+1]; t[nb]='\0'; i--;}
i++;
}
}
int main()
{
char a[50];
int b;
cout << "Tapez une chaine svp :"; cin >> a;
b = strlen(a);
supvoy(a, b);
cout << "La chaine finale est :" << a << endl;
return 0;
}
EXERCICE 4
modifierÉcrire une fonction qui a en paramètres deux chaînes de caractères ch1 et ch2 (paramètres en entrée) et renvoie un booléen indiquant si la chaîne ch2 est contenue dans la chaîne ch1.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- Recherche d'une chaîne incluse dans une autre chaîne.
- Voici le fichier source :
#include <iostream>
using namespace std;
bool contient(char ch1[], char ch2[])
{
int ii=0, i=0, j=0;
bool fini, trouve;
trouve = false;
fini = false;
while (!trouve && !fini)
{
if(ch1[ii] == ch2[j])
{
ii++; j++;
if (ch2[j] == '\0') trouve = true;
}
else
{
i++; ii=i; j=0;
}
if (ch1[ii] == '\0') fini = true;
}
return trouve;
}
int main()
{
char a[50], b[50];
cout << "Tapez une chaine svp :";
cin >> a;
cout << "Tapez une chaine svp :";
cin >> b;
if (contient(a, b)) cout << "la premiere chaine contient la seconde" << endl;
else cout << "la premiere chaine ne contient pas la seconde" << endl;
return 0;
}
EXERCICE 5
modifierÉcrire un programme qui demande à l'utilisateur de taper un verbe du premier groupe et qui le conjugue à l'indicatif présent.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- La construction d'une chaîne à partir d'une autre.
- Voici le fichier source :
#include <iostream>
using namespace std;
int main()
{
char tt[20];
cout << "Tapez un verbe du premier groupe : ";
cin >> tt;
int i;
i = strlen(tt);
if (i<=2 || tt[i-1]!='r' || tt[i-2]!='e')
cout << "le verbe n'est pas du premier groupe" << endl;
else
{
tt[i-2]='\0';
cout << "je " << tt << "e" << endl;
cout << "tu " << tt << "es" << endl;
cout << "il " << tt << "e" << endl;
cout << "nous " << tt << "ons" << endl;
cout << "vous " << tt << "ez" << endl;
cout << "ils " << tt << "ent" << endl;
}
return 0;
}
EXERCICE 6
modifierÉcrire un programme qui saisit une chaîne pouvant contenir des espaces et qui affiche chaque mot de la chaîne, le séparateur étant l'espace.
Exemple, on tape : je pense donc je suis
Le programme affiche :
mot 1 : je
mot 2 : pense
mot 3 : donc
mot 4 : je
mot 5 : suis
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- Analyse syntaxique d'une chaîne de caractères.
- Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
char t[50];
char mot[50];
int i=0, j=0, state=1, num=0;
cout << "Tapez une phrase : "; cin.getline(t, 50);
while (t[i] != '\0'){
if (state == 1)
{
if (t[i] != ' ')
{
state = 2; j=0; mot[0] = t[i]; j++;
}
}
else
{
if (t[i] != ' ') { mot[j] = t[i]; j++; }
else
{
state = 1;
num++;
mot[j] = '\0';
cout << "mot " << num << " : " << mot << endl;
}
}
i++;
}
if (state==2)
{
num++;
mot[j]='\0';
cout<<"mot "<<num<<" : "<<mot<<endl;
}
return 0;
}
EXERCICE 7
modifierÉcrire un programme qui demande à l'utilisateur de taper une chaîne de caractères et qui indique si cette chaîne est un palindrome ou non.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- validation d'une chaîne.
- Voici le fichier source :
#include <iostream>
using namespace std;
int main()
{
char t[50];
int i, j;
bool palin;
palin = true;
cout << "Tapez une chaîne : "; cin >> t;
i = 0;
j = strlen(t) - 1;
while (palin==true && i<j)
if (t[i] != t[j]) palin = false; else { i++; j--; }
if (palin) cout << "C'est un palindrome" << endl;
else cout << "ce n'est pas un palindrome" << endl;
return 0;
}
EXERCICE 8
modifierÉcrire un programme qui demande à l'utilisateur de taper une chaîne de caractères et qui affiche la lettre (minuscule ou majuscule) la plus fréquente.
Cet exercice a pour but de vérifier les points techniques suivants :
- La manipulation de chaînes de caractères.
- Manipulation sur les majuscules ou minuscules.
- Voici le fichier source :
#include <iostream>
using namespace std;
int main()
{
char ch[50];
int t[52];
int i, max;
char cmax;
cout << "Tapez une chaîne : "; cin >> ch;
for (i=0; i<52; i++) t[i] = 0;
i=0;
while (ch[i] != '\0')
{
if (ch[i] >= 'A' && ch[i] <= 'Z') t[ch[i]-'A']++;
else if(ch[i]>='a' && ch[i]<='z') t[ch[i]-'a'+26]++;
i++;
}
max = t[0]; cmax = 'A';
for (i=1; i<52; i++)
if(max<t[i])
{
max = t[i];
if (i<26) cmax = (char) (i+'A');
else cmax = (char) (i-26+'a');
}
cout << "La lettre la plus fréquente est : " << cmax << endl;
return 0;
}
Les tableaux/Exercices sur les tableaux statiques
Exercices
modifierExercice 1
modifierÉcrire un programme qui demande à l’utilisateur de saisir 10 entiers stockés dans un tableau. Le programme doit afficher le nombre d'entiers supérieurs ou égaux à 10.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : recherche du nombre d'éléments vérifiant une propriété.
Voici le fichier source :
#include <iostream>
const int N = 10;
int main()
{
int t[N], i, nb = 0;
for (i=0; i<N; i++) {
std::cout << "Tapez un entier ";
std::cin >> t[i];
}
for (i=0; i<N; i++) {
if(t[i]>=10) {
nb++;
}
}
std::cout << "Le nombre d'entiers supérieurs ou égaux à 10 est : " << nb << std::endl;
return 0;
}
Exercice 2
modifierÉcrire un programme qui demande à l’utilisateur de saisir 10 entiers stockés dans un tableau ainsi qu’un entier V. Le programme doit rechercher si V se trouve dans le tableau et afficher « V se trouve dans le tableau » ou « V ne se trouve pas dans le tableau ».
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : recherche d'un élément dans un tableau.
Voici le fichier source :
#include <iostream>
const int N=10;
int main()
{
int t[N], i, V;
bool trouve;
for (i=0; i<N; i++) {
std::cout << "Tapez un entier : ";
std::cin >> t[i];
}
std::cout << "Tapez la valeur de V : ";
std::cin >> V;
trouve = false;
i = 0;
while (!trouve && i<N) {
if (t[i]==V) {
trouve=true;
} else {
i++;
}
}
if (trouve) {
std::cout << "La valeur V se trouve dans le tableau" << std::endl;
} else {
std::cout << "La valeur V ne se trouve pas dans le tableau" << std::endl;
}
return 0;
}
Exercice 3
modifierÉcrire un programme qui demande à l’utilisateur de saisir 10 entiers stockés dans un tableau. Le programme doit ensuite afficher l’indice du plus grand élément.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : recherche de l'indice du plus grand élément.
Voici le fichier source :
#include <iostream>
const int N = 10;
int main()
{
int t[N], i, indice;
for (i=0; i<N; i++) {
std::cout << "Tapez un entier :";
std::cin >> t[i];
}
indice=0;
for (i=1; i<N; i++) {
if (t[indice]<t[i]) {
indice=i;
}
}
std::cout << "L'indice du plus grand élément est : " << indice << std::endl;
return 0;
}
Exercice 4
modifierÉcrire un programme qui demande à l’utilisateur de saisir 10 entiers stockés dans un tableau ainsi qu’un entier V. Le programme doit rechercher si V se trouve dans le tableau et doit supprimer la première occurrence de V en décalant d’une case vers la gauche les éléments suivants et en rajoutant un 0 à la fin du tableau. Le programme doit ensuite afficher le tableau final.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : suppression d'un éléments avec décalage des suivants.
Voici le fichier source :
#include <iostream>
const int N=10;
int main()
{
int t[N], i, j, V;
bool trouve;
for (i=0; i<N; i++) {
std::cout << "Tapez un entier ";
std::cin >> t[i];
}
std::cout << "Tapez la valeur de V : ";
std::cin >> V;
trouve = false;
i = 0;
while (!trouve && i<N) {
if (t[i]==V) {
trouve = true;
} else {
i++;
}
}
if (trouve) {
for (j=i; j<N-1; j++) {
t[j] = t[j+1];
}
t[N-1] = 0;
}
for (i=0; i<N; i++) {
std::cout << t[i] << std::endl;
}
return 0;
}
Exercice 5
modifierÉcrire un programme qui demande à l’utilisateur de saisir 10 entiers stockés dans un tableau ainsi qu’un entier V et un entier i compris entre 0 et 9. Le programme doit décaler d'une case vers la droite tous les éléments à partir de l'indice i (en supprimant le dernier élément du tableau) et doit mettre la valeur V dans le tableau à l’indice i. Le programme doit ensuite afficher le tableau final.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : insertion dans en tableau avec décalage.
Voici le fichier source :
#include <iostream>
const int N = 10;
int main()
{
int t[N], i, indice, V;
for (i=0; i<N; i++) {
std::cout << "Tapez un entier ";
std::cin >> t[i];
}
std::cout << "Tapez un indice (de 0 à 9) : ";
std::cin >> indice;
std::cout << "Tapez la valeur de V : ";
std::cin >> V;
if (indice>=0 && indice<=N-1) {
for(i=N-1; i>indice; i--) {
t[i] = t[i-1];
}
t[indice] = V;
}
for(i=0; i<N; i++) {
std::cout<< t[i] << std::endl;
}
return 0;
}
Exercice 6
modifierÉcrire un programme qui demande à l’utilisateur de taper 10 entiers qui seront stockés dans un tableau. Le programme doit ensuite afficher soit « le tableau est croissant », soit « le tableau est décroissant », soit « le tableau est constant », soit « le tableau est quelconque ».
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : vérifier si le tableau vérifie une propriété donnée.
Voici le fichier source :
#include <iostream>
const int N=10;
int main()
{
int a[N],i;
bool croissant = true, decroissant = true;
for (i=0; i<N; i++) {
std::cout << "Veuillez taper l'entier numero " << i << " : ";
std::cin >> a[i];
}
for (i=0; i<N-1; i++) {
if (a[i] > a[i+1]) {
croissant = false;
}
if (a[i] < a[i+1]) {
decroissant = false;
}
}
if (croissant && decroissant) {
std::cout << "Le tableau est constant" << std::endl;
}
if (croissant && !decroissant) {
std::cout << "Le tableau est croissant" << std::endl;
}
if (!croissant && decroissant) {
std::cout << "Le tableau est décroissant"<< std::endl;
}
if (!croissant && !decroissant) {
std::cout << "Le tableau est quelconque" << std::endl;
}
return 0;
}
Exercice 7
modifierÉcrire un programme qui demande à l’utilisateur de taper 10 entiers qui seront stockés dans un tableau. Le programme doit trier le tableau par ordre croissant et doit afficher le tableau.
Algorithme suggéré :
On cherche l’indice du plus petit élément parmi les indices de 0 à 9 et on échange cet élément avec t[0].
On cherche l’indice du plus petit élément parmi les indices de 1 à 9 et on échange cet élément avec t[1].
On cherche l’indice du plus petit élément parmi les indices de 2 à 9 et on échange cet élément avec t[2].
On cherche l’indice du plus petit élément parmi les indices de 8 à 9 et on échange cet élément avec t[8].
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : tri d'un tableau.
Voici le fichier source :
#include <iostream>
const int N=10;
int main()
{
int a[N], i, j, min, imin, tmp;
for (i=0; i<N; i++) {
std::cout << "Veuillez taper l'entier numero " << i << " : ";
std::cin >> a[i];
}
for (i=0; i<N-1; i++) {
imin = i;
min = a[i];
for (j=i+1; j<N; j++) {
if(a[j]<min) {
min=a[j]; imin=j;
}
}
tmp = a[imin];
a[imin] = a[i];
a[i] = tmp;
}
std::cout << "Voici le tableau trié :" << std::endl;
for (i=0; i<N; i++) {
std::cout<<"a[" << i << "]=" << a[i] << std::endl;
}
return 0;
}
Exercice 8
modifierÉcrire un programme qui demande à l’utilisateur de taper 10 entiers qui seront stockés dans un tableau. Le programme doit trier le tableau par ordre croissant et doit afficher le tableau.
Algorithme suggéré (tri bulle) :
On parcourt le tableau en comparant t[0] et t[1] et en échangeant ces éléments s'ils ne sont pas dans le bon ordre.
on recommence le processus en comparant t[1] et t[2],... et ainsi de suite jusqu'à t[8] et t[9].
On compte lors de ce parcours le nombre d'échanges effectués.
On fait autant de parcours que nécessaire jusqu'à ce que le nombre d'échanges soit nul : le tableau sera alors trié.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- un algorithme simple sur un tableau : tri d'un tableau.
Voici le fichier source :
#include <iostream>
const int N=10;
int main()
{
int a[N], i, nb, tmp;
for(i=0; i<N; i++) {
std::cout << "Veuillez taper l'entier numero " << i << " : ";
std::cin >> a[i];
}
do {
nb=0;
for (i=0; i<N-1; i++) {
if (a[i] > a[i+1]) {
tmp = a[i];
a[i] = a[i+1];
a[i+1] = tmp;
nb++;
}
}
} while (nb!=0);
std::cout << "Voici le tableau trié :" << std::endl;
for (i=0; i<N; i++) {
std::cout << "a[" << i <<"]=" << a[i] << std::endl;
}
return 0;
}
Exercice 9
modifierÉcrire un programme qui saisit 2 tableaux de 10 entiers a et b. c est un tableau de 20 entiers. Le programme doit mettre dans c la fusion des tableaux a et b. On copiera dans les 10 premières cases de c le tableau a, dans les dix dernières le tableau b. Le programme affiche ensuite le tableau c.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux.
- un algorithme simple sur un tableau : fusion de 2 tableaux.
Voici le fichier source :
#include <iostream>
const int N=10;
int main()
{
int a[N], b[N], c[2*N], i;
std::cout << "Saisie du tableau a :" << std::endl;
for (i=0; i<N; i++) {
std::cout << "Tapez un entier : ";
std::cin >> a[i];
}
std::cout << "Saisie du tableau b :" << std::endl;
for (i=0; i<N; i++) {
std::cout << "Tapez un entier : ";
std::cin >> b[i];
}
for (i=0; i<2*N; i++) {
if (i<N) {
c[i] = a[i];
} else {
c[i] = b[i-N];
}
}
std::cout << "Voici le tableau c"<< std::endl;
for (i=0; i<2*N; i++) {
std::cout << c[i] << " ";
}
std::cout << std::endl;
return 0;
}
Exercice 10
modifierÉcrire un programme qui saisit 2 tableaux de 10 entiers a et b qui doivent être triés dans l'ordre croissant. Le programme devra tout d’abord vérifier que les deux tableaux sont triés. Le tableau c est un tableau de 20 entiers. Le programme doit mettre dans c la fusion des tableaux a et b. Le tableau c devra contenir les éléments de a et ceux de b et devra être trié. Le programme affiche ensuite le tableau c.
#include <iostream>
using namespace std;
const int N=10;
int main()
{
int a[N],b[N],c[2*N],i,trie,indicea,indiceb;
cout<<"SAISIE DU TABLEAU a"<<endl;
for(i=0; i<N; i++) {cout<<"Tapez un entier "; cin>>a[i];}
cout<<"SAISIE DU TABLEAU b"<<endl;
for(i=0; i<N; i++) {cout<<"Tapez un entier "; cin>>b[i];}
trie = true;
i = 0;
while(trie && i<N-1) if(a[i] > a[i+1]) trie = false; else i++;
if(!trie) cout<<"Le tableau a n'est pas trié"<<endl;
else
{
trie = true;
i = 0;
while(trie && i<N-1) if(b[i] > b[i+1]) trie = false; else i++;
if(!trie) cout<<"Le tableau b n'est pas trié"<<endl;
else
{
indicea = 0; indiceb = 0;
for(i=0; i<2*N; i++)
{
if(indicea==N) {c[i]=b[indiceb]; indiceb++;}
else if(indiceb==N) {c[i]=a[indicea]; indicea++;}
else if(a[indicea] < b[indiceb]) {c[i]=a[indicea]; indicea++;}
else {c[i]=b[indiceb]; indiceb++;}
}
}
}
cout << "VOICI LE TABLEAU c" << endl;
for(i=0; i<2*N; i++) cout << c[i] <<" ";
cout << endl;
return 0;
}
Exercice 11
modifierÉcrire un programme qui gère une liste d'entiers grâce au menu suivant :
1. Ajouter un entier
2. Afficher la liste des entiers
3. Supprimer le dernier entier de la liste.
4. Afficher la dernière note tapée
5. Quitter
Il y aura au maximum 10 entiers. Lorsqu'on rajoute un entier, il sera rajouté à la fin de la liste.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- gestion d'une liste simple grâce à un tableau statique.
Voici le fichier source :
#include <iostream>
using namespace std;
const int N=10;
int main()
{
int t[N], nb=0, choix, e, i;
bool fini = false;
while(fini==false)
{
cout<<"1. Ajouter un entier"<<endl;
cout<<"2. Afficher la liste des entier"<<endl;
cout<<"3. Supprimer le dernièr entier de la liste"<<endl;
cout<<"4. Afficher le dernier entier de la liste"<<endl;
cout<<"5. Quitter"<<endl;
cout<<"Votre choix : ";cin>>choix;
switch(choix)
{
case 1:
cout<<"Tapez un entier : ";cin>>e;
if(nb<N) {t[nb]=e; nb++; cout<<"ENTIER AJOUTE"<<endl;}
else cout<<"IMPOSSIBLE LE TABLEAU EST PLEIN"<<endl;
break;
case 2:
if(nb==0) cout<<"LA LISTE EST VIDE"<<endl;
else {
cout<<"VOICI LA LISTE"<<endl;
for(i=0; i<nb; i++) cout << t[i] <<" ";
cout << endl;
}
break;
case 3:
if(nb>0) {nb--; cout<<"ENTIER SUPPRIME"<<endl;}
else cout<<"LA LISTE EST VIDE"<<endl;
break;
case 4:
if(nb>0)cout<<"LE DERNIER ENTIER EST "<<t[nb-1]<<endl;
else cout<<"LA LISTE EST VIDE"<<endl;
break;
case 5:
fini=true;
break;
}
}
return 0;
}
Exercice 12
modifierÉcrire un programme qui gère une liste d'entiers grâce au menu suivant :
1. Ajouter un entier
2. Afficher la liste des entiers
3. Supprimer le premier entier ayant une valeur donnée.
4. Supprimer tous les entiers ayant une valeur donnée
5. Quitter
Il y aura au maximum 10 entiers. La liste devra être en permanence triée : lorsqu’on rajoute un entier, il sera inséré au bon endroit dans la liste pour que celle-ci reste triée.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux.
- gestion d'une liste triée grâce à un tableau statique.
Voici le fichier source :
#include <iostream>
using namespace std;
const int N=10;
int main()
{
int t[N], nb=0, choix, e, V, i, j;
bool fini=false, trouve;
while(fini==false)
{
cout<<"1. Ajouter un entier"<<endl;
cout<<"2. Afficher la liste des entier"<<endl;
cout<<"3. Supprimer le premier entier ayant une valeur donnée"<<endl;
cout<<"4. Supprimer tous les entiers ayant une valeur donnée"<<endl;
cout<<"5. Quitter"<<endl;
cout<<"Votre choix : ";cin>>choix;
switch(choix)
{
case 1:
if (nb<N)
{
cout<<"Tapez un entier : ";cin>>e;
i=0;
while(i!=nb && t[i]<e) i++;
for (j=nb; j>i; j--) t[j] = t[j-1];
t[i] = e;
nb++;
}
else cout<<"IMPOSSIBLE LE TABLEAU EST PLEIN"<<endl;
break;
case 2:
if(nb==0) cout<<"LA LISTE EST VIDE"<<endl;
else {
cout<<"VOICI LA LISTE"<<endl;
for(i=0; i<nb; i++) cout << t[i] <<" ";
cout<<endl;
}
break;
case 3:
cout<<"Tapez la valeur à supprimer :";
cin >> V;
trouve = false;
i=0;
while(!trouve && i<nb) if(t[i]==V) trouve=true; else i++;
if(trouve)
{
for(j=i; j<nb-1; j++) t[j]=t[j+1];
nb--;
}
break;
case 4:
cout<<"Tapez la valeur à supprimer :";
cin >> V;
j=0;
for(i=0; i<nb; i++)
if(t[i] != V) {t[j] = t[i]; j++;}
nb=j;
break;
case 5: fini=true;
break;
}
}
return 0;
}
Exercice 13
modifierÉcrire un programme qui demande à l’utilisateur de taper un entier N<=20 et qui affiche la N-ième ligne du triangle de Pascal.
ligne 1 : 1 1
ligne 2 : 1 2 1
ligne 3 : 1 3 3 1
ligne 4 : 1 4 6 4 1
et ainsi de suite…
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation simple de tableaux ;
- gestion d'une liste triée grâce à un tableau statique.
Voici le fichier source :
#include <iostream>
using namespace std;
int main()
{
int a[21], i, j, N;
cout << "Veuillez taper N : ";
cin >> N;
for (i=1; i<=N; i++)
{
if(i==1) a[0]=1;
a[i]=1;
for (j=i-1; j>=1; j--) a[j] = a[j] + a[j-1];
}
for (i=0; i<=N; i++) cout << a[i] <<" ";
cout<<endl;
return 0;
}
Exercice 14
modifierÉcrire un programme qui demande à l’utilisateur de taper 10 entiers compris entre 0 et 20 qui seront stockés dans un tableau et qui affiche le nombre de fois qu’on a tapé un 0, le nombre de 1, le nombre de 2, …, le nombre de 20.
#include <iostream>
using namespace std;
int main()
{
int a[10], nb[21], i;
for(i=0; i<10; i++)
{
do {cout<<"Veuillez taper l'entier numero "<<i<<" : "; cin>>a[i];}
while (a[i]>20 || a[i]<0);
}
for(i=0; i<21; i++) nb[i]=0;
for(i=0; i<10; i++) nb[a[i]]++;
for(i=0; i<21; i++) {cout<<"Il y a "<<nb[i]<<" fois l'entier "<<i<<endl;}
return 0;
}
Exercice 15
modifierÉcrire un programme qui demande à l’utilisateur de taper le contenu d’un tableau de réels de 4 lignes et 2 colonnes et qui affiche ce tableau mais en affichant la moyenne des éléments de chaque ligne, de chaque colonne et la moyenne globale.
Cet exercice a pour but de vérifier les points techniques suivants :
- utilisation de tableaux à 2 dimensions ;
- modélisation d'un problème mathématique basique.
Voici le fichier source :
#include <iostream>
using namespace std;
const int N_LIGNES = 4;
const int N_COLONNES = 2;
int main()
{
double t[N_LIGNES][N_COLONNES], moyL[N_LIGNES], moyC[N_COLONNES], moy;
int i, j;
for(i=0; i<N_LIGNES; i++)
for(j=0;j<N_COLONNES;j++)
{
cout<<"Tapez la valeur de la ligne "<<i<<" colonne "<<j<<" : ";
cin >> t[i][j];
}
for(i=0; i<N_LIGNES; i++) moyL[i]=0;
for(j=0; j<N_COLONNES; j++) moyC[j]=0;
moy=0;
for(i=0; i<N_LIGNES; i++)
for(j=0; j<N_COLONNES; j++)
{
moyL[i] += t[i][j];
moyC[j] += t[i][j];
moy += t[i][j];
}
// N_COLONNES valeurs par ligne
for(i=0; i<N_LIGNES; i++) moyL[i] /= N_COLONNES;
// N_LIGNES valeurs par colonne
for(j=0; j<N_COLONNES; j++) moyC[j] /= N_LIGNES;
moy /= (N_LIGNES*N_COLONNES);
for(i=0; i<N_LIGNES; i++)
{
for(j=0; j<N_COLONNES; j++)
cout << t[i][j] <<" ";
cout << moyL[i] <<endl;
}
for(j=0; j<N_COLONNES; j++)
cout << moyC[j] <<" ";
cout << endl;
return 0;
}
Tris des tableaux à une dimension
Exercice 1: En utilisant la méthode du tri bulles, écrire un programme qui trie en ordre croissant un tableau d'entiers. On utilisera des sous-programmes.
Tris des tableaux à une dimension/Correction
// Programme TriBulles
/* Trier un tableau à 2 dimensions par la méthode de tri à bulle */
#include <iostream>
using namespace std;
const short max = 10;
typedef short table[max];
void saisirTab(table tab, short taille);
void afficherTab(table tab, short taille);
void echanger2E(short& nb1, short& nb2);
void trieBulles(table tabEnt, short taille);
void main()
{
table tab;
short taille;
do
{
cout << "Nombre d'éléments du tableau (2 à " << max << ") ? ";
cin >> taille;
}
while ((taille < 2) || (taille > max));
saisirTab(tab, taille);
trieBulles(tab, taille);
cout << "Tableau trié : ";
afficherTab(tab, taille);
cout << endl;
}
void trieBulles(table tabEnt, short taille)
/* Trier en ordre croissant un tableau à 1 dimension par la méthode de tri à bulle */
{
bool permut; /* Vrai s'il y a eu au moins une permutation dans l'itération en cours. */
do
{
permut = false;
for(short i=0 ; i<taille-1 ; i++)
{
if (tabEnt[i] > tabEnt[i+1])
{
echanger2E(tabEnt[i], tabEnt[i+1]);
permut = true;
}
}
}
while (permut);
}
void saisirTab(table tabEnt, short taille)
// Saisit les éléments d'un tableau à 1 dimension
{
for(short i=0;i<taille;i++)
{
cout << "Entrer l'élément " << i+1 << " du tableau : ";
cin>>tabEnt[i];
}
cout << endl;
}
void afficherTab(table tabEnt, short taille)
// Affiche les éléments d'un tableau à 1 dimension
{
for(short i=0;i<taille;i++) cout << tabEnt[i] << " ";
cout << endl;
}
void echanger2E(short& nb1, short& nb2)
// Échanger les valeurs de 2 entiers
{
short tampon = nb1;
nb1 = nb2;
nb2 = tampon;
}
Les structures de contrôles
Une série d'instructions dans une fonction s'exécute séquentiellement par défaut. Cependant, il est nécessaire que certaines parties du code ne s'exécutent que sous certaines conditions, ou s'exécutent plusieurs fois dans une boucle pour par exemple traiter tous les éléments d'un tableau.
Le if
modifierCette structure de contrôle permet d'exécuter une instruction ou une suite d'instructions seulement si une condition est vraie.
Syntaxe :
modifierif (condition) instruction
- condition
- Expression booléenne de la condition d'exécution.
- instruction
- Une instruction ou un bloc d'instructions entre accolades exécuté si la condition est vraie.
Lorsqu'il n'y a qu'une ligne d'instructions conditionnelles, la paire d'accolades est conditionnelle, mais les omettre est dangereux.
Sémantique :
modifierOn évalue la condition :
- si elle est vraie on exécute l’instruction et on passe à l’instruction suivante.
- si elle est fausse on passe directement à l’instruction suivante.
L’instruction peut être remplacée par une suite d’instructions entre accolades.
Exemple :
modifier#include <iostream>
using namespace std;
int main()
{
int a;
cout << "Tapez la valeur de a : "; cin >> a;
if (a > 10)
{
cout << "a est plus grand que 10" << endl;
}
cout << "Le programme est fini." << endl;
return 0;
}
Ce programme demande à l'utilisateur de taper un entier a.
Si a est strictement plus grand que 10, il affiche "a est plus grand que 10."
.
Dans le cas contraire, il n'affiche rien.
Exécution 1
modifierTapez la valeur de a : 12 a est plus grand que 10
Exécution 2
modifierTapez la valeur de a : 8
Condition multiple contenant des opérateurs logiques
modifierSi la condition a évaluer est complexe et contient des opérateurs logiques, chaque condition doit être écrite entre parenthèse.
Par exemple, si la condition condition1 et la condition condition2 doivent être vérifiée en même temps, on écrira
if ( (condition1) && (condition2) ) instruction
où l'opérateur logique && peut être remplacé par tout autre opérateur logique.
Ajouter des opérateurs logiques se fait selon le même principe, en prenant en compte les règles de préséance de ces opérateurs pour adapter la place des parenthèses.
Le if...else
modifierCette structure de contrôle permet d'exécuter une instruction si une condition est vraie, ou une autre instruction si elle est fausse.
Syntaxe
modifierif (condition) instruction1 else instruction2
- condition
- Expression booléenne de la condition d'exécution.
- instruction1
- Une instruction ou un bloc d'instructions exécuté si la condition est vraie.
- instruction2
- Une instruction ou un bloc d'instructions exécuté si la condition est fausse.
Sémantique
modifierOn évalue la condition :
- si elle est vraie, on exécute l'
instruction1
et on passe à l'instruction suivante - si elle est fausse, on exécute l'
instruction2
et on passe à l'instruction suivante
L'instruction1
ou l'instruction2
peuvent être remplacées par une suite d'instructions entre accolades.
Lorsqu'il n'y a qu'une ligne d'instructions conditionnelles, la paire d'accolades est conditionnelle, mais les omettre est dangereux.
Exemple :
modifier#include <iostream>
using namespace std;
int main()
{
int a;
cout << "Tapez la valeur de a : "; cin >> a;
if (a > 10)
{
cout << "a est plus grand que 10" << endl;
}
else
{
cout << "a est inférieur ou égal à 10" << endl;
}
return 0;
}
Ce programme demande à l'utilisateur de taper un entier a
. Si a est strictement plus grand que 10, il affiche "a est plus grand que 10"
. Dans le cas contraire, il affiche "a est inférieur ou égal à 10"
.
Exécution 1
modifierTapez la valeur de a : 12 a est plus grand que 10
Exécution 2
modifierTapez la valeur de a : 8 a est inférieur ou égal à 10
Plusieurs instructions par condition
modifierSi plusieurs actions doivent s'enchaîner lorsqu'une condition est vraie, on met obligatoirement ces actions entre accolades. L'accolade fermante ne doit pas être suivie d'un point virgule dans ce cas. Ainsi, on écrira
if (condition1) { instruction1.1 ; instruction1.2 ; ... } // pas de point virgule ici else if (condition2) { instruction2.1 ; ... } ....
qui exécute les instructions instruction1.1 et instruction1.2, ... si la condition condition1 est vraie, et les instructions instruction2.1, ... si la condition condition2 est vraie. Lorsqu'il n'y a qu'une ligne d'instructions conditionnelles, la paire d'accolades est conditionnelle, mais les omettre est dangereux.
Le switch
modifierL'instruction switch
permet de tester plusieurs valeurs pour une expression.
Syntaxe
modifierswitch(expression) { case constante1: instruction1_1 instruction1_2... case constante2: instruction2_1 instruction2_2... ... default: instruction_1 instruction_2... }
- expression
- Expression de type scalaire (entier, caractère, énumération, booléen).
- case constante1: instruction1_...
- Une série d'instructions ou de blocs d'instructions exécutés si expression vaut constante1.
- case constante2: instruction2_...
- Une série d'instructions ou de blocs d'instructions exécutés si expression vaut constante2.
- default: instruction_...
- Une série d'instructions ou de blocs d'instructions exécutés quand aucun des cas précédent ne correspond.
Sémantique
modifierOn teste la valeur de l'expression spécifiée.
On la compare successivement aux constantes constante1
, constante2
, etc.
Si la valeur de l'expression correspond à l'une des constantes, l'exécution débute à l'instruction correspondante.
Si aucune constante ne correspond, l'exécution débute à l'instruction correspondant à default:
.
L'exécution se termine à l'accolade fermante du switch
ou avant si l'instruction break;
est utilisée.
En général, l'instruction break
sépare les différents cas. Il n'est pas utilisé quand plusieurs cas sont traités par les mêmes instructions.
En C++, l'expression n'est jamais une chaine de caractères, ni un tableau, ni un objet, ni une structure.
Exemple 1
modifier#include <iostream>
using namespace std;
int main()
{
int a;
cout << "Tapez la valeur de a : "; cin >> a;
switch(a)
{
case 1 :
cout << "a vaut 1" << endl;
break;
case 2 :
cout << "a vaut 2" << endl;
break;
case 3 :
cout << "a vaut 3" << endl;
break;
default :
cout << "a ne vaut ni 1, ni 2, ni 3" << endl;
break;
}
return 0;
}
Ce programme demande à l'utilisateur de taper une valeur entière et la stocke dans la variable a. On teste ensuite la valeur de a : en fonction de cette valeur on affiche respectivement les messages "a vaut 1"
, "a vaut 2"
, "a vaut 3"
, ou "a ne vaut ni 1, ni 2, ni 3"
.
Exécution 1 :
Tapez la valeur de a : 1 a vaut 1
Exécution 2 :
Tapez la valeur de a : 2 a vaut 2
Exécution 3 :
Tapez la valeur de a : 3 a vaut 3
Exécution 4 :
Tapez la valeur de a : 11 a vaut ne vaut ni 1, ni 2, ni 3
Exemple 2
modifier#include <iostream>
using namespace std;
int main()
{
int a;
cout << "Tapez la valeur de a : "; cin >> a;
switch(a)
{
case 1 :
cout << "a vaut 1" << endl;
break;
case 2 :
case 4 :
cout << "a vaut 2 ou 4" << endl;
break;
case 3 :
case 7 :
case 8 :
cout << "a vaut 3, 7 ou 8" << endl;
break;
default :
cout << "valeur autre" << endl;
break;
}
return 0;
}
Ce programme demande à l'utilisateur de taper une valeur entière et la stocke dans la variable a. On teste ensuite la valeur de a : en fonction de cette valeur on affiche respectivement les messages "a vaut 1"
, "a vaut 2 ou 4"
, "a vaut 3, 7 ou 8"
, ou "valeur autre"
.
Exécution 1 :
Tapez la valeur de a : 1 a vaut 1
Exécution 2 :
Tapez la valeur de a : 4 a vaut 2 ou 4
Exécution 3 :
Tapez la valeur de a : 7 a vaut 3, 7 ou 8
Exécution 4 :
Tapez la valeur de a : 11 valeur autre
L'instruction switch
de cet exemple regroupe le traitement des valeurs 2 et 4, et des valeurs 3, 7 et 8.
Le for "classique"
modifierLe for est une structure de contrôle qui permet de répéter un certain nombre de fois une partie d'un programme.
Syntaxe
modifierfor(instruction_init ; condition ; instruction_suivant) instruction_répétée
- instruction_init
- Une instruction (ou une série d'instruction séparées par une virgule) d'initialisation de la boucle (initialiser un compteur à 0, pointer le premier élément d'une liste, ...).
- condition
- Expression booléenne de la condition de répétition de la boucle.
- instruction_suivant
- Une instruction (ou une série d'instruction séparées par une virgule) pour passer à l'itération suivante (incrémenter un index, passer à l'élément suivant, ...)
- instruction_répétée
- Une instruction ou un bloc d'instruction répété à chaque itération de la boucle.
Sémantique
modifier- on exécute l'
instruction_init
. - On teste la
condition
:- tant qu'elle est vraie, on exécute l'
instruction_repetée
, puis l'instruction_suivant
puis on revient au 2. - si elle est fausse, la boucle est terminée et on passe à l'instruction suivante.
- tant qu'elle est vraie, on exécute l'
- L'
instruction_repetée
peut être une suite d'instructions entre accolades.
Exemples
modifierPremier cas simple :
#include <iostream>
using namespace std;
int main()
{
int i;
for(i=0 ; i<10 ; i++)
cout << "BONJOUR" << endl;
return 0;
}
Second cas où deux index dans un tableau sont utilisés pour le parcourir à partir des deux bouts : le début et la fin.
int main()
{
// 10 entiers indexés de 0 à 9 inclus :
int elements[10] = { 3, 14, 15, 9, 26, 5, 35, 8 };
int i, j;
for (i=0, j=9; i <= j; i++, j--)
cout << "Elements"
<< " [" << i << "]=" << elements[i]
<< " [" << j << "]=" << elements[j]
<< endl;
return 0;
}
Instructions multiples
modifierIl existe également une pratique répandue qui consiste à initialiser plusieurs variables, évaluer plusieurs conditions ou, plus fréquemment, modifier plusieurs variables. Exemple :
float c=10;
for(int a=2, b=a; a<2, b*=a; a++, c/=b)
{
}
Ceci compilera parfaitement, mais est également parfaitement illisible. Dans la plupart des cas, il vaut mieux n'utiliser la boucle for elle-même que pour traiter le ou les compteurs, afin de conserver un code compréhensible. Cela compile parce qu'en C++, la virgule ',' est un opérateur comme les autres, qui évalue l'expression qui la précède et celle qui la suit, et renvoie le résultat de la dernière. Pour donner un exemple plus clair :
int b = 2,10; // b=10. Vous pouvez compiler pour vérifier.
Boucle infinie
modifierQuand aucune condition n'est spécifiée, celle-ci vaut true
par défaut, ce qui créé donc une boucle infinie.
Une boucle infinie est parfois nécessaire quand on ne connait pas l'instant où la boucle doit être arrêtée, et que la condition d'arrêt est évaluée au cœur de la boucle (gestion de messages, écoute de connexions réseau, ...).
for(;;)
{
// ...
}
le for "moderne"
modifierLe dernier standard C++ (surnommé C++11) a ajouté une nouvelle syntaxe pour la boucle for. En effet, on s'est aperçu que l'on utilise le plus souvent la boucle for pour parcourir une collection (une collection est un tableau classique ou un conteneur comme ceux de la stl.) en entier, et que donc le même code revenait très souvent (à la langue des commentaires près ;) ). Voici des exemples de code fréquents :
typedef std::list<int> MaListe;
MaListe maListe;
for(MaListe::iterator it=maListe.begin();it!=maListe.end();++it)
{
}
int tableau[]={0,1,2,3,4};
const int tableauMax=5;//il y a 5 cases dans tableau
for(int i=0; i<tableauMax; ++i)
{
}
Très répétitif, non (surtout le 1er exemple d'ailleurs) ? Donc, une nouvelle syntaxe a été créée qui permet de s'affranchir d'une grande partie de ces frappes et donc de prolonger la durée de vie de nos claviers ;)
Syntaxe
modifierfor(variable : collection) instruction
Sémantique
modifiervariable indique la variable qui recevra les valeurs issues de collection, en commençant par la première jusqu'à la dernière. Si variable n'existe pas, il est possible de la déclarer dans la même ligne, de la même façon que pour l'écriture classique.
Un bémol de cette syntaxe est que le compteur est perdu : si vous avez besoin du compteur dans le bloc d'instruction (ou plus tard) cette syntaxe est inutilisable.
Vous pouvez néanmoins, créer un compteur en le déclarant avant la boucle (en fixant sa valeur de départ à 0, par exemple) et l'entretenir durant l'exécution de la boucle (en l'incrémentant, par exemple).
Exemple
modifierAppliqué au dernier exemple, voici le résultat :
typedef std::list<int> MaListe;
MaListe maListe;
for(int i : maListe)
{
}
int tableau[]={0,1,2,3,4};
for(int i: tableau)
{
}
//pour garder le compteur
int tableau[]={0,1,2,3,4};
int cpt = 0;
for(int i: tableau)
{
++cpt;
}
Beaucoup plus clair, non ?
Le while
modifierSyntaxe
modifierwhile (condition) instruction;
Sémantique
modifierOn teste la condition :
- si elle est vraie, on exécute l'instruction et on recommence.
- si elle est fausse, la boucle est terminée, on passe à l'instruction suivante.
L'instruction peut être une suite d'instructions entre accolades.
Exemple de programme
modifier#include <iostream>
using namespace std;
int main()
{
int i = 0;
while (i < 10)
{
cout << "La valeur de i est : " << i << endl;
i++;
}
cout << "La valeur finale de i est : " << i << endl;
return 0;
}
Exécution
modifierLa valeur de i est : 0 La valeur de i est : 1 La valeur de i est : 2 La valeur de i est : 3 La valeur de i est : 4 La valeur de i est : 5 La valeur de i est : 6 La valeur de i est : 7 La valeur de i est : 8 La valeur de i est : 9 La valeur finale de i est : 10
Explications
modifierLa variable i est initialisée à 0.
À chaque étape, à la fin du corps du while, on incrémente i de 1.
On exécute donc le corps du while la première fois avec i valant 0, la dernière fois avec i valant 9.
Lorsqu’on sort du while, i vaut 10.
Le do ... while
modifierSyntaxe
modifierdo { ...instructions... } while( condition );
Sémantique
modifier- on exécute les instructions ;
- on évalue la condition ;
- si elle est vraie, on recommence au 1 ;
- si elle est fausse, la boucle est terminée, on passe à l ’instruction suivante.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
int i = 0;
do
{
cout << "La valeur de i vaut : " << i << endl;
i++;
}
while (i < 10);
cout << "La valeur finale de i est " << i << endl;
return 0;
}
Exécution :
La valeur de i vaut : 0 La valeur de i vaut : 1 La valeur de i vaut : 2 La valeur de i vaut : 3 La valeur de i vaut : 4 La valeur de i vaut : 5 La valeur de i vaut : 6 La valeur de i vaut : 7 La valeur de i vaut : 8 La valeur de i vaut : 9 La valeur finale de i est : 10
Le goto
modifierLe goto
est une structure de contrôle obsolète et ne doit plus être utilisé.
Le break
modifierL'instruction break
sert à "casser" ou interrompre une boucle (for
, while
et do
), ou un switch
.
Syntaxe
modifierbreak;
Sémantique
modifier- Sort de la boucle ou de l'instruction
switch
la plus imbriquée.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
for (int i = 0; i < 10; i++)
{
cout << "La variable i vaut : " << i << endl;
if (i == 5)
break;
}
return 0;
}
Ce programme interrompt sa boucle lors de la sixième itération.
Exécution
modifierLa variable i vaut : 0 La variable i vaut : 1 La variable i vaut : 2 La variable i vaut : 3 La variable i vaut : 4 La variable i vaut : 5
Note importante
modifierL'instruction break
interrompt le cours normal de l'exécution et doit être utilisée avec parcimonie.
Elle ne doit servir que pour éviter une complexification de la condition ; une condition complexe pouvant entraîner des erreurs de code et une moindre lisibilité.
L'exemple ci-dessous montre un cas où il n'est pas pertinent d'utiliser l'instruction break
.
for (int i=0 ; i<10 ; i++)
{
cout << "La variable i vaut : " << i << endl;
if (i == 5) break;
}
L'arrêt par l'instruction est notamment non pertinent dans ce cas car l'arrêt est prévisible à l'avance. Il suffit pour cet exemple de corriger la condition d'arrêt de la boucle :
for (int i=0 ; i<=5 ; i++)
{
cout << "La variable i vaut : " << i << endl;
}
Par contre, dans le cas de l'exemple suivant, où un arrêt prématuré de la boucle est effectué lorsqu'un item de tableau est trouvé et qu'un traitement complexe détermine qu'il faut interrompre la boucle, l'utilisation de l'instruction break
est pertinent.
L'équivalent avec une condition de boucle serait complexe, ou nécessiterait l'utilisation d'une variable dédiée au stockage de la condition.
for (int i=0 ; i<10 ; i++)
{
cout << "ITEM " << i << endl;
if (item[i]->getType() == type_a_trouver)
{
cout << " Trouvé à l'index " << i << endl;
if (item[i]->analyseContenuFichier() == TYPE_WIKICODE)
{
cout << " Page wiki trouvée." << endl;
break;
}
cout << " Fausse alerte, on continue..." << endl;
}
cout << " Item suivant !" << endl;
}
L'équivalent avec une variable stockant l'état de la condition de sortie impliquerait de tester cet état dans la condition de boucle, déterminer une valeur initiale pertinente pour que la boucle fasse la première itération, de tester cet état aussi avant chaque instruction ou bloc à chaque niveau d'accolade. Cela produit un code beaucoup moins explicite, et réduit la maintenabilité du code (répétition de code) :
int trouve = 0;
for (int i=0 ; i<10 && trouve==0 ; i++)
{
cout << "ITEM " << i << endl;
if (item[i]->getType() == type_a_trouver)
{
cout << " Trouvé à l'index " << i << endl;
if (item[i]->analyseContenuFichier() == TYPE_WIKICODE)
{
cout << " Page wiki trouvée." << endl;
trouve = 1;
}
if (trouve == 0)
cout << " Fausse alerte, on continue..." << endl;
}
if (trouve == 0)
cout << " Item suivant !" << endl;
}
Le continue
modifierL'instruction continue sert à "continuer" une boucle (for
, while
et do
) avec la prochaine itération.
Syntaxe
modifiercontinue;
Sémantique
modifierPasse à l'itération suivante de la boucle la plus imbriquée.
Exemple
modifier#include <iostream>
using namespace std;
int main()
{
for (int i = 0; i < 10; i++)
{
if (i == 5) continue;
cout << "La variable i vaut : " << i << endl;
}
return 0;
}
Ce programme saute l'affichage de la sixième itération.
Exécution
modifierLa variable i vaut : 0 La variable i vaut : 1 La variable i vaut : 2 La variable i vaut : 3 La variable i vaut : 4 La variable i vaut : 6 La variable i vaut : 7 La variable i vaut : 8 La variable i vaut : 9
Voir aussi
modifier
Les structures de contrôles/Exercices
Exercices sur les structures de contrôle
modifierEXERCICE 1
modifierÉcrire un programme qui demande à l'utilisateur de taper un entier et qui affiche GAGNE si l'entier est entre 56 et 78 bornes incluses PERDU sinon.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - Le choix d'une structure de contrôle adaptée au problème !
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int a;
cout<<"Tapez un entier : "; cin>>a;
if ((a>=56)&&(a<=78)) cout<<"GAGNE"<<endl; else cout<<"PERDU"<<endl;
return 0;
}
EXERCICE 2
modifierÉcrire un programme qui affiche tous les entiers de 8 jusqu’à 23 (bornes incluses) en utilisant un for.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation rudimentaire d'un for.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int i;
for (i=8;i<=23;i++) cout<<i<<endl;
return 0;
}
EXERCICE 3
modifierMême exercice mais en utilisant un while
.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation rudimentaire d'un
while
.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int i=8;
while(i<=23)
{
cout<<i<<endl;
i++;
}
return 0;
}
EXERCICE 4
modifierÉcrire un programme qui demande à l’utilisateur de taper 10 entiers et qui affiche leur somme.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation d'un for.
- Étude d'un algorithme usuel : calcul d'une somme.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int i, s=0, x;
for(i=0; i<10; i++)
{
cout<<"Tapez un entier : "; cin>>x;
s = s + x;
}
cout<<"La somme vaut : "<<s<<endl;
return 0;
}
EXERCICE 5
modifierÉcrire un programme qui demande à l’utilisateur de taper 10 entiers et qui affiche le plus petit de ces entiers.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int i, ppt, x;
for(i=0; i<10; i++)
{
cout<<"Tapez un entier : "; cin>>x;
if(i==0) ppt=x; else if(x<ppt) ppt=x;
}
cout<<"Le plus petit vaut : "<< ppt <<endl;
return 0;
}
EXERCICE 6
modifierÉcrire un programme qui demande à l'utilisateur de taper un entier N et qui calcule la somme des cubes de 5^3 à N^3.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation simple du for.
- Étude d'un algorithme usuel : calcul d'une somme.
- Modélisation d'un problème simple issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int N,s=0,i;
cout<<"Tapez la valeur de N : "; cin>>N;
for (i=5 ; i<=N ; i++)
s = s + i*i*i;
cout<<"La somme vaut : "<<s<<endl;
return 0;
}
EXERCICE 7
modifierÉcrire un programme qui demande à l'utilisateur de taper un entier N et qui calcule u(N) défini par :
u(0)=3
u(n+1)=3.u(n)+4
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation simple du for.
- Étude d'un algorithme usuel : calcul des termes d'une suite récurrente.
- Modélisation d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int i, u=3, N;
cout<<"Tapez N : "; cin>>N;
for(i=0; i<N; i++)
u=u*3+4;
cout << "u(" << N << ")=" << u << endl;
return 0;
}
EXERCICE 8
modifierÉcrire un programme qui demande à l'utilisateur de taper un entier N et qui calcule u(N) défini par :
u(0)=1
u(1)=1
u(n+1)=u(n)+u(n-1)
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation simple du for.
- Étude d'un algorithme usuel : calcul d'une suite récurrente.
- Modélisation d'un problème simple issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int nmax;
double un=1,unp1=1,unp2;
cout << " Entrez n_max " ; cin >> nmax;
cout << "La valeur de u(" << 0 <<") = " << un << endl;
cout << "La valeur de u(" << 1 <<") = " << unp1 << endl;
for(int n=2; n<nmax+1; n++)
{
unp2 = un + unp1;
cout << "La valeur de u(" << n <<") = " << unp2 << endl;
un = unp1;
unp1 = unp2;
}
return 0;
}
EXERCICE 9
modifierÉcrire un programme qui demande à l’utilisateur de taper un entier N entre 0 et 20 bornes incluses et qui affiche N+17. Si on tape une valeur erronée, il faut afficher "erreur" et demander de saisir à nouveau l'entier.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation simple du
while
. - Validation des données saisies par l'utilisateur.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int N;
bool ok;
do {
cout<<"Tapez N entre 0 et 20 :"; cin>>N;
ok = (N<=20 && N>=0);
if(!ok)cout<<"ERREUR RECOMMENCEZ"<<endl;
}while(!ok);
N = N + 17;
cout<<"La valeur finale est : "<<N<<endl;
return 0;
}
EXERCICE 10
modifierÉcrire un programme qui permet de faire des opérations sur un entier (valeur initiale à 0). Le programme affiche la valeur de l'entier puis affiche le menu suivant :
1. Ajouter 1
2. Multiplier par 2
3. Soustraire 4
4. Quitter
Le programme demande alors de taper un entier entre 1 et 4. Si l'utilisateur tape une valeur entre 1 et 3, on effectue l'opération, on affiche la nouvelle valeur de l'entier puis on réaffiche le menu et ainsi de suite jusqu'à ce qu'on tape 4. Lorsqu'on tape 4, le programme se termine.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation du
while
. - Utilisation du
switch
. - Gestion d'un programme à l'aide d'un menu.
- Modélisation d'un problème simple sous forme informatique.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int x=0,choix;
cout<<"La valeur initale vaut "<< x << endl;
do{
cout << "---- Menu ----" << endl;
cout<<"1 : Ajouter 1"<<endl;
cout<<"2 : Multiplier par 2"<<endl;
cout<<"3 : Soustraire 4"<<endl;
cout<<"4 : Quitter"<<endl;
cout<<"Quel est votre choix : "; cin>>choix;
switch(choix)
{
case 1: cout << x << " + 1 = ";
x++; break;
case 2: cout << x << " * 2 = ";
x=x*2; break;
case 3: cout << x << " - 4 = ";
x=x-4; break;
default:
cout << "Ce n'est pas un option valide" << endl;
cout << " on a toujours x = ";
}
cout << x << endl;
} while(choix!=4);
cout<<"La valeur finale de x vaut : "<<x<<endl;
return 0;
}
EXERCICE 11
modifierÉcrire un programme qui demande à l'utilisateur de taper des entiers strictement positifs et qui affiche leur moyenne. Lorsqu'on tape une valeur négative, le programme affiche ERREUR et demande de retaper une valeur. Lorsqu'on tape 0, cela signifie que le dernier entier a été tapé. On affiche alors la moyenne. Si le nombre d'entiers tapés est égal à 0, on affiche PAS DE MOYENNE.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation d'un
while
de difficulté moyenne. - Étude d'un algorithme usuel : calcul d'une moyenne.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{ int cmpt = 0;
double moy=0,entree;
do
{
cout << "Entrez une valeur positif (0 pour sortir) "; cin >> entree;
if(entree > 0) {moy = moy + entree; cmpt++;}
else if(entree < 0) cout << "Erreur! valeur positif !" << endl;
} while(entree != 0);
if(cmpt==0) cout << "Pas de moyenne " << endl;
else
{
moy = moy / cmpt;
cout << "La moyenne des " << cmpt << " valeurs = " << moy << endl;
}
return 0;
}
EXERCICE 12
modifierÉcrire un programme qui demande à l'utilisateur de taper un entier N et qui calcule u(N) défini par :
u(0)=3
u(1)=2
u(n)=n.u(n-1)+(n+1).u(n-2)+n
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation d'un
for
. - Étude d'un algorithme usuel : calcul d'une suite récurrente assez difficile.
- Modélisation d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
int main()
{ int N, n=0;
double unm2, unm1, un;
cout<<"Tapez la valeur de N : "; cin>>N;
unm2 = 3;
unm1 = 2;
if (N==0) un=unm2;
else if (N==1) un=unm1;
else for (n=2; n <= N; n++)
{ un=n*unm1+(n+1)*unm2+n; unm2=unm1; unm1=un;
cout<<"u("<< n << ")="<< un <<endl;
}
cout<<"u("<< N <<")=" << un << endl;
return 0;
}
EXERCICE 13
modifierÉcrire un programme qui demande de saisir 10 entiers et qui affiche le nombre d'occurrences de la note la plus haute.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation d'un for.
- Étude d'un algorithme usuel de difficulté moyenne : calcul du nombre d’occurrence d'une valeur.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{ const int N=10;
int cmpt=1,entree,maxi;
cout << " Entree un entier "; cin >> entree;
maxi = entree;
for(int i=0; i < N; i++)
{
cout << " Entree un entier "; cin >> entree;
if(entree == maxi) cmpt++;
else if(entree > maxi) {maxi=entree;cmpt=1;}
}
cout << "La plus grande des " << N << " valeurs = " << maxi
<< " avec une occurrence de " << cmpt << " fois " << endl;
return 0;
}
EXERCICE 14
modifierÉcrire un programme qui demande de saisir un entier N et qui affiche N!.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation du for.
- Étude d'un algorithme usuel : calcul d'une factorielle.
- Modélisation d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int n ;
double fact = 1;
cout << "Tapez un entier : "; cin>>n;
for(int i=2; i<=n; i++) fact=fact*i;
cout << "Le factoriel " << n << "! = " << fact <<endl;
return 0;
}
EXERCICE 15
modifierÉcrire un programme qui demande de saisir un entier et qui indique si cet entier est premier ou non.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation d'un while de difficulté moyenne.
- Étude d'un algorithme usuel assez difficile : primarité d'un entier.
- Modélisation d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int n;
bool premier = true;
int d = 2;
cout<<"Veuillez saisir un entier : "; cin>>n;
if (n<=1) premier = false;
else {
while(premier==true && d*d<=n)
if(n%d==0) premier=false; else d=d+1;
}
if(premier) cout<<n<<" est premier"<<endl;
else cout<<n<<" n'est pas premier"<<endl;
return 0;
}
EXERCICE 16
modifierÉcrire un programme qui demande à l'utilisateur de saisir un entier N et qui affiche le nombre de nombres premiers inférieurs ou égaux à N.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation des boucles
for
etwhile
. - Imbrication de boucles.
- Lire précisément un énoncé.
- Modélisation assez complexe d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int N, i, nb=0, d;
bool est_premier;
cout<<"Tapez la valeur de N : "; cin>>N;
for(i=2;i<=N;i++)
{
/* ecrire un programme qui teste si i est premier */
est_premier=true;
d=2;
while(est_premier && d*d<=i)
if(i%d==0) est_premier=false; else d++;
if(est_premier==true) nb++;
}
cout<<"Le nombre de nombre premiers inférieurs ou égaux à "
<<N<<" est "<<nb<<endl;
return 0;
}
EXERCICE 17
modifierÉcrire un programme qui demande à l'utilisateur de saisir un entier N et qui affiche le N-ième nombre premier.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation des boucles
for
etwhile
. - Imbrication de boucles assez complexe.
- Lire précisément un énoncé.
- Modélisation assez complexe d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int N, i=1, nb=0, d;
bool est_premier;
cout<<"Tapez la valeur de N : "; cin>>N;
while(nb<N)
{
i++;
est_premier=true;
d=2;
while(est_premier && d*d<=i)
if(i%d==0)est_premier=false; else d++;
if(est_premier==true) nb++;
}
cout<<"Le N-ième nombre premier est "<<i<<endl;
return 0;
}
EXERCICE 18
modifierÉcrire un programme qui demande à l'utilisateur de saisir un entier N et qui affiche la figure suivante.
N=1 * N=2 ** * N=3 *** ** *
et ainsi de suite
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation des boucles
for
. - Imbrication de boucles assez complexe.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int N;
cout<<"Tapez la valeur de N : "; cin>>N;
for(int i=1;i<=N;i++)
{
for(int j=1; j<=N+1-i; j++) cout<<"*";
cout<<endl;
}
return 0;
}
EXERCICE 19
modifierÉcrire un programme qui demande à l'utilisateur de saisir un entier N et qui affiche la figure suivante.
N=1 * N=2 ** * N=3 *** ** *
et ainsi de suite.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation des boucles
for
. - Imbrication de boucles.
- Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int N;
cout<<"Tapez la valeur de N : "; cin>>N;
for(int i=1; i<=N; i++)
{
for(int j=1; j<i; j++) cout<<" ";
for(int j=1; j<=N+1-i; j++) cout<<"*";
cout<<endl;
}
return 0;
}
EXERCICE 20
modifierOn considère la suite hongroise :
u(0)=a (a entier)
si u(n) pair alors u(n+1)=u(n)/2 sinon u(n+1)=3*u(n)+1
Pour toutes les valeurs a, il existe un entier N tel que u(N)=1 (conjecture admise).
a) Écrire un programme qui demande à l'utilisateur de taper a et qui affiche toutes les valeurs de u(n) de n=1 à n=N.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation du
while
. - Lire précisément un énoncé.
- Modélisation assez complexe d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int a, n, u;
cout<<"Tapez la valeur de a : "; cin>>a;
n = 0;
u = a;
while(u!=1)
{
if(u%2==0) u=u/2; else u=3*u+1;
n++;
cout<<"u("<<n<<")="<<u<<endl;
}
return 0;
}
b) Écrire un programme qui demande à l'utilisateur de taper un entier M puis qui cherche la valeur de a comprise entre 2 et M qui maximise la valeur de N. On appelle A cette valeur. Le programme doit afficher la valeur A et la valeur N correspondante.
Cet exercice a pour but de vérifier les points techniques suivants :
- Utilisation du
while
et dufor
. - Imbrication de boucles.
- Lire précisément un énoncé.
- Modélisation assez complexe d'un problème issu des mathématiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int a,n,u,M, amax,nmax;
cout<<"Tapez la valeur de M : "; cin>>M;
amax = 2;
nmax = 2;
for(a=3; a<=M; a++)
{
n = 0;
u = a;
while (u!=1)
{
if(u%2==0) u=u/2; else u=3*u+1;
n++;
}
if (n>nmax) { amax=a; nmax=n; }
}
cout<<"La valeur de A est :"<< amax <<endl;
cout<<"La valeur de N correspondante est :"<< nmax <<endl;
return 0;
}
Les structures de contrôles/Exercices if...else et switch
Exercices
modifierEXERCICE 1
modifierÉcrire un programme qui résout l' équation AX+B=0. Bien évidemment, on n'oubliera pas tous les cas particuliers (notamment les cas "tout x est solution" et "pas de solution").
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- Modélisation d'un problème mathématique simple.
- Utilisation de if imbriqués.
- L'utilisation de
cin
et decout
. - L'affectation.
Voici le fichier source :
#include <iostream>
using namespace std;
int main()
{ double a,b,x=0;
cout <<"Entrer la valeur de a" << endl; cin >> a ;
cout <<"Entrer la valeur de b" << endl; cin >> b ;
cout << " On trouve la solution de " << a << "x + " << b << " = 0 " << endl;
if (a==0)
if (b==0) cout << " tout est solution" << endl;
else cout << " pas de solution" << endl;
else
{
x = -b/a ;
cout << " La solution est x = " << x << endl;
}
return 0;
}
EXERCICE 2
modifierÉcrire un programme qui demande à l'utilisateur de taper 5 entiers et qui affiche le plus grand. Le programme ne devra utiliser que 2 variables.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- Recherche du plus grand élément.
- Utilisation du if.
- L'utilisation de
cin
et decout
. - L'affectation.
Voici le fichier source :
#include <iostream>
// Exercice 8 Chapitre 2
using namespace std;
int main()
{
int a,maxv;
cout << "Tapez la valeur numéro 1 : "; cin>>a; maxv=a;
cout << "Tapez la valeur numéro 2 : "; cin>>a; if(a>maxv) maxv=a;
cout << "Tapez la valeur numéro 3 : "; cin>>a; if(a>maxv) maxv=a;
cout << "Tapez la valeur numéro 4 : "; cin>>a; if(a>maxv) maxv=a;
cout << "Tapez la valeur numéro 5 : "; cin>>a; if(a>maxv) maxv=a;
cout << "Le plus grand valeur vaut : " << maxv << endl;
return 0;
}
EXERCICE 3
modifierÉcrire un programme qui résout l’équation ax2+bx+c=0 en envisageant tous les cas particuliers.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - L'affectation.
- Évaluation d'expressions "complexes".
- Utilisation de la bibliothèque cmath.
- Structure de
if... else
imbriquées avec de nombreux cas particuliers. - Proposition d'un jeu de tests complet pour gérer ces cas particuliers.
- Modélisation d'un problème issu des mathématiques.
Voici le fichier source :
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
double a, b, c, re1, re2, im, delta;
cout << "Entrez a : " ; cin >> a ;
cout << "Entrez b : " ; cin >> b ;
cout << "Entrez c : " ; cin >> c ;
cout << "Ce programme trouve la solution de " << a << "x^2 + "
<< b << " x + " << c << " = 0 " << endl << endl;
if (a == 0) // Il s'agit d'une équation linéaire
{
if (b!=0)
{
re1 = -c/b ;
cout << "la solution est " << re1 << endl;
} // Équation linéaire
else if (c==0) cout << " Tout x est solution " << endl;
else cout << " Il n'y a pas de solution " << endl;
}
else // Il s'agit d'une équation quadratique
{
delta = (b*b)-(4*a*c);
if(delta > 0) // Il y a deux solutions réelles
{
re1 = (-b-sqrt(delta))/(a*2);
re2 = (-b+sqrt(delta))/(a*2);
cout << "Les solutions réelles sont : " << re1 << " et " << re2 << endl;
}
else if(delta < 0) // Il y a deux solutions complexes
{
re1 = -b/(a*2);
im = sqrt(-delta)/(a*2);
cout << "Les solutions complexes sont : " << re1 << " + i" << im <<
" et z2 = " << re1 << " - i" << im << endl;
}
else // on arrive ici seulement si delta == 0
{
re1 = -b/(2*a);
cout << "il y a 1 solution : = " << re1 << endl ;
}
}
return 0;
}
EXERCICE 4
modifierÉcrire un programme qui demande à l’utilisateur de saisir les coordonnées de 4 points A, B, C et D puis qui affiche les informations suivantes : * si A et B sont confondus, on affiche ‘A et B sont confondus’ ;
- si C et D sont confondus, on affiche ‘ C et D sont confondus’ ;
ù si A et B ne sont pas confondus et si C et D ne sont pas confondus , on affiche soit ‘AB et CD sont parallèles’, soit 'AB et CD sont confondues', soit ‘AB et CD sont sécantes’. Dans ce dernier cas, on affiche les coordonnées de l'intersection de AB et de CD.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - L'affectation.
- Structure de
if... else
imbriquées. - Modélisation d'un problème issu de la géométrie sous forme informatique.
- Traiter de manière informatique une suite de calculs mathématiques relativement longue *comportant de nombreux cas particuliers.
- Proposition d'un jeu de tests complet pour gérer ces cas particuliers.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
double XA,YA,XB,YB,XC,YC,XD,YD,XI,YI;
double DX1,DY1,R1,DX2,DY2,R2,K;
cout << "Tapez l'abscisse de A : "; cin >> XA;
cout << "Tapez l'ordonnée de A : "; cin >> YA;
cout << "Tapez l'abscisse de B : "; cin >> XB;
cout << "Tapez l'ordonnée de B : "; cin >> YB;
cout << "Tapez l'abscisse de C : "; cin >> XC;
cout << "Tapez l'ordonnée de C : "; cin >> YC;
cout << "Tapez l'abscisse de D : "; cin >> XD;
cout << "Tapez l'ordonnée de D : "; cin >> YD;
DX1 = XB - XA;
DY1 = YB - YA;
DX2 = XD - XC;
DY2 = YD - YC;
if (DX1==0 && DY1==0)
cout << "A et B sont confondus" << endl;
if (DX2==0 && DY2==0)
cout << "C et D sont confondus" << endl;
else
{
K=DX1*DY2-DX2*DY1;
if (K==0)
{
if (DX1*(YC-YA)==DY1*(XC-XA))
cout << "AB et CD sont confondues." << endl;
else cout << "AB et CD sont parallèles." << endl;
}
else
{
cout<<"Les droites sont sécantes."<<endl;
R1=YA*XB-XA*YB;
R2=YC*XD-XC*YD;
XI=(R1*DX2-R2*DX1)/K;
YI=(R1*DY2-R2*DY1)/K;
cout<<"L'abscisse de l'intersection est : "<<XI<<endl;
cout<<"L'ordonnée de l'intersection est : "<<YI<<endl;
}
}
return 0;
}
EXERCICE 5
modifierÉcrire un programme qui demande à l’utilisateur de saisir un entier X et qui affiche la valeur absolue de X.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - Utilisation rudimentaire du if.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int X;
cout<<"Tapez la valeur de X :"; cin>>X;
if (X<0) X = -X;
cout<<"La valeur absolue de X est : "<<X<<endl;
return 0;
}
</pre>
}}
=== EXERCICE 6 ===
Écrire un programme qui demande à l’utilisateur de saisir deux entiers strictement positifs A et B et qui indique si A est divisible par B.
{{Boîte déroulante|titre=Solution|contenu=
Cet exercice a pour but de vérifier les points techniques suivants :
*La notion de variables et leur déclaration.
*L'utilisation de <code>cin</code> et de <code>cout</code>.
*Utilisation du <code>if...else...</code>.
*Validation de données.
Voici le fichier source :
<syntaxhighlight lang="cpp">
#include<iostream>
using namespace std;
int main()
{
int A,B;
cout<<"Tapez la valeur de A : ";cin>>A;
cout<<"Tapez la valeur de B : ";cin>>B;
if (A<=0) cout<<"A n'est pas valide !"<<endl;
else if (B<=0) cout<<"B n'est pas valide !"<<endl;
else if (A%B==0) cout<<A<<" est divisible par "<<B<<endl;
else cout<<A<<" n'est pas divisible par "<<B<<endl;
return 0;
}
EXERCICE 7
modifierÉcrire un programme qui demande à l’utilisateur de saisir 3 entiers A, B et C et qui indique si C est compris entre A et B, bornes incluses.
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - Utilisation du
if...else...
. - Utilisation des opérateurs logiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int A,B,C;
cout<<"Tapez la valeur de A : ";cin>>A;
cout<<"Tapez la valeur de B : ";cin>>B;
cout<<"Tapez la valeur de C : ";cin>>C;
if( (C>A && C<B)||(C<A && C>B)) cout<<"C est compris entre A et B"<<endl;
else cout<<"C n'est pas compris entre A et B"<<endl;
return 0;
}
EXERCICE 8
modifierÉcrire un programme qui demande à l’utilisateur de saisir 4 entiers A, B, C et D, puis qui indique quelle est l'intersection des intervalles [AB] et [CD].
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - Utilisation du
if...else...
. - Utilisation des opérateurs logiques.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
double a=2,b=10,c=-2,d=5,minintv,maxintv;
cout << "Le premier intervalle [A , B] : ";
cout << "Tapez la valeur de A : "; cin >> a;
cout << "Tapez la valeur de B : "; cin >> b;
cout << "Le deuxième intervalle [C , D] : ";
cout << "Tapez la valeur de C : "; cin >> c;
cout << "Tapez la valeur de D : "; cin >> d;
// Attention ce programme ne fonctionne correctement que si b > a et d > c
// Ce défaut peut être corrigé : comment ?
if(a > c) minintv = a; else minintv = c;
if(b < d) maxintv = b; else maxintv = d;
if (minintv > maxintv) cout << "L'intersection est vide." << endl;
else if (minintv == maxintv) cout << "L'intersection ne comporte qu'une valeur :" << minintv << endl;
else cout << "L'intersection des intervalles [" << a << ";" << b << "]"
<< " et [" << c << ";" << d << "] est l'intervalle [" << minintv << ";" << maxintv << "]" << endl;
return 0;
}
EXERCICE 9
modifierÉcrire un programme qui demande à l’utilisateur de saisir un entier A puis qui affiche "ERREUR" si A n'est pas un nombre impair compris entre 83 et 101 bornes incluses. Dans le cas contraire, on affiche "PAS D'ERREUR".
Cet exercice a pour but de vérifier les points techniques suivants :
- La notion de variables et leur déclaration.
- L'utilisation de
cin
et decout
. - Utilisation du
if...else...
. - Utilisation des opérateurs logiques.
- Proposition d'un jeu de tests complet.
Voici le fichier source :
#include<iostream>
using namespace std;
int main()
{
int A;
cout<<"Tapez la valeur de A : ";cin>>A;
if (A%2==0 || A<83 || A>101) cout<<"ERREUR"<<endl;
else cout<<"PAS D'ERREUR"<<endl;
return 0;
}
Les fonctions
Utilisation des fonctions
modifierLe C++ est un langage procédural (entre autres paradigmes), on peut définir des fonctions qui vont effectuer une certaine tâche. On peut paramétrer des fonctions qui vont permettre de paramétrer cette tâche et rendre ainsi les fonctions réutilisables dans d'autres contextes.
Exemple général trivial :
#include <iostream>
using namespace std;
int doubleur(int a_locale_doubleur) // Mise en évidence facultative mais très pédagogique de la portée locale de la variable.
{
return 2*a_locale_doubleur;
}
int main()
{
int nombre = 21;
cout << doubleur(nombre) << endl; // 42.
return 0;
}
Compléments indispensables pour les débutants
modifier- On indique devant le nom de la fonction la nature du
return
:int
,string
,char
,double
... (void
en l'absence dereturn
). - Les variables définies localement restent locales. On peut ajouter au nom de la variable un affixe comme au début de la page, avec par exemple
_locale_
et le nom de la fonction, pour plus de facilité de relecture pour les grands débutants, mais cela n'est pas habituel. - Une fonction pourra appeler d'autres fonctions et ainsi de suite.
- Une fonction peut même s'appeler elle-même : on parle alors de fonctions récursives.
- Limitations ("Ce ne sont pas des bugs, ce sont des fonctionnalités !") actuelles du C++ :
- Le
return
peut être un tableau (c'est-a-dire que la fonction peut renvoyer un tableau). - Le
return
renvoie une seule valeur.
- Le
Prototype d'une fonction
modifierLe prototype va préciser le nom de la fonction, donner le type de la valeur de retour de la fonction (void
quand il n'y a pas de retour), et donner les types des paramètres éventuels, ainsi que leurs éventuelles valeurs par défaut. Le prototype d'une fonction est facultatif. Si on ne met pas de prototype, on devra rédiger la fonction avant le main, au lieu de la rédiger après ou dans un fichier séparé (extension .h
).
syntaxe
modifiertype identificateur(paramètres);
Exemple
modifier// prototype de la fonction f :
double f(double x,double y);
Rôle
modifierLe rôle d'un prototype n'est pas de définir les instructions de la fonction, mais donner sa signature. Il est utilisé pour spécifier que la fonction existe, et est implémentée ailleurs (dans un autre fichier, une librairie, ou à la fin du fichier source).
On les trouve principalement dans les fichiers d'en-tête (extension .h
).
Définition d'une fonction
modifierLa définition va reprendre le prototype mais va préciser cette fois le contenu de la fonction (le corps).
Syntaxe
modifiertype identificateur(paramètres) { ... Liste d'instructions ... }
Exemple
modifier#include <iostream>
using namespace std;
// définition de la fonction f :
double f(double x, double y)
{
double a;
a = x*x + y*y;
return a;
}
int main()
{
double u, v, w;
cout << "Tapez la valeur de u : "; cin >> u;
cout << "Tapez la valeur de v : "; cin >> v;
w = f (u, v); //appel de notre fonction
cout << "Le résultat est " << w << endl;
return 0;
}
Exemple avec prototype
modifier#include <iostream>
using namespace std;
// prototype de la fonction f :
double f(double x, double y);
int main()
{
double u, v, w;
cout << "Tapez la valeur de u : "; cin >> u;
cout << "Tapez la valeur de v : "; cin >> v;
w = f (u, v); //appel de notre fonction
cout << "Le résultat est " << w << endl;
return 0;
}
// définition de la fonction f :
double f(double x, double y)
{
double a;
a = x*x + y*y;
return a;
}
Dans cet exemple, le prototype est nécessaire, car la fonction est définie après la fonction main
qui l'utilise. Si le prototype est omis, le compilateur signale une erreur.
Portée des variables
modifierPrésentation
modifierUne fonction peut accéder :
- à ses différents paramètres,
- à ses variables définies localement,
- aux variables globales (on évitera au maximum d'utiliser de telles variables).
On appelle environnement d'une fonction l'ensemble des variables auxquelles elle peut accéder. Les différents environnements sont donc largement séparés et indépendants les uns des autres. Cette séparation permet de mieux structurer les programmes.
Exemple
modifier#include <iostream>
using namespace std;
int b; // variable globale
double f(double x, double y)
{
double a; // variable locale à la fonction f
a = x*x + y*y;
return a;
}
double g(double x)
{
int r, s, t; // variables locales à la fonction g
/* ... */
}
int main()
{
double u, v, w; // variables locales à la fonction main
/* ... */
return 0;
}
b
est une variable globale (à éviter : une structure la remplace avantageusement !).f
peut accéder- à ses paramètres
x
ety
. - à sa variable locale
a
. - à la variable globale
b
.
- à ses paramètres
g
peut accéder- à son paramètre
x
. - à ses variables locales
r
,s
ett
. - à la variable globale
b
.
- à son paramètre
- la fonction
main
peut accéder- à ses variables locales
u
,v
etw
. - à la variable globale
b
.
- à ses variables locales
Passage de paramètres par pointeur
modifierPasser un paramètre par pointeur permet de modifier la valeur pointée en utilisant l'opérateur de déréférencement *
.
Pour passer un pointeur comme argument de fonction, il faut en spécifier le type dans la définition de la fonction et (si pertinent), dans le prototype de la fonction, comme ceci :
void passagePointeur(int *); // Prototype d'une fonction renvoyant void
// et prenant comme argument un pointeur vers un int
void passagePointeur(int * ptr) // Définition d'une fonction renvoyant void
// et prenant comme argument un pointeur vers un int appelé ptr
{
/* ... */
}
De même, il faut, lors de l'appel de fonction, non pas spécifier le nom de la variable comme lors d'un passage par valeur, mais son adresse. Pour ceci, il suffit de placer le signe & devant la variable.
int a = 5; // Initialisation d'une variable a, de valeur 5
passageValeur( a ); // Appel d'une fonction par valeur
passagePointeur( &a ); // Appel d'une fonction par pointeur
Notez donc le & devant la variable, ceci a pour effet de passer l'adresse mémoire de la variable.
Examinons le programme simple suivant :
#include <iostream>
using std::cout;
void passagePointeur(int *);
void passageValeur(int);
int main()
{
int a = 5;
int b = 7;
cout << "a : " << a << endl;
cout << "b : " << b; // Affiche les deux variables
passageValeur (a); // Appel de la fonction en passant la variable a par valeur
// Une copie de la valeur est transmise à la fonction
passagePointeur (&b); // Appel de la fonction en passant l'adresse de la variable b par pointeur
// Une copie de l'adresse est transmise à la fonction
cout << endl;
cout << "a : " << a << endl;
cout "b : " << b; // Réaffiche les deux variables
system ("PAUSE");
return 0;
}
void passagePointeur(int * ptr)
{
int num = 100;
cout << endl;
cout "*ptr : " << *ptr; // Affiche la valeur pointée
* ptr = 9; // Change la valeur pointée;
ptr = # // <-- modification de l'adresse ignorée par la fonction appelante
}
void passageValeur(int val)
{
cout << endl;
cout "val : " << val;
val = 12; // <-- modification de la valeur ignorée par la fonction appelante
}
On affiche les deux variables puis on appelle les deux fonctions, une par valeur et une par pointeur. Puis on affiche dans ces deux fonctions la valeur et on modifie la valeur. De retour dans main, on réaffiche les deux variables a et b. a, qui a été passée par valeur, n'a pas été modifiée et a toujours sa valeur initiale (5), spécifiée à l'initialisation. Or, b n'a plus la même valeur que lors de son initialisation, 7, mais la valeur de 9. En effet, lors d'un appel par valeur, une copie de cette valeur est créée, donc lorsqu'un appel de fonction par valeur est effectué, on ne modifie pas la valeur d'origine mais une copie, ce qui fait que lorsqu'on retourne dans la fonction d'origine, les modifications effectuées sur la variable dans la fonction appelée ne sont pas prises en comptes. En revanche, lors d'un appel par pointeur, il s'agit de la variable elle même qui est modifiée (puisqu'on passe son adresse). Donc si elle est modifiée dans la fonction appelée, elle le sera également dans la fonction appelante.
Les avantages sont que cet appel nécessite moins de charge de travail pour la machine. En effet, par valeur, il faut faire une copie de l'objet, alors que par pointeur, seule l'adresse de l'objet est copiée. L'inconvénient, c'est qu'on peut accidentellement modifier dans la fonction appelée la valeur de la variable, ce qui se répercutera également dans la fonction appelante. La solution est simple : il suffit d'ajouter const dans le prototype de fonction de cette manière :
void passagePointeur(const int *);
// En d'autres termes, un pointeur vers un int constant
ainsi que la définition de fonction comme ceci :
void passagePointeur(const int * ptr)
{
// * ptr = 9; // <- maintenant interdit
}
En recompilant le programme mis plus haut avec ces deux choses, on constate un message d'erreur indiquant que l'on n'a pas le droit de modifier une valeur constante.
Passage de paramètres par référence
modifierPasser un paramètre par référence a les mêmes avantages que le passage d'un paramètre par pointeur. Celui-ci est également modifiable. La différence est que l'opérateur de déréférencement n'est pas utilisé, car il s'agit déjà d'une référence.
Le passage de paramètre par référence utilise une syntaxe similaire au passage par pointeur dans la déclaration de la fonction, en utilisant &
au lieu de *
.
Par exemple :
void incrementer(int& value)
// value : référence initialisée quand la fonction est appelée
{
value++;
// la référence est un alias de la variable passée en paramètre
}
void test()
{
int a = 5;
cout << "a = " << a << endl; // a = 5
incrementer(a);
cout << "a = " << a << endl; // a = 6
}
Le paramètre ainsi passé ne peut être qu'une variable.
Sa valeur peut être modifiée dans la fonction appelée, à moins d'utiliser le mot const
:
void incrementer(const int& value)
{
value++; // <- erreur générée par le compilateur
}
void test()
{
int a = 5;
cout << "a = " << a << endl;
incrementer(a);
cout << "a = " << a << endl;
}
La question que l'on peut se poser est puisque le passage par référence permet de modifier le paramètre, pourquoi l'en empêcher, et ne pas utiliser un simple passage par valeur ?
La réponse se trouve lorsqu'on utilise des objets ou des structures.
Le passage par valeur d'un objet ou d'une structure demande une recopie de la valeur de ses membres (utilisant un constructeur de recopie pour les objets).
L'avantage de passer une référence est d'éviter la recopie. Le mot const
permet de garantir qu'aucun membre de l'objet ou de la structure n'est modifié par la fonction.
Pointeur de fonction
modifierUn pointeur de fonction stocke l'adresse d'une fonction, qui peut être appelée en utilisant ce pointeur.
La syntaxe de la déclaration d'un tel pointeur peut paraître compliquée, mais il suffit de savoir que cette déclaration est identique à celle de la fonction pointée, excepté que le nom de la fonction est remplacé par (* pointeur)
.
Obtenir l'adresse d'une fonction ne nécessite pas l'opérateur &
. Il suffit de donner le nom de la fonction seul.
Exemple :
#include <iostream>
#include <iomanip>
using namespace std;
int (* pf_comparateur)(int a, int b);
// peut pointer les fonctions prenant 2 entiers en arguments et retournant un entier
int compare_prix(int premier, int second)
{
return premier - second;
}
int main()
{
pf_comparateur = &compare_prix;
cout << "compare 1 et 2 : " << (*pf_comparateur)(1, 2) << endl;
return 0;
}
La syntaxe d'appel à une fonction par un pointeur est identique à l'appel d'une fonction classique.
Passage de fonctions en paramètre
modifierIl s'agit en fait de passer un pointeur de fonction.
Exemple :
void sort_array( int[] data, int count, int (* comparateur_tri)(int a, int b) );
Pour clarifier la lecture du code, il est préférable d'utiliser l'instruction typedef
:
typedef int (* pf_comparateur)(int a, int b);
void sort_array( int[] data, int count, pf_comparateur comparateur_tri );
Exercices
modifierExercice 1
modifierCréez une fonction factorielle
pour calculer la factorielle d'un entier n.
Elle retourne un entier long.
Créez une fonction non récursive.
La factorielle (noté ! en mathématiques) se définit ainsi :
!1 = 1 = 1
!2 = 2 x 1 = 2
!3 = 3 x 2 x 1 = 6
!4 = 4 x 3 x 2 x 1 = 24
long factorielle(int n)
{
long r = 1;
while (n-- > 0)
r *= n;
return r;
}
Exercice 2
modifierCréez une autre fonction factorielle
mais récursive cette fois.
Une fonction récursive est une fonction qui s'appelle elle-même.
!n = n x !(n - 1)
long factorielle(long n)
{
if (n == 1) return 1;
return n*factorielle(n - 1);
}
Exercice 3
modifierDonner la déclaration d'un pointeur fct
sur la dernière fonction factorielle
.
long (*fct)(long);
Exercice 4
modifierEn C++, les systèmes callback
sont des systèmes qui enregistrent des fonctions, afin de les exécuter en cas d'évènement (comme par exemple si on touche le clavier).
Albert n'est pas fort en maths. Il veut un système pour faire des racines carrées, des carrés, des cosinus et des factorielles. Il utilise l'environnement callback situé dans l'annexe 1. Cet environnement crée une structure f_maths, qui enregistre une fonction et son symbole mathématique. Pour utiliser sa calculatrice, il doit enregistrer correctement les fonctions dans le gestionnaire. Ensuite, le programme interroge l'utilisateur pour entrer le symbole de son opération. Puis, l'utilisateur entre sa valeur et le programme effectue l'opération. Compléter le programme en annexe avec la fonction main() uniquement.
#include <cmaths>
#include <stdio.h>
typedef double (*fct)(double);
int nb_fct;
struct f_maths { fct fonction; char symb; };
f_maths gestionnaire[10];
void initialiser(){ nb_fct = 0; }
double carré(double a) { return a * a; }
double factorielle(double n)
{
if (n == 1) return 1;
return n*factorielle(n - 1);
}
void enregistrer(fct f, char sy)
{
struct s;
s.fonction = f;
s.symb = sy;
gestionnaire[nb_fct++] = s;
}
void executer()
{
char sy;
double val;
printf("Entrer le symbole:\n");
scanf("%c", &sy);
printf("Entrer la valeur:\n");
scanf("%g", &val);
for(int i = 0; i < nb_fct; i++)
if (gestionnaire[i].symb == sy)
{
printf("Le résultat est %g.\n", (*gestionnaire[i