Partage
  • Partager sur Facebook
  • Partager sur Twitter

Attributs d'une classe

Sujet résolu
    7 janvier 2021 à 18:11:15

    Bonsoir et bonne année à tous !

    Pour m'entraîner en C++, je suis en train de créer une classe qui a pour but de gérer des ensemble d'entiers de manière dynamique. Je dois pouvoir ajouter un élément à mon ensemble, déterminer si un entier lui appartient et connaître son cardinal. C'est sur ce dernier point où j'ai un problème que je ne comprends pas (le reste il ya quelques trucs à améliorer mais ça fonctionne). Le problème se trouve dans l'attribut qui gère le nombre d'éléments de mon ensemble. En effet, une méthode doit renvoyer cette valeur pour savoir le cardinal de l'ensemble qui l'appellera. Dans le constructeur je l'ai donc initialisé à 0 quand un nouvel objet est créée, et je l'incrémente de 1 quand j'ajoute un élément dans mon ensemble. Sauf qu'en faisant appel à la méthode qui est censée renvoyer le cardinal, j'obtient 0, et ceci pour n'importe quel ensemble. Je vous met donc mon code :

    #ifndef SET_INT_H
    #define SET_INT_H
    
    class Set_int{
    
     private:
      int nbrMaxElements;
      int* ensemble;
      int nbrElements;
      int nbrEleDiff;
    
     public:
      Set_int(int);
      ~Set_int();
      void ajoutElement(int);
      int cardEnsemble();
      bool appartient(int);
      void remplirEnsemble();
      int eleDiff();
      void afficher();
    
    };
    
    #endif
      
    
    #include <iostream>
    #include "set_int.h"
    using namespace std;
    
    Set_int::Set_int(int maxElements):nbrMaxElements(maxElements){
    
      ensemble = new int[nbrMaxElements];
    
      nbrElements = 0;
      nbrEleDiff = 0;
    
      for(int i= 0; i<nbrMaxElements; i++){
    
        ensemble[i] = 0;
    
      }
      
      
    
    }
    
    Set_int::~Set_int(){
      delete [] ensemble;
    }
    
    
    void Set_int::ajoutElement(int n){
      
      if (nbrElements < nbrMaxElements){
        ensemble[nbrElements] = n;
        nbrElements++;
        
      }
      else
        cout<<"Pas de place dans votre ensemble..."<<endl;
    }
    
    int Set_int::cardEnsemble(){
      return nbrElements;
    }
    bool Set_int::appartient(int n){
      int i = 0;
      while(i<=nbrMaxElements){
        if (ensemble[i]==n)
          return true;
        i++;
      }
      return false;
    }
    
    void Set_int::afficher(){
      for(int i = 0; i<nbrMaxElements;i++){
        cout<<"|"<<ensemble[i];
      }
    
    
      cout<<"|"<<endl;
    }
    
    void Set_int::remplirEnsemble(){
      
      char quitter = ' ';
      bool continuer = true;
      int nb = 0;
      int i = 0;
      while((continuer)&&(i < nbrMaxElements)){
        cout<<"Saisissez un nombre pour votre ensemble : ";
        cin>>nb;
    
        ensemble[i] = nb;
        i++;
        cout<<endl;
        cout<<"Voulez-vous continuer ?(c pour continuer, q pour quitter) : ";
        cin>>quitter;
        if(quitter=='q')
          continuer=false;
      }
    }
    
    #include <iostream>
    #include "set_int.h"
    using namespace std;
    
    int main(){
    
      int nbMax = 0;
      cout<<"Saisissez la taille maximale de votre ensemble : ";
      cin>>nbMax;
      cout<<endl;
    
      Set_int ens(nbMax);
      ens.remplirEnsemble();
      ens.afficher();
      cout<<"L'ensemble contient "<<ens.cardEnsemble()<<" éléments"<<endl;
      if(ens.appartient(3))
        cout<<"3 appartient à votre ensemble !"<<endl;
      else
        cout<<"3 n'appartient pas à votre ensemble"<<endl;
    
      if(ens.appartient(5))
        cout<<"5 appartient à votre ensemble !"<<endl;
      else
        cout<<"5 n'appartient pas à votre ensemble"<<endl;
      return 0;
    }





    • Partager sur Facebook
    • Partager sur Twitter
      7 janvier 2021 à 18:29:05

      EjeXjdk a écrit:

      Dans le constructeur je l'ai donc initialisé à 0 quand un nouvel objet est créée, et je l'incrémente de 1 quand j'ajoute un élément dans mon ensemble. 

      Non, je ne vois pas où tu incrémentes le nombre d'éléments dans la fonction "remplirEnsemble".

      -
      Edité par zoup 7 janvier 2021 à 18:29:31

      • Partager sur Facebook
      • Partager sur Twitter
        7 janvier 2021 à 18:42:47

        Purée... En faite j'ai rajouté la méthode "remplirEnsemble" plus tard, et donc je l'avais quasiment oublié. Je trouvais ça bizarre que dans la fonction "ajoutElement" le nombre d'éléments soit incrémenter mais reste à 0, en faite je n'appelais même pas la bonne fonction... Bref, désolé de t'avoir dérangé pour si peu, et merci pour ta remarque car j'aurais pu rester longtemps bloqué sur une bêtise comme ça

        -
        Edité par EjeXjdk 7 janvier 2021 à 18:43:04

        • Partager sur Facebook
        • Partager sur Twitter
          7 janvier 2021 à 19:36:23

          No problem.

          Passe le sujet en "Résolu"

          • Partager sur Facebook
          • Partager sur Twitter
            8 janvier 2021 à 1:12:38

            Salut,

            A ceci près que:

            1- ensemble devrait être un std::vector : c'est exactement le genre de chose qu'il te faut  pour pouvoir travailler ;)

            2- la fonction afficher n'a aucune raison d'exister: surcharge l'opérateur de flux << et rend le ami de ta classe

            3- la fonction remplirEnsemble n'a aucune raison d'être une fonction membre de la classe:

            Les fonctions membres d'une classe, ce doit être les questions que l'utilisateur est en droit de poser à une instance de la classe ou les ordres qu'il peut vouloir lui donner.

            Dans le cas présent, les question seraient du genre de

            • Eh, ensemble, dis moi combien d'éléments tu contiens?
            • Eh, ensemble, dis moi quel est le Nième élément que tu contiens (à condition, bien sur, qu'il y ait au moins N éléments :D )
            • Eh, ensemble, dis moi combien d'éléments tu peux contenir au maximum.

            Quant aux ordres que tu pourrais donner à l'ensemble, elles ressembleraient à:

            • Eh, ensemble, ajoute tel élément (pour autant que ce soit possible, bien sur)
            • Eh, ensemble, fais en sorte de pouvoir rajouter N élément de plus au maximum
            • Eh, ensemble, abandonne l'ensemble des éléments que tu contient, pour recommencer à zéro.

            Comme tu peux le constater, ce sont autant de questions et d'ordres qui s'adressent directement à l'ensemble, or, la fonction remplirEnsemble n'est pas dans le cas: tu ne peux pas demander à l'ensemble de se remplir lui-même, ne serait-ce que parce que, pour y arriver, il faudrait que l'ensemble prenne la responsabilité de demander (à l'utilisateur) quelles valeurs il doit s'ajouter.  Et ca, c'est pas le rôle de l'ensemble: c'est le rôle... de l'utilisateur qui doit ... fournir à l'ensemble les éléments qu'il doit s'ajouter ;).

            Tu peux donc -- effectivement -- avoir une fonction remplirEnsemble, mais ce devrait être une fonction libre qui devrait ressembler à quelque chose comme

            void remplirEnsemble(Set_int & lensemble){
                /* l'utilisateur va devoir introduire  un nombre 
                 * d'éléments qui correspond au nombre maximum d'éléments
                 * de l'ensemble auquel on soustrait le nombre d'éléments
                 * déjà présents
                 */
                size_t nbArajouter = lensemble.maxElements() - lensemble.taille();
                for(size_t cpt = 0; cpt<nbArajouter;++cpt){
                    int valeur = demanderNouvelleValeur();
                    lensemble.ajouter(valeur); 
                }
            }

            car, même si cette fonction ne peut exister que parce que tu dispose de la notion d'ensemble,ce n'est pas un ordre que tu vas donner à l'ensemble lui-même: c'est un ordre que tu vas donner ... à celui qui manipule ton ensemble ;)

            • Partager sur Facebook
            • Partager sur Twitter
            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              8 janvier 2021 à 7:27:13

              Pour éviter (*) les oublis et autres erreurs stupides de programmation, il faut commencer par écrire une série de tests unitaires.

              Genre je cree un ensemble, je vérifie qu'il est de taille 0, et que 12 n'y est pas, j'y mets 34 deux fois, je vérifie que la taille est 1, etc.

              (*) ou plutôt les détecter. Se planter bêtement, ça fait partie du boulot. L'important c'est de savoir faire avec.

              • Partager sur Facebook
              • Partager sur Twitter
                9 janvier 2021 à 16:03:42

                Merci pour vos réponses !

                Koala, il y a plein de choses dont tu parles que je ne connais pas, et ça a l'air bine utile, j'irai regarder tout ça. Effectivement, la méthode remplirEnsemble() n'avait pas besoin d'être une méthode, une simple fonction aurait suffit. Au passage, je ne savais pas vraiment quand une fonction rester une fonction ou passer en méthode, tu m'as bien aidé sur ce sujet là !

                Michelbillaud, qu'est ce qu'un test unitaire, j'avoue ne pas très bien comprendre avec ton exemple...

                • Partager sur Facebook
                • Partager sur Twitter
                  9 janvier 2021 à 16:30:34

                  Bonjour Eje Xjdk,

                  EjeXjdk a écrit :

                  Michelbillaud, qu'est ce qu'un test unitaire, j'avoue ne pas très bien comprendre avec ton exemple...

                  Un test unitaire, dans ce cas, c'est un test qui ne prend en compte que ton objet et qui va valider que les méthodes-membres sont correctement codées, sans bug. Pour ce faire, Michel te propose de faire un exécutable, qui n'utiliseras que ta classe (et pas le reste de ton projet) et qui implémentera la séquence qu'il te propose :

                  1) je crée l'objet,

                  2) je vérifie qu'il est vide,

                  3) J'ajoute la valeur 34,

                  4) Je vérifie que l'objet contient une valeur, et que cette valeur vaut 34

                  5) ...

                  Tu le lances, et en fonction de ce qui se passe, tu corriges.

                  Et tu fais ça avec tous tes objets et toutes tes procédures. En suite tu regroupes tous les sources pour faire ton projet, et tu devrais avoir beaucoup moins de bug !

                  En procèdent de la sorte, tu débug au plutôt tes objet et tes procédures, mais surtout, c'est beaucoup plus facile : il n'y a qu'un objet à la fois à débuger. Si tu attends d'avoir fini ton projet pour commencer à tester, tu es noyer sous les bugs, et tu ne sais pas identifier leurs sources.

                  Cordialement.

                  -
                  Edité par Dedeun 9 janvier 2021 à 16:32:22

                  • Partager sur Facebook
                  • Partager sur Twitter
                    9 janvier 2021 à 16:43:08

                    Heureusement il y a google https://youtu.be/gIBVcWHR7W4?t=13

                    https://fr.wikipedia.org/wiki/Test_unitaire

                    le test unitaire (ou « T.U. », ou « U.T. » en anglais) ou test de composants est une procédure permettant de vérifier le bon fonctionnement d'une partie précise d'un logiciel ou d'une portion d'un programme (appelée « unité » ou « module »).

                    Bon, voila la démarche de développement à partir des tests. Au XXIe siècle, je ne comprends pas que les cours de programmation pour débutants fasse complètement l'impasse dessus. Enfin bon :

                    On commence par construire un "squelette de classe", avec les fonctions membres dont on aura besoin, avec une implémentation bidon, juste pour que ça compile.

                    class IntSet{
                     
                     private:
                     
                     public:
                    	IntSet() {
                    	}
                    	~IntSet() {
                    	}
                    	void add(int) {
                    	}
                    	int size() {
                    		return 0;
                    	}
                    	bool contains(int) {
                    		return false;
                    	}

                    et on écrit des fonctions, avec des scénarios de test

                    #incluse <cassert>
                    
                    
                    
                    void test_add() {
                    	std::cout << "* Test 1" << std::endl;
                    	
                    	IntSet e;
                    
                    	assert(e.size() == 0);
                    	assert(e.contains(12) == false);
                    
                    	e.add(12);
                    
                    	assert(e.size() == 1);
                    	assert(e.contains(12) == true);
                    
                    	e.add(23);
                    	assert(e.size() == 2);
                    	assert(e.contains(12) == true);
                    	assert(e.contains(23) == true);
                    
                    	e.add(12);           // déjà présent
                    	assert(e.size() == 2);
                    	assert(e.contains(12) == true);
                    	assert(e.contains(23) == true);
                    
                    }
                    
                    

                    On écrit un main qui lance les scénarios de test,

                    int main() {
                    	test_add();
                    	std::cout << "Tests ok !" << std::endl;
                    	return 0;
                    }



                    on compile, on fait exécuter, et puis

                    $ g++ -Wall -Wextra Ensemble.cc -o Ensemble
                    $ ./Ensemble 
                    * Test 1
                    Ensemble: Ensemble.cc:35: void test_add(): Assertion `e.size() == 1' failed.
                    Abandon
                    

                    ça plante.

                    TROP COOL !

                    Ça nous donne un objectif : nous demerder pour que ce test passe.

                    Donc on va dire, par exemple, que

                    • l'ensemble est représenté par un vecteur,
                    • l'ajout c'est un push_back.
                    • et la taille, celle du vecteur.
                     private:
                    	std::vector<int> array;
                    	
                     public:
                    	
                    	void add(int n) {
                    		array.push_back(n);
                    	}
                    	int size() {
                    		return array.size();
                    	}

                    et on relance :



                    $ g++ -Wall -Wextra Ensemble.cc -o Ensemble
                    
                    $ ./Ensemble 
                    * Test 1
                    Ensemble: Ensemble.cc:40: void test_add(): Assertion `e.contains(12) == true' failed.
                    Abandon
                    


                    Ah flute, on a oublié contains.
                    Allez hop.

                    	bool contains(int n) {
                    		for (auto x : array) {
                    			if (x == n) return true;
                    		}
                    		return false;
                    	}

                    mais ça coince plus loin :

                    * Test 1
                    Ensemble: Ensemble.cc:51: void test_add(): Assertion `e.size() == 2' failed.
                    Abandon
                    

                    parce qu'il faut faire gaffe, si on ajoute plusieurs fois le même. Correction

                    	void add(int n) {
                    		if (! contains(n)) {
                    		   array.push_back(n);
                    		}
                    	}


                    et là, bingo, tout passe

                    $ ./Ensemble 
                    * Test 1
                    Tests ok !
                    

                    ---

                    Intérêt de cette méthode de travail

                    • les tests sont écrits à l'avance, et on a déterminé ce qu'ils devaient faire, ça oblige à bien réfléchir à ce que qu'on veut que ça fasse. Pas "afficher des trucs qu'on lira peut être".
                    • Pas besoin de rentrer des données à la main à chaque exécution
                    • Tous les tests sont lancés et vérifiés automatiquement à chaque exécution
                    • Si une modif casse quelque chose qui marchait, on s'en rend compte de suite (test de non-régression)
                    Sur le plan "psychologie de la programmation" :
                    • ça guide le travail : modifier le code pour aller un test plus loin.
                    • ça donne des objectifs vérifiables à court terme
                    • le cycle "regarder ce qu'il faut faire, modifier le code pour que ça marche, tester, être content" est rapide.
                    • On est content que que ça marche très souvent.

                    A comparer avec "j'écris 500 lignes et je je me mets à chercher/corriger les erreurs dedans...." qui est un excellent point de départ pour plusieurs jours de frustration.



                    -
                    Edité par michelbillaud 9 janvier 2021 à 16:56:25

                    • Partager sur Facebook
                    • Partager sur Twitter
                      9 janvier 2021 à 17:34:58

                      Merci pour vos explications qui vont m'être très précieuses ! Je n'avais pas vraiment cherché uné méthode de travail efficace et effectivement je regardais que mon code marche que quand j'avais fini de l'écrire. Mais donc, si j'ai bien tout compris, un test unaire consiste à tester toutes les méthodes d'une classe et de vérifier que le résultat est bien celui attendu ? Cette méthode est surtout utile quand plusieurs classes sont implémentées dans mon projet, non ?

                      Et quand tu dis : "On commence par construire un "squelette de classe", avec les fonctions membres dont on aura besoin, avec une implémentation bidon, juste pour que ça compile." Il faut quand même écrire le code de la fonction membre pour qu'elle fasse ce que l'on veut qu'elle fasse, et ensuite on la test ?

                      Dedeun a écrit:


                      En procèdent de la sorte, tu débug au plutôt tes objet et tes procédures, mais surtout, c'est beaucoup plus facile : il n'y a qu'un objet à la fois à débuger. Si tu attends d'avoir fini ton projet pour commencer à tester, tu es noyer sous les bugs, et tu ne sais pas identifier leurs sources.

                      Effectivement, étant donné que je n'ai jamais réalisé de "gros" projets, je ne faisais pas attention à ça.



                      • Partager sur Facebook
                      • Partager sur Twitter
                        10 janvier 2021 à 8:07:10

                        Deux choses différentes

                        • Le fait d'écrire une batterie de tests automatisés pour chaque composant (= tests unitaires)
                        • Le fait d'écrire ces tests AVANT de se lancer dans le détail du codage des "vraies" méthodes, et de s'en servir pour guider le travail. (= développement dirigé par les tests)

                        Dans le squelette initial, les fonctions membres ont le prototype voulu, et font le strict minimum pour que ça compile, par exemple

                        • Retourner 12 si ça doit renvoyer un int
                        • False si c'est un bool
                        • Ne fait rien si c'est un void
                        C'est ce qu'on appelle des stubs (bouchons)

                        Alternative : afficher un message et exit. Ce qui permet de différencier deux cas

                        • Appel d'une méthode pas encore écrite pour de vrai
                        • Méthode écrite mais qui ne fait pas ce qu'elle devrait 

                        --- ne pas oublier : shit happens

                        • On fait aussi des erreurs en écrivant les cas de tests, y jeter un coup d'oeil si on a u  probleme recalcitrant
                        • On ne pense pas à tout : ne pas hésiter à en rajouter 

                        Et pour finir : quand on est payé pour écrire du code, tout le dispositif de test fait partie du "produit livrable".

                        Le boulot du programmeur n'est pas de produire un exécutable, mais un logiciel  que l'on pourra ensuite maintenir (corrections et evolutions). Et les tests, on en aura besoin, ils en font partie. Donc code source, tests, documentation etc.

                        -
                        Edité par michelbillaud 10 janvier 2021 à 8:28:38

                        • Partager sur Facebook
                        • Partager sur Twitter
                          10 janvier 2021 à 15:16:20

                          Super merci beaucoup pour toutes ces explications ;) ! Mais j'avoue ne pas avoir compris la différence entre tests unitaires et le développement dirigé par les tests.

                          -
                          Edité par EjeXjdk 10 janvier 2021 à 15:17:46

                          • Partager sur Facebook
                          • Partager sur Twitter
                            10 janvier 2021 à 16:06:32

                            Relis le premier paragraphe "Deux choses différentes"

                            Dans le dev dirigé par les tests, qui est une méthodologie , on fait (pas que) des tests unitaires, du code qui verifie automatiquement que ça donne le résultat attendu sur un exemple préparé à l'avance.

                            Dans une approche "traditionnelle" de découpage des tâches, les développeurs recevaient un document leur disant quoi faire, pondaient un programme, et en suite quelqu'un d'autre (le testeur) faisait des tests pour contrôler que ça marchait. 

                            Bien sûr le dev avait un peu testé avant de livrer, mais il ne se basait pas sur des tests écrits à l'avance pour guider son travail.

                            -
                            Edité par michelbillaud 10 janvier 2021 à 17:28:32

                            • Partager sur Facebook
                            • Partager sur Twitter

                            Attributs d'une classe

                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                            • Editeur
                            • Markdown