Construction et destruction des objets

Vu le principe d'encapsulation, il est formellement interdit d'atteindre directement les attributs d'une classe. Il faut systématiquement passer par une méthode. Cette technique est judicieuse puisque le changement d'état d'un objet passe d'abord par un changement de comportement. Il existe des méthodes adaptées pour résoudre les initialisations explicites pour que l'objet soit dans l'état désiré. Ces méthodes spécifiques s'appellent des constructeurs. La construction personnalisée des objets est très fréquente alors que la destruction l’est beaucoup moins. Nous verrons, malgré tout, des cas où il est absolument nécessaire de redéfinir le destructeur proposé par défaut. Avant cela, nous allons nous intéresser au pointeur this.

Liste des travaux pratiques

Gestion des notes en mode console

Vous allez mettre en œuvre un programme qui permet de gérer un ensemble de notes afin que, par la suite, il soit possible de déterminer une moyenne, donner la note la plus petite, la plus grande, etc. Pour implémenter ce comportement, vous allez constituer deux classes fondamentales, la première la classe Notes qui gère l'ensemble des calculs relatifs aux notes intoduites, la deuxième Gestion s'occupe, comme son nom l'indique, de gérer l'ensemble du programme en mode console.

Fonctionnalités du programme - diagramme des cas d'utilisation

Grâce au diagramme des cas d'utilisation nous spécifions toutes les fonctionnalités attendues par le système de gestion des notes. Ainsi, à tout moment, nous pouvons ajouter une nouvelle note, supprimer une ou toutes les notes déjà introduites. Quelquesoit  le choix effectué, nous visualisons systématiquement toutes les caractéristiques d'une gestion de notes : l'ensemble des notes, la moyenne, la note maxi et la note mini.

Diagramme des classes

Afin de bien suivre les fonctionnalités attendues, je vous propose de respecter la structure suivante qui comme nous l'avons évoqué lors de l'introduction est constituées de deux classes : la première la classe Notes qui gère l'ensemble des calculs relatifs aux notes intoduites, la deuxième Gestion s'occupe, comme son nom l'indique, de gérer l'ensemble du programme en mode console.

Dans la classe Gestion, remarquez bien que la plupart des méthodes sont privées sauf le constructeur et la méthode run().
Diagramme des composants

La description de ces deux classes se fera dans des fichiers associés, d'une part, les fichiers en-tête pour la déclaration des deux classes et d'autre part les fichiers source pour la définition des méthodes correspondant à la classe.

Diagramme de séquence

Lecture du diagramme :

  1. Au démarrage du programme, création de l'objet gestion avec en paramètre la valeur 10 demande de gérer 10 notes.
  2. Dès que l'objet est créé, nous avons également la création de l'objet notes attribut de Gestion.
  3. Dès que les objets sont opérationnels, le programme principal appelle la méthode run() de l'objet gestion remarquez que c'est tout ce que réalise le programme principal.
  4. À l'intérieur de la méthode run(), la méthode privée menu() est d'abord sollicitée.
  5. Quand le menu est affiché, c'est la méthode choisir(3) 3 correspond au nombre d'options disponibles dans le menu qui est appelée à son tour. Cette dernière attend la saisie du numéro de la commande de la part de l'utilisateur.
  6. L'utilisateur propose son choix. Il est alors nécessaire de vérifier si la valeur de ce choix correspond à une des options du menu, sinon il faut redemander une nouveau choix à l'utilisateur jusqu'à ce que sa sélection soit correcte.
  7. Ensuite, suivant l'option demandée, soit nous ajoutons une nouvelle note soit nous en supprimons une ou alors nous les supprimons toutes.
  8. Enfin, nous passons à l'affichage avec pas mal de petits traitements spécifiques.
  9. La méthode run() se termine lorsque l'utilisateur choisi l'option 0. Cette méthode retourne également la valeur 0 qui peut servir pour clôturer le programme.
Mise en oeuvre du codage

Respectez l'ensemble de ces diagrammes. Il est judicieux de s'occuper en premier de la classe Notes. Une fois que vous l'avez complètement contituée, vous vous préoccupez ensuite de la classe Gestion qui utilise fréquemment les compétences de la classe Notes appel de ses différentes méthodes. Pour finir, vous complèterez la fonction principale main().

notes.h
#ifndef NOTES_H
#define NOTES_H

class Notes
{
private:
    double *notes;
    unsigned nombre=0, taille;
public:
    Notes(unsigned taille=7);
    ~Notes();
    void ajouter(double note);
    void supprimer(double note);
    void toutSupprimer();
    double moyenne() const;
    double maxi() const;
    double mini() const;
    unsigned getNombre() const;
    double operator[](unsigned indice) const;   
};

inline Notes::~Notes() { delete[] notes; }
inline void Notes::ajouter(double note) { if (nombre<taille) notes[nombre++] = note; }
inline unsigned Notes::getNombre() const { return nombre; }
inline double Notes::operator[](unsigned indice) const { return notes[indice]; }
inline void Notes::toutSupprimer() { nombre = 0; }

#endif // NOTES_H
notes.cpp
#include "notes.h"

Notes::Notes(unsigned taille)
{
  this->taille = taille;
  notes = new double[taille];
}

void Notes::supprimer(double note)
{
  bool existe = false;
  unsigned indice = 0;
  while (indice<nombre)
  {
    if (notes[indice]==note) { existe = true; break; }
    indice++;
  }
  if (existe) { notes[indice] = notes[nombre-1]; nombre--; }
}

double Notes::moyenne() const
{
  if (nombre==0) return 0.0;
  double somme = notes[0];
  for (unsigned i=1; i<nombre; i++) somme += notes[i];
  return somme / nombre;
}

double Notes::maxi() const
{
  if (nombre==0) return 0.0;
  double max = notes[0];
  for (unsigned i=1; i<nombre; i++)
    if (notes[i]>max) max = notes[i];
  return max;
}

double Notes::mini() const
{
  if (nombre==0) return 0.0;
  double min = notes[0];
  for (unsigned i=1; i<nombre; i++)
    if (notes[i]<min) min = notes[i];
  return min;
}
gestion.h
#ifndef GESTION_H
#define GESTION_H

#include "notes.h"

class Gestion
{
  double note;
  unsigned choix;
  Notes notes;
public:
  Gestion(unsigned nombre) : notes(10) {}
  int run();
private:
  void menu();
  unsigned choisir(unsigned limite);
  void afficheResultats();
  void tableauNotes();
};

#endif
gestion.cpp
#include <iostream>
using namespace std;
#include "gestion.h"

int Gestion::run()
{
  do {
    menu();
    choix = choisir(3);
    switch (choix)
    {
    case 1:
      cout << "Note à rajouter : ";
      cin >> note;
      notes.ajouter(note);
      break;
    case 2:
      cout << "Note à supprimer : ";
      cin >> note;
      notes.supprimer(note);
      break;
    case 3:
      notes.toutSupprimer();
      break;
    }
    afficheResultats();
  }
  while (choix!=0);
  return 0;
}

void Gestion::menu()
{
    cout << "------------------------------" << endl;
    cout << "Ajouter une note ............1" << endl;
    cout << "Enlever une note ............2" << endl;
    cout << "Supprimer toutes les notes ..3" << endl;
    cout << "Quitter .....................0" << endl;
    cout << "------------------------------" << endl;
    cout << "Votre choix ? ";
}

unsigned Gestion::choisir(unsigned limite)
{
  unsigned choix;
  do {
    cin >> choix;
    if (choix>limite)
    {
       cout << "ATTENTION ! Votre choix n'est pas prévu, recommencez" << endl;
       menu();
    }
  }
  while (choix>limite);
  return choix;
}

void Gestion::afficheResultats()
{
  cout << "--------------------------" << endl;
  cout << "Tableau de notes   : ";
  tableauNotes();
  cout << endl;
  cout << "Valeur moyenne     : " << notes.moyenne() << endl;
  cout << "Note la plus haute : " << notes.maxi() << endl;
  cout << "Note la plus basse : " << notes.mini() << endl;
}

void Gestion::tableauNotes()
{
  cout << '[';
  for (unsigned i=0; i<notes.getNombre(); i++) cout << notes[i] << ' ';
  cout << (notes.getNombre()>0 ? "\b]" : "vide]");
}
main.cpp
#include <iostream>
using namespace std;
#include "gestion.h"

int main()
{
    cout << "Démarrage du système de gestion de notes..." << endl;
    Gestion gestionNotes(10);
    return gestionNotes.run();
}

Calcul d'une distance à partir d'une trajectoire

Pour ce premier petit projet, je vous invite à créer une classe Trajectoire qui permet d'évaluer la distance parcourue à partir d'un ensemble de points correspondant au trajet effectué. Afin de faciliter le traitement, la distance entre deux points sera toujours considérée comme une droite la trajectoire correspond alors à une suite de segments consécutifs. Pour que le calcul ne soit pas trop éloigné de la réalité, il faut donner des points le plus fréquemment possible.

Fonctionnalités du programme - diagramme des cas d'utilisation

À partir du point origine dans un plan, vous allez rajouter les points intermédiaires du trajet. Au fur et à mesure, la distance parcourue se calcule automatiquement. Il est possible de proposer une nouvelle trajectoire en cliquant sur le bouton .

Diagramme des classes

Structure de l'IHM et diagramme des composants

Mise en oeuvre du codage

Prenez bien le temps d'analyser chacun de ces diagrammes et respectez scrupuleusement ce qui vous est proposé. 

trajectoire.h
#ifndef TRAJECTOIRE_H
#define TRAJECTOIRE_H

#include <math.h>

class Trajectoire
{
  double x=0.0, y=0.0;
  double somme=0.0;
public:
  Trajectoire() = default;
  void ajout(int nx, int ny);
  double distance() const;
  void raz();
};

inline void Trajectoire::ajout(int nx, int ny)
{
  somme += sqrt((nx-x)*(nx-x) + (ny-y)*(ny-y));
x = nx; y = ny;
} inline double Trajectoire::distance() const { return somme; } inline void Trajectoire::raz() { somme = x = y = 0.0; } #endif // TRAJECTOIRE_H
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include "trajectoire.h"

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT
public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void ajouterPoint();
  void raz();
private:
  Trajectoire trajet;
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
}

void Principal::ajouterPoint()
{
  trajet.ajout(abscisse->value(), ordonnee->value());
  distance->setValue(trajet.distance());
}

void Principal::raz()
{
  trajet.raz();
  abscisse->setValue(0.0);
  ordonnee->setValue(0.0);
  distance->setValue(0.0);
}

Gestion des notes en mode fenêtré

Vous allez reprendre l'idée du projet de gestion de notes précédent, mais cette fois-ci, vous allez le développer en mode fenêtré. Dans ce nouveau projet, reprennez dans son intégralité la classe Notes. Par rapport à une étude antérieure avec un traitement sous forme de fonctions, nous allons rajouter une combo-box qui permettra de connaître les notes déjà introduites et de sélectionner la note à supprimer éventuellement.

Fonctionnalités du programme - diagramme des cas d'utilisation

Par rapport au diagramme précédent, vous remarquez la présence d'un nouveau cas d'utilisation Sélectionner une note qui s'effectue à l'aide de la combo-box.

Diagramme des classes

Diagramme des composants et structure de l'IHM

Mise en oeuvre du codage

Prenez bien le temps d'analyser chacun de ces diagrammes et respectez scrupuleusement ce qui vous est proposé. Dans ce projet, vous récupérez entièrement les deux fichiers correspondants à la description de la classe Notes

notes.h
#ifndef NOTES_H
#define NOTES_H

class Notes
{
private:
    double *notes;
    unsigned nombre=0, taille;
public:
    Notes(unsigned taille=7);
    ~Notes();
    void ajouter(double note);
    void supprimer(double note);
    void toutSupprimer();
    double moyenne() const;
    double maxi() const;
    double mini() const;
    unsigned getNombre() const;
    double operator[](unsigned indice) const;   
};

inline Notes::~Notes() { delete[] notes; }
inline void Notes::ajouter(double note) { if (nombre<taille) notes[nombre++] = note; }
inline unsigned Notes::getNombre() const { return nombre; }
inline double Notes::operator[](unsigned indice) const { return notes[indice]; }
inline void Notes::toutSupprimer() { nombre = 0; }

#endif // NOTES_H
notes.cpp
#include "notes.h"

Notes::Notes(unsigned taille)
{
  this->taille = taille;
  notes = new double[taille];
}

void Notes::supprimer(double note)
{
  bool existe = false;
  unsigned indice = 0;
  while (indice<nombre)
  {
    if (notes[indice]==note) { existe = true; break; }
    indice++;
  }
  if (existe) { notes[indice] = notes[nombre-1]; nombre--; }
}

double Notes::moyenne() const
{
  if (nombre==0) return 0.0;
  double somme = notes[0];
  for (unsigned i=1; i<nombre; i++) somme += notes[i];
  return somme / nombre;
}

double Notes::maxi() const
{
  if (nombre==0) return 0.0;
  double max = notes[0];
  for (unsigned i=1; i<nombre; i++)
    if (notes[i]>max) max = notes[i];
  return max;
}

double Notes::mini() const
{
  if (nombre==0) return 0.0;
  double min = notes[0];
  for (unsigned i=1; i<nombre; i++)
    if (notes[i]<min) min = notes[i];
  return min;
}
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include "notes.h"

class Principal : public QMainWindow, public Ui::Principal
{
    Q_OBJECT
    
public:
    explicit Principal(QWidget *parent = 0);
private:
    Notes eleve;
private:
    void miseAjour();
    void listeDeroulante();
private slots:
    void ajouter();
    void supprimer();
    void choisirNote(QString n);
    void effacer();
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
    setupUi(this);
}

void Principal::ajouter()
{
    eleve.ajouter(note->value());
    miseAjour();
    listeDeroulante();
}

void Principal::supprimer()
{
    eleve.supprimer(note->value());
    miseAjour();
    listeDeroulante();
}

void Principal::effacer()
{
    eleve.toutSupprimer();
    miseAjour();
    listeDeroulante();
}

void Principal::miseAjour()
{
    moyenne->setValue(eleve.moyenne());
    maxi->setValue(eleve.maxi());
    mini->setValue(eleve.mini());
}

void Principal::listeDeroulante()
{
    notes->clear();
    for (unsigned i=0; i<eleve.getNombre(); i++)
        notes->addItem(QString::number(eleve[i]));
}

void Principal::choisirNote(QString n)
{
    note->setValue(n.toDouble());
}

Utiliser un vecteur pour la classe Notes

Nous allons modifier le projet précédent afin que la classe Notes ne soit plus limitée par un nombre de notes spécifique, en faisant en sorte que le conteneur s'auto adapte suivant le nombre de notes introduites, sans limitation aucune. Pour cela, nous allons prendre la classe vector comme unique attribut de la classe Notes. L'objectif de ce projet consiste à revoir entièrement cette classe en prenant en compte ce nouvel attribut sans changer le comportement global du système de gestion des notes, et de garder ainsi les mêmes fonctionnalités que précédemment.

Diagramme des classes

Mise en oeuvre du codage

Réajustez cette classe Notes afin maintenant de prendre en compte l'attribut notes de type vector<double>. Seule la méthode supprimer() est difficile à faire par rapport aux connaissances que vous avez vous la mettrez au point ultérieurement avec votre professeur. Il est en effet nécessaire de faire une recherche de la note introduite à l'aide d'un algorithme déjà préfabriqué dans la librairie standard. Il s'agit de la fonction find() à voir avec votre professeur.

notes.h
#ifndef NOTES_H
#define NOTES_H

#include <vector>
#include <algorithm>
using namespace std;

class Notes
{
    vector<double> notes;
public:
    void ajouter(double note);
    void supprimer(double note);
    void toutSupprimer();
    double moyenne() const;
    double maxi() const;
    double mini() const;
    unsigned getNombre() const;
    double operator[](unsigned indice) const;   
};

inline void Notes::ajouter(double note) { notes.push_back(note); }
inline void Notes::supprimer(double note)
{
  notes.erase(find(notes.begin(), notes.end(), note));
}
inline unsigned Notes::getNombre() const { return notes.size(); }
inline double Notes::operator[](unsigned indice) const { return notes[indice]; }
inline void Notes::toutSupprimer() { notes.clear(); }

#endif // NOTES_H
notes.cpp
#include "notes.h"

double Notes::moyenne() const
{
  if (notes.empty()) return 0.0;
  double somme = 0.0;
  for (double note : notes) somme += note;
  return somme / notes.size();
}

double Notes::maxi() const
{
  if (notes.empty()) return 0.0;
  double max = notes[0];
  for (unsigned i=1; i<notes.size(); i++)
    if (notes[i]>max) max = notes[i];
  return max;
}

double Notes::mini() const
{
  if (notes.empty()) return 0.0;
  double min = notes[0];
  for (unsigned i=1; i<notes.size(); i++)
    if (notes[i]<min) min = notes[i];
  return min;
}
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include "notes.h"

class Principal : public QMainWindow, public Ui::Principal
{
    Q_OBJECT
    
public:
    explicit Principal(QWidget *parent = 0);
private:
    Notes eleve;
private:
    void miseAjour();
    void listeDeroulante();
private slots:
    void ajouter();
    void supprimer();
    void choisirNote(QString n);
    void effacer();
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
    setupUi(this);
}

void Principal::ajouter()
{
    eleve.ajouter(note->value());
    miseAjour();
    listeDeroulante();
}

void Principal::supprimer()
{
    eleve.supprimer(note->value());
    miseAjour();
    listeDeroulante();
}

void Principal::effacer()
{
    eleve.toutSupprimer();
    miseAjour();
    listeDeroulante();
}

void Principal::miseAjour()
{
    moyenne->setValue(eleve.moyenne());
    maxi->setValue(eleve.maxi());
    mini->setValue(eleve.mini());
}

void Principal::listeDeroulante()
{
    notes->clear();
    for (unsigned i=0; i<eleve.getNombre(); i++)
        notes->addItem(QString::number(eleve[i]));
}

void Principal::choisirNote(QString n)
{
    note->setValue(n.toDouble());
}

Afficheur AM-03127-LED

Nous allons maintenant nous intéresser au développement d'une application embarqué sur pcDuino afin de gérer l'afficheur AM-03127-LED. Nous avons déjà largement travaillé avec ce système, mais les programmes réalisés jusqu'à présent étaient sous forme structuré programmation fonctionnelle. L'objectif de ce TP est pour l'instant de proposer les mêmes fonctionnalités que précédemment mais avec une programmation plus modulaire en utilisant les objets. Le but est donc de mettre au point une nouvelle classe Afficheur qui s'occupe de la gestion de l'affichage de cet élément physique AM-03127-LED.

Diagramme des cas d'utilisation

Le programme à réaliser est extrêmement simple puisqu'il s'agit d'afficher un seul mot, sur une seule page de l'afficheur avec une durée limité de 10 secondes. Lorsque le temps est écoulé l'afficheur ne doit plus rien afficher. Mis à part le temps écoulé, nous avons déjà réaliser ce type de programme, mais cette fois-ci la conception est plus modulaire et respecte la modélisation objet.

Diagramme de déploiement

Le diagramme de déploiement nous permet de bien voir les différents systèmes mis en jeu. Vous devez mettre en oeuvre du cross-développement pour créer votre programme qui sera automatiquement déployé sur le système embarqué pcDuino à l'aide des trois fichiers nécessaires à l'élaboration du projet. La classe Afficheur sera développée dans les deux fichiers afficheur.h et afficheur.cpp. Vous placerez les fonctionnalités attendues dans le fichier principal main.cpp.

Diagramme des classes

D'après ce diagramme vous remarquez que la classe Afficheur est composée de deux attributs. D'une part l'attribut connexion qui nous précise si le système est bien en communication avec avec l'afficheur AM-03127-LED prise USB branchée. D'autre part l'attribut usb, issu de la classe serialib, qui représente l'interface série par connexion USB, avec ses trois méthodes utiles pour ce projet : l'ouverture du port de communication, sa fermeture et la possibilité d'envoyer des messages textuels.

Diagramme de séquence

Mise en oeuvre du codage

Respectez l'ensemble de ces diagrammes. Il est judicieux de s'occuper en premier de la classe Afficheur. Une fois que vous l'avez complètement constituée, vous vous préoccupez ensuite de la fonction principale main().

AfficheurClasses.pro
TEMPLATE = app
CONFIG += console c++11
CONFIG -= app_bundle qt

SOURCES += main.cpp afficheur.cpp serialib.cpp
HEADERS += afficheur.h serialib.h

TARGET = afficheur
target.path = /home/ubuntu/Public
INSTALLS += target
afficheur.h
#ifndef AFFICHEUR_H
#define AFFICHEUR_H

#include "serialib.h"
#include <string>
using namespace std;

class Afficheur
{
  serialib usb;
  bool connexion;
public:
  Afficheur()   { connexion = usb.Open("/dev/ttyUSB0", 9600) == 1; }
  ~Afficheur()  { affichePage(" "); usb.Close(); }
  bool etat()   { return connexion; }

  void affichePage(const string &message);
private:
  string checksum(string texte);
};

#endif // AFFICHEUR_H
afficheur.cpp
#include "afficheur.h"
#include <sstream>

void Afficheur::affichePage(const string &message)
{
  string commande = "<L1><PA><FE><MA><WC><FE><CE>";
  commande += message;
  ostringstream trame;
  trame << "<ID01>" << commande << checksum(commande) << "<E>";
  usb.WriteString(trame.str().c_str());
  usleep(50000);
}

string Afficheur::checksum(string texte)
{
  char calcul = 0;
  for (char octet : texte) calcul^=octet;
  char bas = calcul & 0b00001111;
  char haut = (calcul & 0b11110000) >> 4;
  bas = bas < 10 ? bas+=0x30 :bas+=0x41-10;
  haut = haut < 10 ? haut+=0x30 : haut+=0x41-10;
  return {haut, bas};
}
main.cpp
#include "afficheur.h"
#include #include "afficheur.h"
#include <iostream>
using namespace std;

int main()
{
  string mot;
  Afficheur lcd;
  if (lcd.etat())
  {
    cout << "Afficheur connecté" << endl;
    cout << "Tapez votre mot à afficher : ";
    cin >> mot;
    lcd.affichePage(mot);
    cout << "Fin du programme dans 10s !" << endl;
    cout << "L'afficheur va s'éteindre complètement..." << endl;
    sleep(10); // attente de 10s
  }
  else cout << "Afficheur non connecté" << endl;
}
Modification du programme principal

Modifiez la fonction principale, dans le fichier main.cpp, de telle sorte que nous voyons l'écoulement du temps des 10 secondes d'attente, comme cela vous est montré dans la figure ci-dessous :

main.cpp
#include "afficheur.h"
#include #include "afficheur.h"
#include <iostream>
using namespace std;

int main()
{
  string mot;
  Afficheur lcd;
  if (lcd.etat())
  {
    cout << "Afficheur connecté" << endl;
    cout << "Tapez votre mot à afficher : ";
    cin >> mot;
    lcd.affichePage(mot);
    cout << "Fin du programme dans 9s";
    cout.flush();
    for (int temps=8; temps>=0; temps--)
    {
      sleep(1); // attente de 1s
      cout << "\b\b" << temps << 's';
      cout.flush();
    }
    cout << endl;
  }
  else cout << "Afficheur non connecté" << endl;
}