Big Tuto : Apprenez le C++

Chapitre 12 : Entrées / sorties - lecture et écriture de fichiers

Tutoriel présenté par : Robert Gillard (Gondulzac)
Date de publication : 22 mai 2016
Date de révision : -

 

     Préliminaires

   Dans le chapitre 1 de notre Big Tuto (Analyse d'un programme C++), nous avons introduit la notion d'entrées/sorties (§ 2, 4, et 5) à l'aide de quelques exemples.
   Si vous en sentez le besoin vous pouvez relire ces paragraphes car, avant de parler d'écriture et de lecture de fichiers, nous voudrions quelque peu compléter notre propos sur les entrés/sorties en général. wink Allez, en piste ! angel

 

Retrouvez les projets complets de ce chapitre :

  

 

   1 – Entrées / sorties ( flux de données )
      1.1 – Définition

   On peut considérer les entrées / sorties comme des flux de données s'écoulant dans un réservoir ou tampon. Ces flux de données proviennent de sources émettrices (clavier, console, disques, fichiers...) et sont envoyés vers des récepteurs tels les écrans, disques ou autres supports de stockage.

 

      1.2 – Implémentation des flux et tampons

   C++ s'occupe de la gestion des flux de données par une approche orientée objet (nous en parlerons bientôt wink) et utilise certaines classes pour cette gestion.
   Ainsi, la classe streambuf gère les tampons, la classe ios (input / output stream - rien à voir avec une certaine marque fruitée wink) est la classe de base des flux d'entées / sorties, et les classes istream et ostream, toutes deux dérivées de la classe ios, gèrent le comportement des flux I/O ( Entrées/sorties ou Input/Output en Anglais ).

 

      1.3 – Objets I/O standards

   Nous en avons parlé dans le chapitre 1 et nous les avons beaucoup utilisés dans nos exemples. Il s'agit des quatre objets suivants crées par la classe iostream : cin, cout, cerr, et clog.
   Ce dernier, que nous découvrons pour la première fois, gère les messages d'erreurs qui sont envoyés vers l'écran, transitent par un tampon et sont souvent redirigés vers un fichier journal wink. Les trois premiers sont connus du lecteur mais nécessitent quelques explications et exemples complémentaires.

 

   Nous avons déjà dit que la saisie d'une chaîne de caractères avec ''cin'' posait un problème d'affichage si des caractères ''espace'' faisaient partie de la chaîne et que, pour résoudre ce problème, il fallait utiliser l'instruction cin.getline().

   Jusqu'à présent, nous avons terminé une partie de nos exemples avec l'instruction cin.get(). La fonction get() est une fonction surchargée (voir chapitre 6 - § 2.3 ainsi que le chapitre 12 - § 4 wink) et dans cette forme sans paramètres, elle renvoie la valeur du caractère reçu. Jusqu'ici nous l'avons utilisée pour permettre à l'utilisateur de pouvoir lire les données à l'écran jusqu'à ce qu'il frappe une touche pour que le programme se termine. cheeky
   Cependant cette fonction peut-être utilisée dans un programme pour la saisie de données caractère par caractère. Elle peut alors prendre les formes surchargées sans paramètre, avec un paramètre ou avec deux paramètres. wink
   Nous allons voir trois exemples d'appels à la fonction get() dans leurs différentes formes surchargées. smiley

 

   Première forme de surcharge de get() : appel de la fonction sans paramètres
 

//Projet 074cin_get()
//Saisie d'une chaîne avec la forme surchargée de cin.get() sans paramètres
#include<iostream>
#include<conio.h>
 
using namespace std;
 
 
int main()
{
char caract(' ');
 
cout << "Entrez vos nom et prenom : ";
while (caract >> cin.get())
cout << caract;
 
_getch();
return 0;
}

 

   Dans cette première forme surchargée, nous saisissons tous les caractères des nom et prénom demandés jusqu'à ce que la touche ''return'' soit pressée. Attention que pour cette forme, la variable caract doit être initialisée sous peine de réclamation du compilateur (VS 2013). surprise Celui-là, quand-même... angry
 

   Seconde forme de surcharge de get() : appel de la fonction avec un paramètre :

 

//projet 075cin_get2()
//Saisie d'une chaine avec la forme surchargée de cin.get() comprenant un paramètre;
#include<iostream>
#include<conio.h>
 
using namespace std;
 
 
int main()
{
char caract, caract2, caract3;
 
//Seconde forme surchargée : appel de get() avec un paramètre
//Demande de plusieurs entrées
cout << endl;
 
cout << "entrez 3 lettres : ";
cin.get(caract).get(caract2).get(caract3);
cout << "vous avez entre " << caract << caract2 << caract3;
cout << endl;
 
_getch();
return 0;
}

 

   Nous utilisons ici la seconde forme surchargée de get(), mais une forme moins connue nous permet d'entrer aussi plusieurs caractères dans une forme un peu spéciale de cin.get() à qui l'on passe le même nombre de variables que de caractères désirés.

   Le résultat dans la console nous donne :


   Et jusqu'ici il n'y a rien de spécial à observer. indecision


   Troisième forme de surcharge de get() : appel de la fonction avec deux paramètres :

 

//projet 076cin_get3()
//Saisie d'une chaîne avec la forme surchargée de cin.get() comprenant deux paramètres;
#include<iostream>
#include<conio.h>
 
using namespace std;
using uint = unsigned long;
 
 
int main()
{
const uint longueurChaine = 128;
char chaine[longueurChaine];
 
//Seconde forme surchargée : appel de get() avec deux paramètres
cout << endl;
cout << "Entrez une chaine" << endl;
cin.get(chaine, longueurChaine);
 
cout << "vous avez entre :";
cout << chaine;
cout << endl << endl;
 
cout << "entrez une nouvelle chaine" << endl;
cin.get(chaine, longueurChaine);
 
cout << "vous avez entre :";
cout << chaine;
cout << endl << endl;
 
_getch();
return 0;
}

 

   Voyons d'abord le résultat de la console : 

 

   Ho ho, il semble y avoir un problème !? surprise Nous entrons la première chaîne comme demandé à l'écran et nous l'affichons. Nous voulons ensuite entrer une nouvelle chaîne dans la même variable à l'aide de cin.get() mais nous n'en avons pas le temps... blush Le programme se termine sans prévenir et attend que nous frappions une touche pour quitter. Qu'es-ce que cela veut dire ? angry

   Il se fait que cin.get() n'efface pas le tampon d'entrée une fois la saisie effectuée et il nous faut trouver un moyen d'entrer une seconde chaîne à l'écran. cheeky

   Il existe une autre fonction de ''cin'' qui permet d'ignorer les caractères qui restent sur une ligne jusqu'à la rencontre d'une fin de ligne (EOL) ou d'une fin de fichier (EOF). La fonction ignore() attend deux paramètres, le nombre de caractères à ignorer et le caractère de fin de ligne ('\n'). Utilisée sans paramètres, cette fonction efface complètement le buffer (tampon) d'entrée.

 

   Dans notre cas, il suffit d'ajouter l'instruction cin.ignore() avant de demander l'entrée d'une nouvelle chaîne, cela libérera le tampon d'entrée et nous permettra la saisie d'une seconde chaîne. wink

   Nous introduisons donc l'instruction cin.ignore() au bon endroit : 

cout << endl << endl;

cin.ignore();

cout << "entrez une nouvelle chaine" << endl;

 

   On compile à nouveau le programme et nous observons le nouveau résultat dans la console :

 

   Et voilà, maintenant c'est bon. angel

   Bien, vous pourriez me dire ''Mais ta variable ne garde que la dernière chaîne, la première est perdue !'' surprise Et vous auriez raison, aussi rien ne nous empêche de sauvegarder les chaînes saisies dans des variables de type string ou dans un vector. Imaginez un rpg dans lequel on vous demande de choisir (d'entrer) le nom de votre héros, vous pourriez utiliser cin.getline() ou cin.get() et transférer ce nom dans une chaîne de type string. Et ceci m'amène à une idée de petit exercice. wink

 

      Exercice 1 :

   En utilisant cin.get() comme nous venons de le faire ci-dessus, écrivez une fonction qui va permettre de saisir 5 chaînes de caractères de type ''char'' et renverra un pointeur sur un tableau de 5 chaînes de type ''string'' à lire dans la fonction main().


   1.4 – Deux autres méthodes de ''cin'' : peek() et putback()

   Si vous vous rappelez d'un petit challenge proposé dans le chapitre 8 avec son corrigé dans le chapitre 9, où il était demandé de remplacer tous les espaces de la chaîne de type ''string'' Katana tranchant du Samourai du Pont des Etoiles'' par un caractère de soulignement, vous aurez remarqué que cet exercice faisait appel aux itérateurs et à la fonction isspace() de la librairie cctype.

   Il se fait que ''cin'' propose deux méthodes de sa classe pour réaliser ce genre de transformation de caractères en ce qui concerne les chaînes de type ''char''. Il s'agit des méthodes peek() et putback()wink


   Nous allons voir un petit exemple de mise en œuvre de ces fonctions lors de la saisie d'une chaîne de caractères à l'écran. Les explications seront données par la suite.

 

//Projet 077peek_putback
 
//Modification de la saisie de caractères
//Utilisation des méthodes peek() et putback() de cin.
#include<iostream>
 
 
using namespace std;
using uint = unsigned int;
 
 
int main()
{
 
char caract(' ');
 
cout << endl;
cout << "Ce programme donnera comme resultat la chaine suivante :" << endl;
cout << "Le_Sorcier_du_Temple_de_la_Lune_Rousse" << endl << endl;
cout << "entrez exactement la chaine suivante :" << endl;
cout << "Le Sorcier du!Temple!de la!Lune!Rousse" << endl << endl;
int i = 0;
 
 
while ((caract = cin.get()) != EOF)
{
 
if (caract == ' ')
cin.putback('_');
 
else
cout << caract;
 
while (cin.peek() == '!')
{
cin.ignore(1, '!');
cin.putback(' ');
}
 
++i;
}
 
return 0;
}

 

   Ce qui donne comme résultat dans la console :

 

   La fonction putback() remplace un caractère entré au clavier et prend le caractère désiré en paramètre (le caractère lui-même ou sa valeur numérique). La fonction peek(), quant-à-elle, examine le caractère entré (ou sa valeur numérique) en vue d'une modification éventuelle.


   Voyons cela dans notre programme.

   On demande à l'utilisateur d'entrer très exactement la chaîne suivante :

Le Sorcier du!Temple!de la!Lune!Rousse

   La chaîne à obtenir est : Le_Sorcier_du_Temple_de_la_Lune_Rousse

   Chaque caractère est testé à l'entrée. Un caractère ''espace'' sera transformé en caractère de soulignement par la fonction putback('_') que nous aurions pu écrire sous la forme putback(char(95)), ce qui revient au même. Dans la boucle intérieure, la fonction peek() teste le caractère que nous recherchons, ici un point d'exclamation. Si le caractère est trouvé, la fonction ignore() est appelée pour le supprimer. Ce caractère doit cependant être remplacé par un caractère ''espace'' (qui est la condition de notre boucle extérieure) et qui lui-même sera remplacé par le caractère de soulignement pour obtenir le résultat final. Et puisque la boucle se fait jusqu'à EOF, nous quitterons le programme en pressant CTRL-Zcheeky

 

      Exercice 2 :

   Modifiez le projet 077peek_putback. La chaîne de départ à entrer au clavier sera :

Le_Sorcier_du!Temple!de_la!Lune!Rousse (cette fois les espaces sont remplacés par des caractères de soulignement)

   La chaîne à obtenir est : le sorcier du temple de la lune rousse (chaîne dans laquelle il faudra en plus remplacer les caractères majuscules par des caractères minuscules).

   Mais non, ce n'est pas sorcier à réaliser... devil

 

   1.5 – Fonctions de sortie : cout.put() et cout.write()

   La fonction cout.put() écrit un caractère unique vers le flux de sortie ostream. De même que ''cin'' peut enchaîner les entrées avec get(), cout nous permet également d'enchaîner les sorties avec put().

   Ce petit exemple affichera ''Bonjour'' à l'écran :

cout.put('B').put('o').put('n').put('j').put('o').put('u').put('r').put('\n');

 

   La fonction write(), quant-à-elle, agit de la même manière que ''<<'' mais demande le passage de deux paramètres, la chaîne à écrire ainsi que le nombre de caractères exact de cette chaîne. Le nombre de caractères peut-être inférieur au nombre de caractères réel mais dans ce cas seuls ces caractères seront affichés. wink


   Inversement, le nombre de caractères passés en paramètres peut être supérieur au nombre de caractères réel mais dans ce cas les caractères se trouvant en mémoire au delà de la chaîne à afficher seront également ''envoyés'' à l'écran, ce qui n'a bien entendu aucune raison d'être. cheeky

   Voyons ceci à l'aide d'un exemple : 

 

//projet 078cout_write()
//Affiche une chaîne à l'écran
//Utilisation de la méthode write() de cout.
#include<iostream>
#include<string>
 
using namespace std;
using uint = unsigned int;
 
 
int main()
{
char chaine[] ("Cette chaine est ecrite en entier");
uint longueurChaine = strlen(chaine);
 
cout << endl;
cout << "On passe la longueur reelle de la chaine en parametre de write()" << endl;
cout.write(chaine, longueurChaine) << endl << endl;
 
cout << "On enleve 6 unites a la longueur de la chaine" << endl;
cout.write(chaine, longueurChaine - 6) << endl << endl;
 
cout << "On ajoute 6 unites a la longueur de la chaine" << endl;
cout.write(chaine, longueurChaine + 6) << endl;
 
cin.get();
return 0;
}

 

   Avec comme résultat dans la console :

 

   Et pas besoin de commentaires pour cet exemple. Nous dirons simplement que cout.write() ne peut pas être utilisé avec une chaîne de type stringcheeky


   2 – Lecture et écriture dans les fichiers

   Pour lire et écrire dans un fichier, on utilise des objets des classes ''ifstream'' (entrées) et ''ofstream'' (sorties) qui dérivent toutes deux de la classe ''iostream'' que nous connaissons bien. wink

 

      2.1 – Ecriture de chaînes de type ''char'' dans un fichier

   Pour écrire dans un fichier, nous créons premièrement un objet ''ofstream'' à qui l'on passe un nom de fichier en paramètre. Ce nom de fichier comportera une extension d'un type de fichier sur lequel l'écriture peut se faire, soit par exemple ofstream writeFile(''UnFichier.txt'').

   Un exemple d'ouverture de fichier en écriture :

 

//projet 079writeFile
//Ouverture de fichier en écriture et écriture de caractères dans le fichier
#include<fstream>
#include<iostream>
#include<conio.h>
 
using namespace std;
 
 
int main()
{
char nom_Fichier[50];
char texte[255];
 
cout << endl;
cout << "Entrez le nom d'un fichier avec l'extension .txt" << endl;
cin >> nom_Fichier;
 
//Ouverture du fichier en écriture
ofstream writeFile(nom_Fichier);
cout << endl;
cout << "Entrez une ligne de texte a ecrire dans le fichier :" << endl;
 
cin.ignore();
 
//Saisie d'une ligne de texte
cin.getline(texte, 255);
cout << endl;
 
//Ecriture du texte dans le fichier
writeFile << texte << '\n';
 
cout << "Entrez une seconde ligne de texte :" << endl;
cin.getline(texte, 255);
 
//Ecriture du texte dans le fichier
writeFile << texte << '\n';
 
//Fermeture du fichier
writeFile.close();
 
cout << endl;
cout << "***End of File***" << endl;
 
_getch();
 
return 0;
}

 

   Remarquez que j'ai nommé l'objet ofstream ''writeFile'' pour bien indiquer que nous ouvrons un fichier en écriture mais vous pouvez donner le nom que vous voulez à cet objet. wink

   Bien, nous venons d'écrire nos premières lignes de texte dans un fichier. Avant de voir comment les relire à l'aide d'un programme, vérifiez que le fichier .txt que vous venez de créer se trouve bien dans le dossier de votre projet. wink Ouvrez-le et vous verrez les deux lignes de texte que nous avons saisies dans notre programme. angel

   Bien, nous pouvons maintenant examiner notre programme. Après avoir entré le nom d'un fichier, nous avons créé un objet ''ofstream'' afin d'ouvrir un fichier en écriture et nous l'avons dénommé ''writeFile''. Remarquez que l'instruction cin.ignore(), écrite avant la saisie de notre première ligne de texte est nécessaire. En effet, cette instruction supprime le caractère ''entrée'' qui restait dans le tampon ''texte'' lors de la saisie du nom de fichier. cheeky

 

   Finalement, après écriture des deux lignes de texte dans le fichier, nous devons fermer celui-ci à l'aide de la fonction close(). A ne pas oublier ! indecision

   Et le résultat dans la console :

 

     Voila pour ce qui est de l'écriture dans un fichier. Une fois le fichier écrit, nous devons relire ce que nous y avons inscrit et c'est ce que nous allons faire avec un exemple d'ouverture de fichier en lecture. wink

 

      2.2 – Lecture de chaînes de type ''char'' dans un fichier

   Pour lire le contenu d'un fichier nous créons premièrement un objet ''ifstream'' à qui l'on passe le nom de fichier en paramètre. Ce nom de fichier sera celui dans lequel nous avons écrit nos deux lignes de texte et notre instruction d'ouverture, par conséquent ifstream readFile(''monFichier.txt'');  - monFichier.txt étant le nom du fichier que nous avons utilisé pour écrire nos textes. wink

 

//projet 080readFile
//Ouverture de fichier en mode lecture et lecture de caractères dans le fichier
 
#include<fstream>
#include<iostream>
#include<conio.h>
 
using namespace std;
 
 
int main()
{
char nomFichier[50] = "monFichier.txt";
 
//Ouverture du fichier en lecture
ifstream readFile(nomFichier);
cout << endl;
cout << "Lecture du fichier " << nomFichier << " :" << endl << endl;
 
char caract;
//Lecture caractère par caractère
while (readFile.get(caract))
cout << caract;
 
//Fermeture du fichier
readFile.close();
 
cout << endl;
cout << "***End of File***" << endl;
 
_getch();
 
return 0;
}

 

   Ce qui donne comme résultat dans la console :

 

   Et vous n'aurez pas oublié, avant la lecture, de faire une copie de votre fichier à lire dans le dossier de votre nouveau projet sans quoi la console resterait désespérément vide... indecision


   Si votre fichier se trouve dans un dossier à un autre endroit du PC, vous devrez ajouter le chemin complet (path) au nom du fichier. wink

   Nous venons de voir la lecture et l'écriture de chaînes de caractères de type char. Nous allons maintenant faire la même chose avec des chaînes de type string en améliorant quelque peu nos programmes. angel

 

      2.3 - Ecriture de chaînes de type ''string'' dans un fichier 

 

//projet 081writeFileStrings
//Ecriture de chaînes de type string dans un fichier
#include<fstream>
#include<iostream>
#include<string>
#include<conio.h>
 
using namespace std;
 
int main()
{
string nomFichier;
string chaine;
 
cout << endl;
cout << "Entrez le nom d'un fichier avec l'extension .txt" << endl;
cin >> nomFichier;
cout << endl;
 
ofstream writeFile(nomFichier);
 
//Si le fichier a bien été crée...
if (writeFile)
{
cout << "Entrez une chaine a ecrire dans le fichier :" << endl;
 
//Saisie d'une chaine
cin.ignore();
getline(cin, chaine);
 
//Ecriture de la chaine dans le fichier
writeFile << chaine << endl;
 
cout << endl << endl;
cout << "Entrez une seconde chaine :" << endl;
 
getline(cin, chaine);
 
//Ecriture de la chaîne dans le fichier
writeFile << chaine << endl;
 
cout << endl;
cout << "***Le fichier a ete cree***" << endl;
 
//Fermeture du fichier
writeFile.close();
}
 
else
{
cout << "Erreur : le fichier n'a pu etre ouvert !" << endl;
}
 
_getch();
return 0;
}

 

   La différence entre ce projet et le projet 079writeFile est minime. Dans celui-ci, nous entrons deux chaînes de type string dans le fichier. Après l'ouverture du fichier en écriture, nous faisons un test pour voir si le fichier a bien été crée sinon une erreur est signalée.

 

   Remarquez cependant les formes d'acquisition des données selon les cas :

   Pour saisir un tableau de ''char'' à l'écran nous avons utilisé la forme :

cin.getline(chaine, longueurChaine);

   ...tandis que pour la saisie d'une chaîne de type string nous utilisons :

getline(cin, chaine);

 

   Le reste est à l'identique, ainsi que le résultat dans la console que nous ne reproduirons pas ici. wink

   Et de nouveau, nous devons relire ce que nous avons écrit dans notre fichier.

 

      2.4 - Lecture de chaînes de type ''string'' dans un fichier

   Nous utilisons bien sûr le même procédé que pour la lecture de chaînes de type ''char'' : création d'un objet ifstream auquel on passe le nom du fichier en paramètre mais au lieu d'utiliser l'instruction while (readFile.get(caract)) pour l'acquisition des données du fichier, nous utiliserons l'instruction while(getline(readFile, ligne)) pour une lecture ligne par ligne. 

 

//projet 082readFileStrings
//Lecture de chaînes de type string dans un fichier
#include<fstream>
#include<iostream>
#include<string>
#include<conio.h>
 
using namespace std;
 
int main()
{
string nom_Fichier = "monFichier2.txt";
string ligne;
 
cout << endl;
cout << "Lecture du fichier " << nom_Fichier << endl << endl;
ifstream readFile(nom_Fichier);
 
if (readFile)
{
//On lit le fichier ligne par ligne
while (getline(readFile, ligne))
cout << ligne << endl;
}
 
else
cout << "Erreur : le fichier n'a pu etre ouvert !" << endl;
 
cout << endl;
cout << "***End of File***" << endl;
 
//Fermeture du fichier
readFile.close();
 
_getch();
return 0;
}

 

   Et je pense que les commentaires sont inutiles en ce qui concerne ce programme, tout a été dit auparavant. Le résultat dans la console est semblable à celui du projet 080readFile.

 

      2.5 – Ajouter des données en fin de fichier

   C'est bien beau tout ça mais vous aurez remarqué que si vous voulez écrire de nouvelles données dans un fichier, si celui-ci existait déjà, vos anciennes données sont supprimées et remplacées par les nouvelles.
   Si vous voulez ajouter des données en fin de fichier sans écraser les anciennes, vous devrez ajouter un second paramètre à l'objet ofstream. Si vous prenez l'exemple du projet 081writeFileStrings, vous ajouterez ios::app lors de la demande d'ouverture du fichier. L'appel se faisant alors comme suit :

ofstream writeFile(nomFichier, ios::app);

   Nous ne donnerons pas d'exemple pour ceci, c'est tout ce qu'il faut modifier dans le programme pour ajouter des données à la fin d'un fichier existant. wink


   Et je sais que vous l'attendiez avec impatience... blush

   Allez, un petit challenge (Exercice 3) ! laugh

 

   Exercice 3.1

   Dans la fonction main(), saisissez un nom de fichier à l'écran (par exemple : Ennemis.txt) de type ''string''. Déclarez ensuite la chaîne suivante: 

const string mesEnnemis[] = { "Gobelin", "Orc", "Squelette", "Liche", "Elfe noir" };

 

   En vous référant au chapitre 10 (§ 2.2), vous déclarerez ensuite une référence sur un tableau de 5 chaînes. Créez ensuite deux fonctions, EcrisFich() et LisFich(). Dans la première fonction, vous passerez le nom du fichier à créer ainsi que la référence sur le tableau de 5 strings. La fonction LisFich(), quant-à-elle, sera appelée en lui passant le nom du fichier en paramètre. Cette fonction devra afficher le contenu du fichier à l'écran.

 

   Exercice 3.2

   En vous inspirant du projet 064VectorFunc_3 du chapitre 9, réalisez le même programme que l'exercice 3.1 en initialisant un vector avec un tableau. Les éléments du vector seront écrits dans la fonction EcrisFich() et affichés dans la fonction LisFich(). 

 

   3 – Corrigés des exercices du chapitre 11


    Corrigé de l'exercice 1 :

 

   On demandait de réécrire le projet 069Tableaux3 en utilisant la forme de la boucle ''for'' du standard C++ 11.

   Voici le code :

 

//Chap11Exercice_1
//Remplissage d'un tableau bi-dimensionnel
 
//Utilisation de la boucle for du C++ 11
#include<iostream>
#include<string>
 
using namespace std;
using uint = unsigned int;
 
 
int main()
{
const uint nb_lignes(4), nb_colonnes(5);
 
//On déclare un tableau d'unsigned int dont les dimensions sont les constantes
//nb_lignes et nb_colonnes
uint tab[nb_lignes][nb_colonnes];
 
//Une variable pour remplir les emplacements de notre tableau bi-dimensionnel
size_t compteur(0);
 
for (auto &nb_lignes : tab)
{
for (auto &nb_colonnes : nb_lignes)
{
nb_colonnes = compteur;
++compteur;
}
}
 
//Lecture du tableau
cout << "Remplissage et lecture d'un tableau bi-dimensionnel 4 lignes 5 colonnes" << endl;
cout << "Utilisation de la boucle for du C++ 11";
cout << endl << endl;
 
for (auto &nb_lignes : tab)
{
for (auto &nb_colonnes : nb_lignes)
{
cout << nb_colonnes << " ";
}
cout << endl;
}
 
cout << endl;
 
cin.get();
return 0;
}

 

   Il n'y avait rien de difficile dans cet exercice, la boucle d'assignation des valeurs du tableau vous a été donnée dans le chapitre précédent et nous l'avons utilisée à plusieurs reprises maintenant. Notez que si nous avons utilisé une référence sur les variables nb_lignes et nb_colonnes, nous aurions pu tout aussi bien nous en passer pour le remplissage et la lecture du tableau. wink

   Avec VS 2015 par exemple, nous aurions pu déclarer la première ligne de la fonction main() de cette façon :

constexpr size_t nb_lignes(4), nb_colonnes(5);

 

   Corrigé de l'exercice 2 (Un petit challenge) :

 

   Dans cet Exercice il était demandé :

   En utilisant les pointeurs, écrivez une fonction qui affichera les éléments d'un tableau bi-dimensionnel d'entiers (vous pouvez reprendre l'exemple du code reproduit ci-dessous comme corps de la fonction). Le tableau devra être initialisé dans la fonction main().

   Le tableau sera déclaré int tab[5][10];

   Solution 1 – Utilisez des indices pour initialiser le tableau.

   Solution 2 – Initialisez le tableau avec la boucle for du C++ 11.

// Prendre exemple sur ce code
for (auto ptr = tab; ptr != tab + 4; ++ptr)
{
for (auto ptr2 = *ptr; ptr2 != *ptr + 5; ++ptr2)
cout << *ptr2 << " ";
cout << endl;
}

 

   Voici le code de l'exercice proposé (version 1 : utilisation d'indices pour initialiser le tableau) :

 
//Chap11Exercice2_1
//Remplissage d'un tableau bi-dimensionnel
//Création d'une fonction LisTab() qui utilise des pointeurs
//Version 1 : remplissage du tableau par indices dans la fonction main()
#include<iostream>
#include<string>
 
using namespace std;
 
// On déclare une fonction LisTab() avec un pointeur sur un tableau bi-dimensionnel
// en argument.
void LisTab(int (*tab)[10]);
 
 
int main()
{
const int nb_lignes(5), nb_colonnes(10);
//On déclare un tableau d'unsigned int dont les dimensions sont les constantes
//nb_lignes et nb_colonnes
 
int tableau[nb_lignes][nb_colonnes];
 
//Remplissage d'un tableau bi-dimensionnel de 5 lignes et 10 colonnes
cout << "Lecture d'un tableau bi-dimensionnel d'entiers a l'aide de pointeurs";
cout << endl;
 
//Lignes
for (size_t i = 0; i != nb_lignes; ++i)
{
//Colonnes
for (size_t j = 0; j != nb_colonnes; ++j)
{
//On assigne à chaque position la valeur de leur index
tableau[i][j] = i * nb_colonnes + j;
}
}
 
cout << endl;
 
// On passe le nom du tableau en paramètre à la fonction LisTab()
LisTab(tableau);
 
cin.get();
return 0;
}
 
 
void LisTab(int(*tab)[10])
{
for (auto ptr = tab; ptr != tab + 5; ++ptr)
{
for (auto ptr2 = *ptr; ptr2 != *ptr + 10; ++ptr2)
cout << *ptr2 << " ";
cout << endl;
}
} 

 

   Le remplissage du tableau bi-dimentionnel à l'aide d'indices dans la fonction main() ne cause pas de problème. Mais on demande d'utiliser un pointeur à passer à la fonction LisTab(). En nous référant à la théorie du chapitre précédent (1.3 – Tableaux multidimensionnels et pointeurs), nous avons vu que nous pouvions initialiser un pointeur sur le tableau intérieur (colonnes) d'un tableau bi-directionnel. Or, le tableau de notre exercice est :

int tableau[5][10];

   ce qui veut dire que le pointeur sur le tableau est :

int (*ptr) [10];

   et que nous pouvons passer ce pointeur sur le tableau lors de la définition de la fonction LisTab(). L'appel de la fontion LisTab() dans la fonction main() se fera, en lui passant le nom du tableau en paramètre. wink


   Les explications concernant le contenu de la fonction LisTab() ont été énoncées dans le chapitre 11, je vous invite à revoir le paragraphe 1.3 du chapitre pré-cité si vous avez besoin de vous les remémorer. angel

 


   Second code proposé (initialisation du tableau à l'aide de la boucle ''for'' du nouveau standard)

   Le contenu de la fonction LisTab() étant le même que celui de la première version, nous ne reproduirons ici que la fonction main() modifiée :

 

//Version 2 : remplissage du tableau à l'aide de la boucle for du C++ 11 dans la
//fonction main()
int main()
{
 
const int nb_lignes(5), nb_colonnes(10);
//On déclare un tableau d'unsigned int dont les dimensions sont les constantes
//nb_lignes et nb_colonnes
 
int tableau[nb_lignes][nb_colonnes];
size_t compteur(0);
 
//Remplissage d'un tableau bi-dimensionnel de 5 lignes et 10 colonnes
//Lignes
cout << "Lecture d'un tableau bi-dimensionnel d'entiers a l'aide de pointeurs";
cout << endl << endl;
 
for (auto &nb_lignes : tableau)
{
for (auto &nb_colonnes : nb_lignes)
{
nb_colonnes = compteur;
++compteur;
}
}
 
 
// On le passe le nom du tableau en paramètre à la fonction LisTab()
LisTab(tableau);
 
cin.get();
return 0;
} 

 

   Et cette boucle a également déjà été expliquée dans le chapitre 11. wink


   Corrigé de l'exercice 3 :

 

    Pour cet exercice il était demandé de réécrire le projet 072const_cast en remplaçant les références par des pointeurs dans la déclaration de la fonction :

string&EstPlusGrand(string&chaine1, string&chaine2);

 

    Voici le code :

 

//Projet Chap11Exercice_3
//Comparaison de la longueur de deux chaînes
//Implémentation de 2 fonctions surchargées.
//L'une renvoyant un pointeur sur un const string
//L'autre renvoyant une référence sur un const string
#include <iostream>
#include <string>
 
 
using namespace std;
using uint = unsigned int;
 
// Déclaration des fonctions surchargées EstPlusGrand()
string *EstPlusGrand(string *chaine1, string *chaine2);
const string &EstPlusGrand(const string &chaine1, const string &chaine2);
 
int main()
{
 
string plusGrandeChaine;
 
const string ch1{ "Assourbanipal" };
const string ch2{ "Assourdan" };
 
string ch3{ "Assourouballit" };
string ch4{ "Assournazirabal" };
 
cout << endl;
cout << "Une fonction qui renvoie une reference sur la plus grande" << endl;
cout << "des chaines const 'Assourbanipal' et 'Assourdan'" << endl << endl;
 
plusGrandeChaine = EstPlusGrand(ch1, ch2);
cout << "La fonction surchargee avec passage d'arguments 'CONST' est appelee" << endl;
cout << "la plus grande chaine est " << plusGrandeChaine << endl;
cout << "Son nombre de caracteres est de " << plusGrandeChaine.size();
cout << endl << endl;
 
cout << "Une fonction qui renvoie un pointeur sur la plus grande" << endl;
cout << "des chaines non const 'Assourouballit' et 'Assournazirabal'" << endl << endl;
 
plusGrandeChaine = *EstPlusGrand(&ch3, &ch4);
cout << "La fonction surchargee avec passage d'arguments 'NON CONST' est appelee" << endl;
cout << "la plus grande chaine est " << plusGrandeChaine << endl;
cout << "Son nombre de caracteres est de " << plusGrandeChaine.size();
cout << endl << endl;
 
cin.get();
return 0;
 
}
 
//Fonction surchargée avec passage d'arguments 'const'
//Renvoie une référence sur une chaine 'const'
const string &EstPlusGrand(const string &chaine1, const string &chaine2)
{
return (chaine1 > chaine2 ? chaine1 : chaine2);
}
 
//Fonction surchargée avec passage d'arguments 'non const'
//Renvoie un pointeur sur une chaîne 'const'
//par un const_cast des deux chaînes.
string *EstPlusGrand(string *chaine1, string *chaine2)
{
auto r1 = const_cast<const string*>(chaine1);
auto r2 = const_cast<const string*>(chaine2);
 
if (chaine1 > chaine2)
{
return const_cast<string*>(r1);
}
 
else
return const_cast<string*>(r2);
} 

 

   Dans cet exercice, nous remplaçons donc les références par des pointeurs dans la déclaration de la fonction string &EstPlusGrand(string &chaine1, string &chaine2);


   Cela implique que lors de l'appel d'une des deux fonctions surchargées EstPlusGrand() dans la fonction main(), nous allons passer les adresses des arguments non const ch3 et ch4, c'est-à-dire &ch3 et &ch4.


   L'implémentation de la fonction string *EstPlusGrand(string *chaine1, string *chaine2) s'en trouve donc modifiée. Celle-ci, à qui l'on passe des pointeurs sur des variables de type string non const, va renvoyer, par l'intermédiaire d'un const_cast sur une chaîne non const, un pointeur sur une chaîne const (notez que r1 et r2 sont bien tous les deux de type const std::string * wink).


   Le résultat est renvoyé dans la fonction main() par l'intermédiaire de la variable PlusGrandeChaine qui affiche la plus grande des deux chaînes non const ainsi que le nombre de ses caractères.


   Et bien, je crois qu'en voilà assez pour ce chapitre. Les chapitres 13 et 14 nous permettront de découvrir les types de données complexes et notamment les Structures, avec encapsulation de données et fonctions membres, ce qui sera déjà une bonne introduction à notre future (re)découverte des classes C++. wink

 

   Les corrigés des exercices de ce chapitre seront présentés à la fin du chapitre 13.

 

       @ bientôt pour le chapitre 13 – Types de données complexes (1).                                                                         

            Gondulzak.  angel
 
 
 

Connexion

CoalaWeb Traffic

Today117
Yesterday178
This week617
This month4291
Total1743498

25/04/24