User Tools

Site Tools


in202:seance_1:td_1

TD : Premiers pas avec C++

Première Partie :Installation d'un environnement C++

Etape 1 : Installation de l'environnement

En vous référant à la documentation disponible sous ce lien et en n'hésitant pas à demander à l'enseignant de vous accompagner, installer un environnement de développement sur votre ordinateur.

Etape 2 : Compilation d'un exemple simple de classe C++

En fonction de l'environnement, essayer de créer un projet “console” et ensuite ajouter le code suivant dans ce projet.

  • Fichier rational.hpp
    #ifndef rationalHPP
    #define rationalHPP
     
    #include<exception>
    #include<stdexcept>
    #include<iostream>
     
    class rational
    {
    private:
    	long m_numerator;
    	long m_denominator;
     
    	template<typename  charT, typename charTraits> 
    	friend inline std::basic_ostream<charT, charTraits>& operator << (
    			std::basic_ostream<charT, charTraits>& aStream, const rational& aRational);
     
    public:
    	rational() : m_numerator(0), m_denominator(1)
    	{}
    	rational(long theNumerator) : 
    		m_numerator(theNumerator), m_denominator(1) 
    	{}
    	rational(long theNumerator, long theDenominator)
    	{
    		if (theDenominator == 0)
    			throw std::invalid_argument("denominator is 0");
    		if (theDenominator < 0)
    		{
    			theNumerator *= -1;
    			theDenominator *= -1;
    		}
    		m_numerator = theNumerator;
    		m_denominator = theDenominator;
    	}
    	rational(const rational& aRational): 
    		m_numerator(aRational.m_numerator), 
    		m_denominator(aRational.m_denominator)
    	{}
     
    	operator double() const
    	{
    		return (double)m_numerator / (double)m_denominator;
    	}
    	rational operator + (const rational& anotherRational) const
    	{
    		return rational(m_numerator * anotherRational.m_denominator + m_denominator * anotherRational.m_numerator,
    			m_denominator * m_numerator);
    	}
    	rational operator - (const rational& anotherRational) const
    	{
    		return rational(m_numerator * anotherRational.m_denominator - m_denominator * anotherRational.m_numerator,
    			m_denominator * m_numerator);
    	}
    	rational operator * (const rational& anotherRational) const
    	{
    		return rational(m_numerator * anotherRational.m_numerator,
    			m_denominator * m_numerator);
    	}
    	rational operator / (const rational& anotherRational) const
    	{
    		return rational(m_numerator * anotherRational.m_denominator - m_denominator * anotherRational.m_numerator,
    			m_denominator * m_numerator);
    	}
    };
     
    template<typename  charT, typename charTraits>
    inline std::basic_ostream<charT, charTraits>& operator << (
    	std::basic_ostream<charT, charTraits>& aStream, const rational& aRational)
    {
    	aStream << aRational.m_numerator;
    	if (aRational.m_denominator != 1)
    		aStream << "/" << aRational.m_denominator;
    	return aStream;
    }
     
    #endif
  • Fichier main.cpp
    #include"rational.hpp"
     
    #include<iostream>
     
    int main()
    {
    	try
    	{
    		rational valueA = { 1, 0 };
    	}
    	catch (std::exception& e)
    	{
    		std::cout << e.what() << std::endl;
    	}
    	rational valueA = { 4, 3 };
    	rational valueB = { 3, 4 };
     
    	std::cout << "A + B = " << (valueA + valueB) << std::endl;
    	std::cout << "A - B = " << (valueA - valueB) << std::endl;
    	std::cout << "A * B = " << (valueA * valueB) << std::endl;
    	std::cout << "A / B = " << (valueA / valueB) << std::endl;
     
    	std::cout << "4 * B = " << (4 * valueB) << std::endl;
            return 0;
    }

Assurez vous que le projet compile et s'exécute correctement.

Correction

Correction

Il suffit de vérifier que le programme compile et s'exécute correctement.

Seconde partie : Création d'une classe "Compteur" en C++

Question 2.1

Créer un projet “console” vide dans votre environnement de développement.

Correction

Correction

Cela dépend de votre environnement.

Question 2.2

Ajoutez un fichier Counter.h ou Counter.hpp ou Counter.hh dans votre fichier et introduisez la structure de la classe Counter dans ce fichier.

class MyCounter
{
public:
    unsigned counter;
    unsigned max; 
};

Correction

Correction

A nouveau, il suffit de créer les deux fichiers. Par contre, il est important de s'assure que le contenu d'un fichier entête (ie. .h, .hh ou .hpp) ne puissent pas être compilé deux fois même s'il est chargé plus d'une fois. Pour ce faire, il est de bonne pratique d'utiliser la structure suivante :

#ifndef nameHPP
#define nameHPP
 
// contenu de votre fichier.
#endif  

name correspond au nom de votre fichier, “HPP” pour l'extension '.hpp', “H” pour l'extension '.h', ainsi de suite.

La première fois que le fichier est chargé par le compilateur, l'identifiant nameHPP n'est pas défini, on analyse le code situé après la directive #ifndef. La première ligne suivante : #define nameHPP définit l'identifiant nameHPP et puis ensuite le contenu du fichier est analysé.

Si on charge une seconde fois le fichier, l'identifiant nameHPP est défini, donc le contenu situé entre la directive #ifndef nameHPP et la directive #endif est ignoré. Cela empêche d'avoir des erreurs liées au fait que l'on charge le fichier d'entête plus d'une fois.

Question 2.3

Ajoutez les fonctions membres à votre compteur :

class MyCounter
{
public:
 
    ...
 
    void increment() {
      counter ++;
      if(counter > max)
        counter = 0;
    }
 
    void reset() {
      counter = 0;
    }
};

Expliquer ce que font ces fonctions membres.

Correction

Correction

La première fonction augmente le compteur de un en un tant que la valeur maximale n'est pas atteinte. Si la valeur maximale est atteinte, il recommance à compter à partir de la valeur 0.

La seconde fonction remet le compteur à 0.

Question 2.4

Testez le code avec le programme main.cpp suivant :

#include<iostream>
#include "counter.hpp"
 
void testCounter()
{
  MyCounter counterA;
  counterA.counter = 0;
  counterA.max = 2;
 
  MyCounter counterB;
  counterB.counter = 0;
  counterB.max = 4;
 
  for(int i = 0; i < 4; i++)
  {
      std::cout << "compteur A: (" << counterA.counter << ", " << counterA.max << ")" << std::endl;
      std::cout << "compteur B: (" << counterB.counter << ", " << counterB.max << ")" << std::endl;
      counterA.increment();
      counterB.increment();
  }
}
 
int main()
  testCounter();
  return 0;
}

Expliquer le comportement des compteurs.

Correction

Correction

Le counterA a été créé avec count à 0 et max à 2.

Le counterB a été créé avec count à 0 et max à 4.

Incrémenter le compteur counterA va générer la séquence : 0, 1, 2, 0, …

Incrémenter le compteur counterB va générer la séquence : 0, 1, 2, 3, …

Troisième partie : Les constructeurs

Nous voyons dans le code précédent que nous créons un compteur et que nous l'initialisation en mettant à jour les valeurs counter et max.

Cependant, ceci n'est pas optimal pour plusieurs raisons:

  • nous avons un objet qui n'a pas été initialisé du tout.
  • nous avons un objet qui peut-être partiellement initialisé, seul le champ max ou le champ counter a été initialisé,
  • nous avons un objet qui est mal initialisé, par exemple la valeur du chmap max est inférieur à la valeur du counter, ce qui ne doit jamais se produire.

Question 3.1

Vérifier le problème de la mauvaise initialisation d'un objet en exécutant le code suivant.

void createArrayOfCounters()
{
    MyCounter counters[4];
    for(int i = 0, i < 4; i ++)
      std::cout << i << " : " << counters[i].counter << "/" << counters[i].max << std::endl;
}
 
void main()
{
  createArrayOfCounters();
  return 0;
}

Correction

Correction

Vous constatez que les valeurs des champs count et max ont des valeurs qui sortent de nul part.

Question 3.2

Proposer des constructeurs pour les compteurs pour s'assurer que le compteur est bien initialisé.

On proposera au moins deux constructeurs :

  • un constructeur qui met counter à 0 et max à une valeur maximale passée en paramètre et strictement supérieure à 0,
  • un constructeur qui met counter et max à des valeurs passées en paramètre. Cependant si counter est supérieur à max on met counter à 0.

Question 3.2.1

Ajouter ces constructeurs à votre classe.

Correction

Correction

Nous ajoutons les deux constructeurs comme suit:

class MyCounter
{
public:
    int counter;
    int max;
 
    explicit MyCounter(int theMaxValue): count(0), max(theMaxValue) {}
    MyCounter(int theCounter, int theMaxValue): count(theCounter), max(theMaxValue) {}
 
    void increment() {
      counter ++;
      if(counter > max)
        counter = 0;
    }
 
    void reset() {
      counter = 0;
    }
};

Comme nous ne souhaitons pas une conversion automatique d'un entier en compteur, nous devons mettre explicit pour empêcher que le compilateur utiliser le constructeur MyCounter(int) comme constructeur de conversion lui permettant de convertir un entier en un objet de type MyCounter.

Question 3.2.2

Modifier la fonction testCounter pour utiliser ces constructeurs afin de créer et d'initialiser les objets counterA et counterB.

Correction

Correction

Le code est simplifier, puisqu'il suffit de passer au constructeur la valeur maximale du compteur, supprimant les initialisations des champs counter et max qui ne sont désormais plus nécessaires et souhaitables.

void testCounter()
{
  MyCounter counterA(2);
  MyCounter counterB(4);
 
  for(int i = 0; i < 4; i++)
  {
      std::cout << "compteur A: (" << counterA.counter << ", " << counterA.max << ")" << std::endl;
      std::cout << "compteur B: (" << counterB.counter << ", " << counterB.max << ")" << std::endl;
      counterA.increment();
      counterB.increment();
  }
}
</hidden>
 
===== Quatrième partie : Rendre publique/privée des champs et fonctions =====
 
Nous voyons bien que les champs ''counter'', ''max'' sont des champs qui ne devraient pas pouvoir être modifié accidentellement puisque cela modifie le comportement du compteur. Par exemple, le code suivant conduit à un comportement erratique :
 
<code cpp>
int main()
{
  MyCounter counterA;
  counterA.counter = 0;
  counterA.max = 10;
 
 
  for(int i = 0; i < 4; i++)
  {
      std::cout << "compteur A: (" << counterA.counter << ", " << counterA.max << ")" << std::endl;
      counterA.increment();
      counterA.max -= 2;
  }
  return 0;

Dans ce cas, il faut rendre les champs uniquement accessible en lecture et non pas en lecture et écrire. Pour ce faire, il suffit de :

  • rendre les champs privés (on doit transformer la déclaration de l'objet MyCounter de struct en class.
  • définir dans cette classe au moins une section public et une section private:
        class MyCounter
        {
            private:
              ... // section privée
            public:
              ... // section publique
        };
  • mettre les champs privés et définir des fonctions pour accéder aux valeurs. Nous appellons ces fonctions des accesseurs. Souvent en lecture elle commence par get suivit du nom de la variable, en écriture, par set et le nom de la variable. Ici, nous proposons de définir les fonctions getCounter et getMax qui elles seront publiques.

Question 4.1

Modifier votre classe en rendant les champs max et counter privés et en déclarant des méthodes d'accès getCounter et getMax.

Correction

Correction

Il suffit de déplacer les deux champs counter et max dans la section privée private de la classe et d'ajouter les méthodes int getCounter() const et int getMax() const qui retourne une copie du champ counter et du champ max. On ajoute le mot clé const pour indiquer que la méthode ne modifie aucun champ de l'objet et donc ne modifie pas l'objet.

class MyCounter
{
private:
    int counter;
    int max;
public:    
    explicit MyCounter(int theMaxValue): count(0), max(theMaxValue) {}
    MyCounter(int theCounter, int theMaxValue): count(theCounter), max(theMaxValue) {}
 
    int getCounter() const { return counter; }
    int getMax() const { return max; }
 
    void increment() {
      counter ++;
      if(counter > max)
        counter = 0;
    }
 
    void reset() {
      counter = 0;
    }
};

Question 4.2

Vérifier que la compilation de la fonction testCounter génère des erreurs.

Correction

Correction

Le compilateur indique qu'il n'est pas possible d'accéder aux champs counter et max.

Question 4.3

Modifier la fonction testCounter pour désormais appeller les méthodes d'accès. Vérifier le bon fonctionnement de la fonction testCounter.

Correction

Correction

Il suffit de remplacer les accès aux champs max et counter par des appels aux méthodes getMax() et getCounter().

<code cpp> void testCounter() {

MyCounter counterA(2);
MyCounter counterB(4);

for(int i = 0; i < 4; i++)
{
    std::cout << "compteur A: (" << counterA.getCounter() << ", " << counterA.getMax() << ")" << std::endl;
    std::cout << "compteur B: (" << counterB.getCounter() << ", " << counterB.getMax() << ")" << std::endl;
    counterA.increment();
    counterB.increment();
}

}

in202/seance_1/td_1.txt · Last modified: 2022/11/18 10:46 (external edit)