IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Fichiers : Afficher toutes les lignes qui contiennent une chaîne de caractère spécial


Sujet :

C++

  1. #1
    Nouveau Candidat au Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2019
    Messages
    9
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Guinée

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2019
    Messages : 9
    Points : 0
    Points
    0
    Par défaut Fichiers : Afficher toutes les lignes qui contiennent une chaîne de caractère spécial
    Bonjour,

    Je voudrais créer un petit programme qui permettrait de gérer un répertoire téléphonique, alors voila la liste des fonctionnalités attendues :

    -ajouter un contact;

    -rechercher un contact;

    -supprimer un contact;

    -modifier un contact;

    -afficher un contact;

    -afficher la liste de tous les contacts.

    Pour le moment j'ai pu gérer l'ajout d'un contact, l'affichage de tous les contacts. Je voudrais maintenant gérer la recherche, c'est à dire quand l'utilisateur saisi une chaîne de caractère, qu'on recherche dans le fichier toutes les lignes qui contiennent cette chaîne et qu'on affiche ces lignes dans la console. J'ai déjà ajouter une fonction qui permet de vérifier si une chaîne de caractère se trouve ou non dans le fichier.

    Merci de m'aider à résoudre ce problème.


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    #include <iostream>
    #include <cstring>
    #include <fstream>
    #include <vector>
     
    using namespace std;
     
    typedef struct
    {
        string nom;
        string prenom;
        int numeroTelephone;
    } repertoire;
     
    void charger(ifstream &fichier)
    {
        string ligne;
     
        if(fichier)
        {
            while(getline(fichier, ligne))
            {
                cout << "\n" << ligne << endl;
            }
        }
        else
            cout << "error !" << endl;
    }
     
    repertoire ajouterContact(repertoire t)
    {
        cout << "\nPrenom : ";
        cin >> t.prenom;
        cout << "Nom : ";
        cin >> t.nom;
        cout << "Numero de telephone : ";
        cin >> t.numeroTelephone;
     
        return t;
    }
     
    void sauvegarder(repertoire t)
    {
        // Ouverture du fichier en mode écriture
        string const fichier("C:/Users/Dell/Desktop/projet-repertoire-telphonique/repertoire.txt");
        ofstream monFlux(fichier.c_str(), ios::app);
     
        //Récupération des éléments de la structure
        repertoire r = ajouterContact(t);
     
        if(monFlux)
            monFlux << r.prenom << " " << r.nom  << " " << r.numeroTelephone << endl;
        else
            cout << "Echec ouverture !" << endl;
    }
     
    void rechercheMot( ifstream& fichier, string mot)
    {
        int compteur(0);
        char charFichier;
     
        while( !fichier.eof() && compteur < mot.size()) // Lit le fichier tant qu'on n'a pas atteint la fin
        {
            fichier.get(charFichier);// Récupère le caractère qui se trouve dans le fichier
            if( charFichier == mot[compteur]) // Compare le caractère récupéré au caractère qui se trouve à la position du compteur
            {
                compteur += 1;// Incrémente le compteur si les caractère sont égaux
            }
            else
            {
                compteur = 0;
            }
        }
     
        // Test de fin pour déterminer si le mot existe dans le fichier
        if (compteur == mot.size()) // On compare la taille de la chaine au compteur
        {
            cout << "\nLe mot existe dans le fichier ! " << endl;
        }
        else
        {
            cout << "\nLe mot n'existe pas dans le fichier ! " << endl;
        }
    }
     
    void menu()
    {
        int choix;
        repertoire t;
        char ajouter = 'o';
        string nom;
     
        //Ouverture du fichier en mode lecture
        ifstream fichier("C:/Users/Dell/Desktop/projet-repertoire-telphonique/repertoire.txt");
     
        cout << "\n============================== Menu ================================\n";
        cout << "\n" << "1==>----------------------Ajouter un contact------------------------" << endl;
        cout << "2==>----------------------Rechercher un contact---------------------" << endl;
        cout << "3==>----------------------Supprimer un contact----------------------" << endl;
        cout << "4==>----------------------Modifier un contact-----------------------" << endl;
        cout << "5==>----------------------Afficher un contact-----------------------" << endl;
        cout << "6==>----------------------Afficher tous les contacts----------------" << endl;
        cout << "7==>----------------------Quitter-----------------------------------" << endl;
        cout << "\n====================================================================\n";
        cout << "\nTapez votre choix : ";
        cin >> choix;
     
        switch(choix)
        {
        case 1:
            while(ajouter == 'o')
            {
                sauvegarder(t);
                cout << "\nVoulez vous ajouter un autre contact ? (o/n) : ";
                cin >> ajouter;
            }
            break;
        case 2:
            cout << "\nNom a rechercher : ";
            cin >> nom;
            rechercheMot(fichier, nom);
            break;
        case 6:
            charger(fichier);
            break;
        }
     
    }
    int main()
    {
        menu();
     
        return 0;
    }

  2. #2
    Nouveau membre du Club
    Homme Profil pro
    Ingénieur de recherche
    Inscrit en
    Janvier 2020
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur de recherche
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Janvier 2020
    Messages : 14
    Points : 39
    Points
    39
    Par défaut
    Bonjour Dia390,

    Est-ce que tu as besoin de passer par un fichier à chaque opération ? Ce serait plus pratique d'opérer sur les données que tu gardes en mémoire, et d'écrire dans le fichier que pour sauvegarder / charger.
    Julien Lopez
    Ingénieur de recherche à R++ : https://rplusplus.com/

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    En fait, tu ne dois surtout pas passer par ton fichier pour manipuler tes contacts. Ce serait particulièrement suboptimal, parce que l'accès à un fichier compte parmi les accès les plus lents que tu puisse trouver.

    Pour faire simple, ton fichier n'est là que pour te permettre de récupérer les contacts qu'il contient lorsque tu (re)lances ton application (après qu'elle ait été quittée). Autrement dit, tu ne dois y accéder qu'à deux moments bien particuliers:
    1. lorsque l'application démarres, pour charger le contenu du fichier (s'il existe) et
    2. juste avant que l'application ne s'arrête, pour sauvegarder les modifications apportées (s'il y en a)

    ET C'EST TOUT

    Entre ces deux "moments clés", tout le reste devrait se faire à partir ou sur des données maintenues en mémoire.

    Le plus facile pour y arriver, c'est d'utiliser les différentes collections fournies par la bibliothèque standard. Je pense, entre autres, à la classe std::vector pour le maintient des données en mémoire et à la classe std::set (ou std::map) pour faciliter la recherche d'un contact en fonction des différents critères qui t'intéressent.

    Mais, avant d'en parler, je vais m'attaquer un peu au code que tu nous présentes, car il y a de la place pour le progres

    (Ne prends pas cette remarque pour du dénigrement ou pour une attaque personnelle, car c'est le cas de la très grosse majorité des codes écrits par des débutants. Seulement, un débutant ne pourra s'améliorer que si on lui explique "où il s'est planté" )

    Commençons simple: la forme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    typedef struct
    {
        string nom;
        string prenom;
        int numeroTelephone;
    } repertoire;
    est une forme typique issue du C, car il faut indiquer au compilateur que la structure décrite doit être considérée comme un type de donnée.

    Le compilateur C++ est "beaucoup plus intelligent" sur ce coup là, car il sait d'office que tous les types définis par l'utilisateur (qu'il s'agisse d'une structure struct, d'une classe class, d'une énumération enum ou d'une union union) devront être considérés comme étant des types de données.

    On peut donc se contenter d'un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct repertoire
    {
        string nom;
        string prenom;
        int numeroTelephone;
    } ;
    et tout fonctionnera très bien

    A ceci près que le nom que tu donnes à ta structure n'est pas cohérent: la structure que tu définis ici ne représente pas un répertoire, mais quelque chose qui ressemble d'avantage à un contact.

    Or, comme le disait l'autre,
    Citation Envoyé par Eragon (2006)
    Blissinghr signifie le feu. C'est le feu.
    Le mot, c'est la chose. Connaît le mot, et tu maîtrise la chose.
    Il est très important lorsque tu développe une application que les noms que tu choisis pour désigner "quelque chose" (qu'il s'agisse du type d'une donnée, d'une donnée en elle-même ou d'une fonction) permette à celui qui lira le code de savoir sans l'ombre d'un doute l'usage auquel est destinée la chose en question.

    Dans le cas présent, il serait sans doute judicieux de renommer cette structure en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct contact
    {
        string nom;
        string prenom;
        int numeroTelephone;
    } ;
    De plus, cette structure présente très clairement ce que l'on appelle une sémantique d'entité, par rapport à des structures comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct point
    {
        int positionX:
        int positionY;
        int positionZ;
    };
    ou comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct couleur{
        char rouge;
        char vert;
        char bleu;
    };
    , qui elles, présentent ce que l'on appelle une sémantique de valeur.

    Quelle est la différence pourras tu me demander.

    Hé bien,elle tient dans le fait que cela ne nous embêtera absolument pas d'avoir plusieurs instances de point ou de couleur qui présentent exactement les mêmes valeurs:

    Il n'y a, par exemple, absolument rien qui t'empêche de peindre les murs de ta cuisine avec la même couleur que celle utilisée pour peindre ta salle de bain.

    Cela signifie que tu peux
    • décider à tout moment de copier la couleur de la cuisine pour l'utiliser "ailleurs"
    • décider à tout moment de changer la couleur de n'importe quelle pièce (on parle d'affectation)



    Tu peux d'ailleurs comparer ces deux couleurs, au minimum par égalité, pour savoir si elle sont identiques, en utilisant un code qui serait sans doute proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    if(cuisine.laCouleur == sdb.laCouleur){
        std::cout<<"Les deux couleurs sont les mêmes\n";
    }else{
        std::cout<<"Les deux couleurs sont différentes\n";
    }
    Mais ces structures sont -- a priori du moins -- immuables, parce que si tu décide de changer la valeur de rouge, de vert ou de bleu, même si c'est pour ne rajouter (ou soustraire) que 1, tu obtiendras une couleur définitivement différente.

    Il se peut, bien sur, que tu ne remarques pas forcément la différence entre la couleur {rouge = 10, vert = 15, bleu = 20} et la couleur {rouge = 11, vert = 15, bleu = 20} mais tu peux me croire sur parole: la différence apparaîtra "comme le nez au milieu de la figure" pour l'ordinateur, au point que si tu utilises une de ces couleur pour la cuisine et l'autre pour la salle de bain et que tu exécute ce petit code, tu obtiendra l'affichage de Les deux couleurs sont différentes.

    Bien sur, ce que j'écris ici et qui est vrai pour les couleurs sera vrai pour les points, pour les quantités, pour les distances, pour les durées etc. Bref: pour tout ce qui place un "curseur" dans un référentiel particulier.

    Par contre, rien de tout cela n'est vrai pour notre structure contact. C'est la raison pour laquelle on dit qu'elle a sémantique d'entité.

    En effet, si tu as un contact dont les informations respectives sont
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    nom : Camus
    prenom: Albert
    téléphone : 555719
    tu ne veux absolument pas courrir le risque de te retrouver, à un moment donné de l'exécution de ton programme, avec deux données qui présentent exactement les mêmes valeurs.

    Pourquoi

    Hé bien, imaginons que les données contact1 et contact2 soit des données différentes mais dont les valeurs sont exactement les même (Camus, Albert, 555719), et que je décide de changer le numéro de téléphone de contact1 pour lui donner la valeur de 917555, sans doute parce que ce cher monsieur a perdu son GSM .

    Je risque de me retrouver face à un très sérieux problème, car, si je recherche monsieur Camus, j'ai autant de chances de trouver en premier sur contact1 (téléphone 917555, qui est le numéro de téléphone correct) que sur contact2 (téléphone 555719, qui ne répondra plus jamais).

    Et, le problème, c'est qu'après "un certain temps" (qui peut être très court), je serai dans l'incapacité complète de savoir si c'est contact1 ou contact2 qui me donne le bon numéro de GSM.

    C'EST TOTALEMENT INACCEPTABLE!!!! Parce que, si je crée cette application, c'est bien dans l'espoir ... de pouvoir me fier aux informations qu'elle mettra à ma disposition.

    C'est la raison pour laquelle on dit que la structure contact a normalement sémantique d'entité: parce que
    1. on ne peut pas créer de copie d'une instance existante
    2. on ne peut pas assigner les valeurs issues d'une instance donnée à celle d'une autre instance existante
    3. il y a même certaines informations issues de n'importe quelle instance qui ne pourront absolument pas être modifiées: Si on remplace le nom (Camus) par Einstein, on obtient un autre grand albert, mais qui n'a rien à voir... Et si au lieu de cela on remplace le prénom (Albert) par Lucien Auguste, nous obtenons le nom du père d'albert, et ce n'est pas non plus Albert Camus
    4. si chacune des données qui composent une instance donnée de structure peut être comparée avec la donnée équivalent d'une autre instance de la structure, il ne sert absolument à rien de comparer l'ensemble des données d'une instance avec celles d'une autre instance, car une différence entre les deux.
    Par contre, notre structure contact pourrait parfaitement se satisfaire d'un héritage publique avec une classe personne, par exemple. De même, nous pourrions distinguer les contacts en fonctions des circonstances dans lesquelles a des contacts avec eux, en faisant hériter les structures contact_personnel fournisseur et client de notre structure contact.

    Au final, nous devrions prévenir le compilateur que nous ne voulons surtout pas qu'il crée une copie ou qu'il assigne les valeurs d'une instance de notre structure contact à une autre en définissant le constructeur de copie et l'opérateur d'affectation comme étant delete. Par la même occasion, nous pourrions être "bien inspirés" de déclarer le destructeur de cette structure comme étant virtuel (afin de profiter du polymorphisme que l'héritage public rend possible). Elle prendrait donc une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    struct contact{
        contact(contact const &) = delete;
        contact & operator = (contact const & ) = delete;
        virtual ~contact() = default;
        string nom;
        string prenom;
        int numeroTelephone;
    }
    Et, tant qu'à faire, nous pourrions aussi insister sur cette particularité qui veut que ni le nom ni le prénom d'un contact ne pourront être changés. Cela nous inciterait sans doute, dans un premier temps, à modifier notre structure pour lui donner une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    struct contact{
        contact(contact const &) = delete;
        contact & operator = (contact const & ) = delete;
        virtual ~contact() = default;
        string const nom;
        string const prenom;
        int numeroTelephone;
    }
    Mais ce ne serait pas suffisant, parce que du coup, si on essaye d'écrire un code "aussi simple" que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int main(){
        contact c; // ok : nom vide, prenom vide, numeroTelephone = 0
        c.nom="Camus"; // BOUM : nom est constant, ne peut pas être modifié
        c.prenom="Albert"; // mêmes causes, mêmes effets
    }
    Mais, dans un sens, tant mieux!

    Parce que cette variable c dont je ne peux modifier ni le champs nom ni le champs prenom me pose un très sérieux problème: elle ne représente aucun contact en particulier, du fait que les deux éléments qui auraient du me permettre de l'identifier ne présentent aucune valeur définie.
    Citation Envoyé par règle d'or
    Quand tu crées une variable, quel qu'en soit le type, elle doit être "directement exploitable", sans nécessiter d'action supplémentaire de la part de l'utilisateur
    Autrement dit, tu dois faire en sorte que toutes les variables que tu crées, quel qu'en soit le type, soient dans un état valide et cohérent pour en permettre l'utilisation.

    Pour permettre à n'importe quelle instance de notre structure contact d'être utilisable tout de suite après sa création, sans obliger l'utilisateur à utiliser des actions supplémentaires, nous devons donc ajouter un constructeur paramétré, ce qui prendra la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct contact{
        contact(std::string const & n, std::string const & p, int num):
            nom{n},prenom{p},numeroTelephone{num}{
        }
        contact(contact const &) = delete;
        contact& operator = (contact const &) = delete;
        virtual ~contact() = default;
        std::string const nom;
        std::string const prenom;
        int numeroTelephone;
    };
    De cette manière, nous pourrons créer des instances de contact sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    int main(){
        contact c1{"Camus","Albert",555719};   // c1: Camus, Albert, 555179
        contact c2{"Einstein","Albert",917555};// c2: Einstein, Albert 917555
    }
    Et, en plus, cela nous permettra de respecter le conseil vif de
    retarder le plus possible la création de vos données.

    Dans l'idéal, attendez au moins de disposer de toutes les informations nécessaires
    Un code un peu plus cohérent avec ce que tu feras sans doute serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int main(){
        std::cout<<"Veuillez introduire le nom du contact :";
        std::string nom;
        std::cin>>nom;
        std::cout<<"Veuillez introduire le prenom du contact : ";
        std::string prenom;
        std::cin>> prenom;
        std::cout<<"veuillez introduire le numero de telephone du contact : ";
        int num;
        std::cin >> num;
        /* on dispose maintenant de toutes les informations, on peut
         * créer notre contact
         */
        contact c{nom, prenom, num};
    }
    Ce qui nous amène tout naturellement à parler de ta fonction ajouterContact.

    D'abord et avant tout, le nom de cette fonction est très mal choisi, parce qu'elle n'ajoute un contact à rien du tout

    Ensuite, il n'y a aucune raison de lui transmettre un paramètre de type repertoire (pour rappel, c'était le nom que tu avais donné à la structure qui s'appelle maintenant contact )

    Enfin, si... Si on avait une fonction appelée ajouterContact et dont le but est effectivement d'ajouter un contact à un répertoire existant, il y aurait bel et bien une raison satisfaisante au fait de lui transmettre comme paramètre
    1. le contact qui doit être ajouté
    2. la notion (réelle) de répertoire (en gros une collections de contacts)

    et elle prendrait une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void ajouterContact(std::unique_ptr<contact> &c, std::vector<std::unique_ptr<contact>> & repetoire){ //(*)
        repertoire.emplace_back(std::move(c));
    }
    parce que le SRP (Single Responsability Principle ou principe de la responsabilité unique) nous impose de veiller à ce qu'elle ne fasse que cela: ajouter un contact existant au répertoire qui est destiné à le maintenir en mémoire.

    (*)je parlerai de std::unique_ptr un peu plus bas
    Ce qui nous amène, tout naturellement, à la nécessité de créer un contact. En gros, le code de la fonction main que je viens de donner devrait faire parfaitement l'affaire, pour autant que l'on veille à en changer le nom

    Oui, mais, au fait! Parce que j'aime pas trop devoir faire plusieurs fois la même chose, uniquement parce qu'il y a un détail qui change... Profitons en pour réfléchir un peu à ce qu'il faudrait faire pour créer un contact à partir de notre fichier.

    En remettant l'écriture du fichier dans le bon ordre (non, prénom, numéro de téléphone) par rapport à l'ordre que tu avais donné dans ton code, nous aurions donc un fichier qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Camus Albert 555719
    Einstein  Albert 917555
    Stroutrup Bjarne 666999
    Coplien Jame 999666
    Et, si on y réfléchit bien, en transmettant le flux d'entrée (std::cin ou le std::ifstream correspondant au fichier) la seule différence entre le fait d'extraire un contact depuis l'entrée standard (le clavier) ou depuis le fichier tiens dans le fait que ... on veut afficher un petit message pour "guider" l'utilisateur lorsqu'il introduit les informations au clavier.

    Autrement dit, il serait très facile de créer une seule et unique fonction qui serait capable de récupérer les contacts dans les deux situations. Tu veux voir comment

    Bah, même si tu veux pas, je te le montre quand même . Au lieu de nommer cette fonction creeContact, je vais la nommer extraireContact, car la logique répond d'avantage de l'extraction de données que de la création. Voici à quoi elle pourrait ressembler
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    std::unique_ptr<contact> extraireContact(std::istream & in, bool affiche){ // affiche permet de savoir s'il faut afficher les informations
        if(affiche)
            std::cout<<"Veuillez introduire le nom du contact :";
        std::string nom;
        in>>nom;
        if(affiche)
            std::cout<<"Veuillez introduire le prenom du contact : ";
        std::string prenom;
        std::cin>> prenom;
        if(affiche)
            std::cout<<"veuillez introduire le numero de telephone du contact : ";
        int num;
        std::cin>>num;
        return std::make_unique<contact>(nom, prenom, num);
    }
    Elle pourra être utilisée aussi bien pour extraire les données depuis un fichier, grâce à une logique proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main(){
        std::ifstream ifs{"fichier.txt"};
        if(!ifs)
            throw std::runtime_error("fichier inaccessible");
        while(ifs){
            auto c = extraireContact(ifs, false);
            /* tout le reste */
        }
    }
    que pour extraire les données depuis le clavier sous grâce à une logique proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main(){
        size_t count;
        std::cout<<"Combien de contacts voulez vous introduire?";
        std::cin >> count;
        for(size_t i = 0; i<count;++i){
            auto c = extraireContact(std::cin, true);
            /* tout le reste */
        }
    }
    Avoue que c'est pas mal, non

    Il est, maintenant, plus que temps de parler de std::unique_ptr. J'espère juste que les limites de la base de données du forum m'en laisseront l'occasion

    Tu te souviens que j'ai insisté plus haut sur la sémantique d'entité de notre structure contact, que j'ai utilisé cette sémantique d'entité pour justifier le fait que je ne voulais surtout pas que les différentes instances soient copiées ou assignées.

    Le problème auquel je suis désormais confronté avec ma structure contact, c'est donc bien qu'elle ne peut pas être copiée. Or, lorsque l'on essaye d'ajouter un élément dans une collection (comme dans un std::vector), c'est justement que l'insertion s'effectue -- a priori -- en effectuant une copie.

    Je pourrais, bien sur, profiter d'une possibilité qui nous est offerte depuis C++11 qui consiste à utiliser la sémantique de déplacement. L'idée étant que, si on ne peut pas copier une donnée, on peut peut être assigner toutes les ressources qui appartiennent à la donnée d'origine à une autre, quitte à ce que la donnée d'origine ne soit plus vailde.

    Voici un petit exemple pour comprendre le principe:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    int main(){
        /* imaginons une chaine de caractères représentant un nom */
        std::string nom{"Camus"};
        /* pour pouvoir contenir les cinq caractères de la chaine, std::string a eu recours à l'allocation 
         * dynamique de la mémoire (faisons simple: elle a appelé new ;) ) 
         * Au lieu d'allouer de la mémoire pour créer la copie, on peut décider de récupérer celle
         * qui appartient à la chaine d'origine
         */
        auto copie= std::move(nom);
        /* mais comme deux chaines de caractères ne peuvent pas se partager le même espace mémoire
         * pour représenter les caractères,
         * copie a littéralement "vampirisé" les données de nom: 
         * nom est devenu complètement invalide, parce que toutes ses ressources ont éta
         * données à copie:
         */
        std::cout<<"taille de nom : "<<nom.size()<<"\n"
               <<"taille de copie : "<<copie.size()<<"\n";
    }
    Mais cela m'obligerait à rajouter le constructeur de copie par déplacement (contact(contact && ) = default;) et l'opérateur d'affectation par déplacement (contact & operator= (contact &&) = default;) à ma cela risque de poser des problèmes de "slicing" par la suite.

    En effet, je t'ai expliqué qu'une classe ayant sémantique d'entité est la candidate idéale pour intervenir dans une hiérarchie d'héritage, que ce soit en tant que "classe de base" ou en tant que "classe dérivée".

    Cela veut dire que je peux décider de faire dériver ma structure contact d'une structure client qui prendrait la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    struct client : public contact{
        int enCours; // le total des factures que le ciient doit encore me payer
    };
    Le slicing va faire que, si j'essaye de fournir une donnée de type client à une collection prévue pour contenir des données de type contact, même en utilisant la sémantique de déplacement, il n'y aura que les données qui font qu'un client peut etre considéré comme étant un contact qui seront récupérées, alors que les données qui font que ce contact spécifique est, en définitive, un client seront irrémédiablement perdues.

    Et, bien sur, cela posera de sérieux problèmes lorsque je voudrais faire la distinction, parmi tous les contact que j'aurai ajouté à la collection, entre ceux qui sont des contacts "normaux" et ceux qui sont en réalité des clients, vu que je ne disposerai plus de cette information particulière qui fait qu'un contact est en réalité un client
    Citation Envoyé par ATTENTION
    Je ne sais pas, à l'heure actuelle, si j'aurai besoin de cette structure.

    Il est tout à fait possible que je décide de ne jamais la créer.

    Mais comme je peux le faire, il vaut mieux partir du principe que je le ferai sans doute tôt ou tard, parce que cela me posera beaucoup moins de problème de "faire comme si je finirai par crééer cette structure" sans jamais devoir la créer que de partir du principe "que je ne créerai jamais cette structure" et de finir par le faire, je suis surement bien mieux inspiré de faire "comme si" cette structure existait virtuellement déjà
    La solution à ce problème de slicing est ** relativement ** simple: au lieu de maintenir une collection de contacts par valeur (sous la forme d'un std::vector<contact>), je peux décider de maintenir un ensemble de pointeurs (comprend: de données numériques entières (généralement) non signées représentant l'adresse mémoire à laquelle nous trouverons effectivement la donnée de type contact ou client).

    Seulement, je dois obtenir la garantie absolue que chaque pointeur que je placerai dans la collection correspond effectivement à l'adresse mémoire à laquelle nous trouverons un contact (ou un client) qui existe effectivement.

    Pire encore, je doit obtenir la garantie absolue qu'il n'y aura, dans ma collection de pointeurs, que des pointeurs correspondant effectivement à l'adresse mémoire à laquelle se trouve un contact existant.

    Et les deux mots mis en évidence (que et effectivement) sont très importants dans cette phrase, car
    1. je ne veux pas risquer de me retrouver avec des pointeurs null. J'ai autre chose à faire qu'à commencer à m'interroger sur la validité des pointeurs et
    2. si je me retrouve à manipuler "ce que je crois être un contact" à une adresse mémoire donnée, je veux que ce soit un contact, car si c'est "autre chose" (une durée ou une couleur), ca va vraiment pas le faire


    Or, les règles "classiques" de portées des données (pour ne citer qu'elles) vont --typiquement -- tout faire pour "foutre le bordel" dans mon tableau de pointeurs, et pour mener à des situations où l'adresse mémoire représentée par un pointeur particulier contient en réalité ... autre chose qu'un client

    "S'il n'y a pas de solution, c'est qu'il n'y a pas de problèmes" disait l'autre. Et le fait est qu'il y a une solution bien connue au problème qu'implique les règles de portées. Elle s'appelle allocation dynamique de la mémoire.

    Seulement, dés que l'on parle d'allocation dynamique, on parle aussi de tous les problèmes qu'elle peut engendrer:
    1. si tu n'a pas libéré la mémoire allouée dynamiquement au plus tard au moment où tu perds le dernier pointeur sur l'adresse utilisée, tu te retrouve avec une fuite mémoire qui, à termes, finira par rendre tout ton système instable
    2. si tu essayes de libérer (une deuxième fois) de la mémoire qui a déjà été libérée, ton programme va planter ("double free corruption")
    3. si tu essayes d'accéder à une donnée dont la mémoire a déjà été libérée, ton programme va planter ("segmentation fault")

    Si l'on ajoute à cela le fonctionnement tout particulier de l'application lorsqu'une exception survient, on se rend facilement compte que le souhait de gérer la mémoire allouée de manière dynamique à la main n'est peut être pas impossible, mais que cela va foutre un joyeux bordel dans notre code .

    Par chance, l'évolution de la bibliothèque standard de 2011 est arrivée avec la notion de "pointeur intelligents" (smart pointers en anglais).

    Le principe est simple: c'est qu'il faut commencer par déterminer qui est le "propriétaire légal"; qui aura le "droit de vie et de mort" sur l'espace mémoire alloué de manière dynamique; et donc qui aura le droit de décider de la libérer "quand il le jugera nécessaire".

    Evidemment, le fait de déterminer qui est le "propriétaire légal" d'une ressource implique que "tout le reste" qui essaye d'accéder à cette ressource devra être considéré comme "un utilisateur" qui n'aura en aucun cas l'autorisation de la libérer.

    La classe std::unique_ptr permet de travailler sur le cas le plus simple que l'on puisse trouver: il y a un et un seul propriétaire légal de la ressource et "une multitude" (potentielle) d'utilisateurs, mais la ressource est automatiquement libérée lorsque le propriétaire légal cesse d'exister.

    Bien sur, on peut "se refiler" la propriété de la ressource, mais, à un instant T de l'exécution, on a la certitude qu'il n'existe qu'un seul propriétaire légal. C'est, pour donner un exemple, ce que je fais dans la fonction void ajouterContact(std::unique_ptr<contact> &c, std::vector<std::unique_ptr<contact>> & repetoire).

    Au final, et bien que j'aie laissé pas mal de fonctionnalités non développées, voici à quoi pourrait ressembler ton code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    #include <string>
    #include <iostream>
    #include <vector>
    #include <cassert>		
    #include <memory>
    #include <limits>
    struct contact{
        contact(std::string const & n, std::string const & p, int num):
            nom{n},prenom{p},numeroTelephone{num}{
        }
        contact(contact const &) = delete;
        contact& operator = (contact const &) = delete;
        virtual ~contact() = default;
        std::string const nom;
        std::string const prenom;
        int numeroTelephone;
    };
    using contact_ptr = std::unique_ptr<contact>;
    using repertoire = std::vector<contact_ptr>;
    std::unique_ptr<contact> extraireContact(std::istream & in, bool affiche){ // affiche permet de savoir s'il faut afficher les informations
        if(affiche)
            std::cout<<"Veuillez introduire le nom du contact :";
        std::string nom;
        in>>nom;
        if(affiche)
            std::cout<<"Veuillez introduire le prenom du contact : ";
        std::string prenom;
        std::cin>> prenom;
        if(affiche)
            std::cout<<"veuillez introduire le numero de telephone du contact : ";
        int num;
        std::cin>>num;
        return std::make_unique<contact>(nom, prenom, num);
    }
    void ajouterContact(repertoire & r, contact_ptr & c){
        r.emplace_back(std::move(c));
    }
    void afficherMenu(){
     
        std::cout << "\n============================== Menu ================================\n"
                  << "\n" 
                  << "1==>----------------------Ajouter un contact------------------------\n" 
                  << "2==>----------------------Rechercher un contact---------------------\n" 
                  << "3==>----------------------Supprimer un contact----------------------\n" 
                  << "4==>----------------------Modifier un contact-----------------------\n" 
                  << "5==>----------------------Afficher un contact-----------------------\n" 
                  << "6==>----------------------Afficher tous les contacts----------------\n" 
                  << "7==>----------------------Quitter-----------------------------------\n" 
                  << "\n"
                  << "====================================================================\n"
                 << "\nTapez votre choix : ";
    }
    int choixAction(){
        while(true){
            std::string temp;
            std::getline(std::cin, temp);
            size_t pos;
            try{
                int choix = std::stoi(temp,&pos);
                if(pos != temp.size()){
                std::cout<<"veuillez introduire un chiffre uniquement\n";
                }else if(choix <1 || choix >7){
                    std::cout<<"veuillez choisir un chiffre entre 1 et 7\n";
                }else{
                    return choix;
                }
            }
            catch(std::invalid_argument & e){
                std::cout<<"veuillez introduire un chiffre uniquement\n";
            }
            afficherMenu();
        }
        return 0;
    }
    void afficherTousContacts(repertoire const & r){
        for(auto const & i : r){
            std::cout<<"nom :"<<i.get()->nom<<"\n";
            std::cout<<"prenom :"<<i.get()->prenom<<"\n";
            std::cout<<"telephone :"<<i.get()->numeroTelephone<<"\n";
        }
    }
     
    int main(){
        repertoire repert;
        bool aboutToQuit{false};
        while(!aboutToQuit){
            afficherMenu();
            int choix = choixAction();
            switch(choix){
                case 1:
                    {
                    auto c = extraireContact(std::cin, true);
                    ajouterContact(repert, c);
                    std::cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
                    }
                	break;
                case 2 : assert(false && "Not yet implemented");
                    break;
                case 3: assert(false && "Not yet implemented");
                   break;
                case 4: assert(false && "Not yet implemented");
                   break;
                case 5: assert(false && "Not yet implemented");
                   break;
                case 6: afficherTousContacts(repert);
                   break;
                case 7: aboutToQuit = true;
            }
        }
    }
    N'hésite pas à poser des questions sur les explications qui manquent
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Réponses: 6
    Dernier message: 21/06/2017, 16h57
  2. [XL-2013] VBA - repérer les cellules qui contiennent une chaine de caractères
    Par Pix_elle dans le forum Macros et VBA Excel
    Réponses: 17
    Dernier message: 16/10/2016, 22h25
  3. Copier les lignes qui contiennent une valeur
    Par coolmomodu31 dans le forum Macros et VBA Excel
    Réponses: 12
    Dernier message: 11/12/2013, 21h39
  4. [XL-2007] Retrouver toutes les lignes qui ont une valeur identique dans la colonne A
    Par bartimeus35 dans le forum Macros et VBA Excel
    Réponses: 3
    Dernier message: 24/06/2012, 17h47
  5. Réponses: 1
    Dernier message: 29/11/2005, 00h37

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo