420KEL – Intégration de techniques nouvelles en informatique de gestion

Quelques raccourcis :

Ceci est un petit site de support pour le cours 420-KEL-LG – Intégration de techniques nouvelles en informatique de gestion. Il se construira au fil des semaines à partir de ce que nous aurons effectivement fait en classe (j'ai mon plan, mais on restera agiles de manière à être le plus adaptés possible à vos besoins et à votre réalité).

Ce cours, étant en partie intensif, en partie à distance et en partie intercalé avec votre stage, suivra un format atypique. Ceci explique que les dates effectives des séances en classe restent (au moins en partie) à déterminer, et que j'aie choisi d'identifier les séances par Sxx (pour « séance xx ») plutôt que par Txx (théorie) ou Lxx (laboratoire).

De manière générale, vos besoins guideront en partie notre démarche et nos choix de contenu. Les problèmes (techniques) que vous rencontrerez en stage pourront être discutés en classe, et nous chercherons à leur apporter des solutions à la fois élégantes et efficaces.

Certaines de nos séances seront virtuelles (les dates sur le calendrier ci-dessous seront identifiées s/o, donc sans objet). Elles seront constituées d'une mise en situation (idéalement en personne, lors de nos séances in vivo) puis de travail pratique lorsque votre horaire le permettra, alimenté par des échanges électroniques. Lorsque nous nous reverrons, nous mettrons en commun nos approches, nos questionnements, et nous présenterons la matière au prochain défi. J'ai calculé l'équivalent de 30 séances, soit 15 séances « théoriques » et 15 séances « pratiques », mais nos séances seront pour l'essentiel des hybrides. Bien que le cours soit siglé 2-2-2, nous aurons des séances de format variable, tout en essayant de nous en tenir aux 60 heures prévues (incluant le volet « à distance »).

J'aimerais vous proposer quatre volumes de programmation orientée objet (entre autres) plutôt touffus, qui pourront vous être utiles dans le cours comme dans votre carrière, mais étant donné que vous êtes déjà en stage, je comprendrais si vous préfériez ne pas vous les procurer. Ces volumes présentent des exemples et des concepts en C++ (principalement), mais aussi en C#, en Java et en VB.NET.

Pour éviter de faire imprimer un trop grand nombre de copies, je ferai circuler une feuille en classe et je n'en ferai imprimer que pour celles et ceux parmi vous qui en font la demande.

Quand je connaîtrai les identifiants pour obtenir ces volumes à la coop, je mettrai à jour les puces ci-dessous :

Je veux ces volumes

Je ne veux pas ces volumes

Nicholas Thérien Roussel

 
   
   
   
   
   
   
   
   
   
   

 

 

Détail des séances en classe

Techniquement, vous serez deux groupes de 420KEL cet hiver, soit les groupes (a) et (b). L'horaire sera organisé de manière à alterner les séances d'avant-midi et d'après-midi d'un groupe à l'autre. Soyez donc attentives et attentifs aux variations d'horaire pour éviter de manquer une séance (dans un format intensif, c'est pas vraiment une bonne idée de se lever trop tard).

Date Séance Détails
9 janvier
S00

Groupe (a) :h à 12 h

Groupe (b) : 13 h à 16 h

Cours au P-152. Au menu :

10 janvier S01

Groupe (b) :h à 12 h

Groupe (a) : 13 h à 16 h

C'est l'anniversaire de Donald Knuth aujourd'hui!

Cours au P-152. Au menu :

  • Survol de la différence entre initialisation avec parenthèses et initialisation avec accolades
  • Rôle de la Sainte-Trinité
  • Présentation de l'idiome d'affectation sécuritaire
  • Discussions à propos de la représentation des paramètres culturels à l'aide d'instances de std::locale
  • Réflexions sur la répartition des responsabilités entre un conteneur et son contenu (dans votre code : entre ListeEntiers et Noeud)
  • Qu'est-ce qu'un invariant? (survol)
  • Qu'est-ce qu'une précondition? (survol)
  • Qu'est-ce qu'une postcondition? (survol)
  • Qu'est-ce que la complexité algorithmique? (survol)
  • Exercice de rédaction d'une version plus efficace de ListeEntiers, représentant une liste simplement chaînée d'entiers
  • Réflexion sur le problème de la méthode ajouter() de ListeEntiers, dans sa déclinaison initiale
    • discussion de solutions possibles
  • Présentation sommaire de la sémantique de mouvement
    • implémentation pour ListeEntiers
    • comparaison avec la copie
    • comparaison du rôle de la copie et du rôle du mouvement

Dans les notes de cours :

À faire pour la séance S02, seul(e) ou en équipe de deux :

  • Améliorer la classe ListeEntiers pour que :
    • La méthode taille() soit de complexité
    • La méthode ajouter() soit de complexité
    • Le constructeur de copie et l'affectation deviennent de complexité
    • Vous assurer que les attributs d'instance que vous aurez ajouté pour y arriver soient tenus à jour de manière cohérente dans l'ensemble des méthodes de ListeEntiers
  • Vous aurez droit à un petit bonus si vous ajustez inverser() pour qu'elle n'ait plus à faire d'allocation dynamique de mémoire
  • Remettez votre code dans un seul fichier .cpp par équipe, avec les noms des équipières et des équipiers à la fois dans le nom du .cpp et dans les commentaires au début du fichier
11 janvier S02

Groupe (a) :h à 12 h

Groupe (b) : 13 h à 16 h

Cours au P-152. Au menu :

  • Petit truc de débogage : la classe Noisy
  • Exercice de rédaction d'une Liste<T>, représentant une liste simplement chaînée générique, en Java ou en C# (à votre choix)
  • Peu importe que votre code de Liste<T> soit écrit en Java ou en C#, cette classe doit offrir au minimum les services suivants :
    • constructeur par défaut, qui crée une liste vide
    • mécanisme pour dupliquer une liste, donc créer une nouvelle liste distincte de l'originale mais équivalente à cette dernière
      • deux listes a et b sont équivalentes si elles ont le même nombre de noeuds, et si les noeuds de ces deux listes ont les mêmes valeurs dans le même ordre
    • mécanisme permettant d'inverser les éléments d'une liste
    • mécanisme permettant d'ajouter un élément à une extrémité de la liste
    • mécanisme permettant d'extraire un élément à l'autre extrémité de la liste
    • mécanisme permettant de connaître le nombre d'éléments dans la liste
    • mécanisme permettant de tester la liste pour savoir si elle est vide ou non
    • mécanisme pour comparer deux listes et savoir si elles sont équivalentes
    • mécanisme pour afficher les éléments d'une liste sur un flux
    • mécanisme pour vider une liste
  • Consignes d'ordre général :
    • visez à respecter les idiomes de votre langage (si vous faites du Java, faut que ça respecte les pratiques du code Java; si vous faites du C#, faut que ça respecte les pratiques du code C#)
    • utilisez les exceptions de manière judicieuse
    • évitez les fuites de ressources
  • Il faut que votre programme de test fasse au minimum les opérations suivantes à l'aide d'instances de votre type Liste<T> :
    • tester chacun des services ci-dessus
    • valider que si une liste est une copie d'une autre liste, les deux peuvent être modifiées indépendamment l'une de l'autre

La classe Liste<T> est à remettre au début de la séance S03.

16 janvier S03

Groupe (b) :h à 12 h

Groupe (a) : 13 h à 16 h

Cours au P-152. Au menu :

  • Échanges en classe sur les itérateurs et sur les foncteurs, de même que sur les λ, pour voir des manières contemporaines de solutionner certains des exercices
  • Programmer par algorithmes
  • Exercices formatifs d'utilisation d'algorithmes standards
  • Implémenter quelques algorithmes maison :
    • l'algorithme inverser(debut,fin) qui inverse l'ordre des éléments dans la séquence . Testez votre implémentation avec un tableau de float, un vector<int> et une list<string>. Votre algorithme doit fonctionner avec une séquence vide, et doit donner les bons résultats que le nombre d'éléments y soit pair ou impair. Vous avez le droit d'utiliser std::swap()
    • l'algorithme trouver(debut,fin,val) qui retourne un itérateur sur le premier élément de la séquence qui soit égal à val au sens de l'opérateur ==. Testez votre implémentation avec des itérateurs sur un vector<int> et une list<string>. Si aucun élément n'est trouvé, retournez fin
    • l'algorithme trouver_si(debut,fin,pred) qui retourne un itérateur sur le premier élément e de la séquence tel que pred(e) soit vrai. Testez votre implémentation avec des itérateurs sur un vector<int> et une list<string>. Si aucun élément n'est trouvé, retournez fin
  • Pour celles et ceux qui auront terminé, amusez-vous à implémenter :
    • l'algorithme pivoter_gauche(debut, fin) qui pivote tous les éléments de la séquence d'une position vers la gauche (donc une séquence contenant initialement contiendra en fin de parcours
    • l'algorithme pivoter_droite(debut, fin) qui pivote tous les éléments de la séquence d'une position vers la droite (donc une séquence contenant initialement contiendra en fin de parcours

Notez que ces algorithmes sont pour la plupart implémentés dans <algorithm>, mais l'idée de ces exercices est d'essayer de les implémenter vous-mêmes; lorsque vous les aurez implémentés, vous pourrez comparer votre solution avec celle livrée par le standard si le coeur vous en dit.

Dans les notes de cours :

  • Les foncteurs sont décrits dans POO – Volume 02, pp. 152-173
  • Les λ sont décrites dans POO – Volume 02, pp. 179-190
  • Les algorithmes standards sont survolés dans POO – Volume 02, pp. 87-94
17 janvier S04

Groupe (a) :h à 12 h

Groupe (b) : 13 h à 16 h

Cours au P-152. Au menu :

  • Comparatif d'implémentations Java et C# d'une ListeEntiers
    • caractéristiques de l'implémentation Java
    • caractéristiques de l'implémentation C#
  • Survol de l'approche orientée objet, en particulier du principe de substitution de Liskov (ne dériver publiquement une classe d'une autre que si les deux classes ont des invariants mutuellement cohérents)

Ensuite, quelques exercices à réaliser pour S06 :

  • Écrivez l'algorithme trouver_consecutifs(debut,fin,n) qui retournera un itérateur sur le début de la première séquence de n éléments consécutifs de même valeur dans la séquence . Cet algorithme retournera fin si aucune telle séquence n'existe, ou encore si
  • Écrivez l'algorithme plus_longue_sequence(debut,fin), qui retournera une paire (voir std::pair et std::make_pair()) dont le premier élément sera un itérateur sur le début de la plus longue séquence de valeurs consécutives trouvée dans la séquence , et le second élément sera un itérateur sur la fin de cette « sous-séquence ». Veillez à ce que la paire d'itérateurs retournée forme une séquence à demi-ouverte. Si la séquence est vide, alors retournez une paire fin,fin. Si plusieurs séquences distinctes ont la même (plus longue) longueur, alors retournez une paire d'itérateurs sur la première d'entre elles. Vous pouvez vous aider en écrivant des algorithmes auxiliaires. Vous avez le droit d'utiliser std::distance()
  • Écrivez l'algorithme plus_longue_sequence(debut,fin,pred), qui retournera une paire (voir std::pair et std::make_pair()) dont le premier élément sera un itérateur sur le début de la plus longue séquence de valeurs consécutives respectant le prédicat pred trouvée dans la séquence , et le second élément sera un itérateur sur la fin de cette « sous-séquence ». Veillez à ce que la paire d'itérateurs retournée forme une séquence à demi-ouverte. Si la séquence est vide, alors retournez une paire fin,fin. Si plusieurs séquences distinctes ont la même (plus longue) longueur, alors retournez une paire d'itérateurs sur la première d'entre elles. Vous pouvez vous aider en écrivant des algorithmes auxiliaires. Vous avez le droit d'utiliser std::distance()
  • Écrivez l'algorithme inverser_mots(s) qui retourne une chaîne de la même taille que s, mais dont le contenu est l'inverse de l'ordre des mots dans la chaîne s sans toutefois modifier l'ordre et la taille des séquences de blancs dans s. Un caractère c est un blanc si isspace(c,locale{""}). Par exemple :
    • si s est " j'aime   mon prof  ", alors la fonction retournera " prof   mon j'aime  "
    • si s est "yo", alors la fonction retournera "yo"
    • si s est "     yo man", alors la fonction retournera "     man yo"
    • si s est "yo  man", alors la fonction retournera "man  yo"
  • Écrivez l'algorithme inverser_lettres(s) qui retourne une chaîne de la même taille que s, mais dont le contenu est tel que seul l'ordre des symboles dans chacun des mots est inversé. Par exemple :
    • si s est " j'aime   mon prof  ", alors la fonction retournera " emia'j   nom forp  "
    • si s est "yo", alors la fonction retournera "oy"
    • si s est "     yo man", alors la fonction retournera "     oy nam"
    • si s est "yo  man", alors la fonction retournera "oy  nam"

De ces exercices, deux seront recueillis lors de S06 et seront notés au bulletin. Je ne sais malheureusement (!) pas lesquels au moment d'écrire ces lignes, mais je vous les communiquerai au plus tard 48 heures à l'avance.

18 janvier S05

Groupe (b) :h à 12 h

Groupe (a) : 13 h à 16 h

Cours au P-152. Au menu :

Dans les notes de cours :

  • L'idiome RAII est décrit dans POO – Volume 00, Annexe 03 puis dans POO – Volume 01, pp. 206-210

Pour les intéressé(e)s, le code de la fonction distance_() « maison » va comme suit :

#include <iterator>
using namespace std;
template <class It>
   auto distance_(It debut, It fin, forward_iterator_tag) {
      typename iterator_traits<It>::difference_type n{};
      for (; debut != fin; ++debut)
         ++n;
      return n;
   }
template <class It>
   auto distance_(It debut, It fin, random_access_iterator_tag) {
      return fin - debut;
   }
template <class It>
   auto distance_(It debut, It fin) {
      return distance_(debut, fin, typename iterator_traits<It>::iterator_category{});
   }

Aussi, la classe ListeEntiers avec ébauche d'intérateur (incomplète; il manque des morceaux, mais ça donne un aperçu) allait comme suit (ajouts en caractères gras) :

#include <utility>
class ListeEntiers final {
   struct Noeud final {
      Noeud *succ{};
      int val;
      Noeud(int val) : val{ val } {
      }
      Noeud(const Noeud &n) : val{ n.val } {
      }
   };
   Noeud *tete{};
   Noeud *fin{};
   int nelems{};
public:
   //
   //
   //
   class iterator {
      Noeud * cur;
   public:
      using value_type = int;
      using reference = int&;
      using pointer = int*;
      using difference_type = std::ptrdiff_t;
      using iterator_category = std::forward_iterator_tag;
      iterator() : cur{} {
      }
      iterator(Noeud *p) : cur{} {
      }
      iterator& operator++() { // ajouter operator++(int)
         cur = cur->succ;
         return *this;
      }
      bool operator==(const iterator &autre) const {
         return cur == autre.cur; // ajouter operator!=
      }
      int& operator*() { // ajouter version const
         return cur->val;
      }
   };

   iterator begin() { return { tete }; }
   iterator end() { return { }; }
   //
   //
   //
   bool est_vide() const noexcept {
      return !tete;
   }
   int taille() const noexcept {
      return nelems;
   }
   class ListeVide { };
   void clear() {
      while (!est_vide()) extraire();
   }
   ListeEntiers() = default;
   ListeEntiers(const ListeEntiers &lst) {
      for (auto p = lst.tete; p; p = p->succ)
         ajouter(p->val);
   }
   ListeEntiers(ListeEntiers && lst)
      : tete{ lst.tete }, fin{ lst.fin }, nelems{ lst.nelems } {
      lst.tete = {};
      lst.fin = {};
      lst.nelems = {};
   }
   void swap(ListeEntiers &lst) {
      using std::swap;
      swap(tete, lst.tete);
      swap(fin, lst.fin);
      swap(nelems, lst.nelems);
   }
   ListeEntiers& operator=(const ListeEntiers &lst) {
      ListeEntiers{ lst }.swap(*this);
      return *this;
   }
   ListeEntiers& operator=(ListeEntiers && lst) {
      clear();
      tete = lst.tete;
      fin = lst.fin;
      nelems = lst.nelems;
      lst.tete = {};
      lst.fin = {};
      lst.nelems = {};
      return *this;
   }
   ~ListeEntiers() {
      clear();
   }
   void ajouter(int val) {
      auto p = new Noeud{ val };
      if (est_vide())
         tete = fin = p;
      else {
         fin->succ = p;
         fin = p;
      }
      ++nelems;
   }
   int extraire() {
      if (est_vide()) throw ListeVide{};
      auto p = tete;
      tete = tete->succ;
      int val = p->val;
      if (p == fin) fin = {};
      delete p;
      --nelems;
      return val;
   }
   // attention : version de qualité très discutable
   void inverser() {
      Noeud *nouvelleTete = {},
         *nouvelleFin = {};
      if (tete) { // cas particulier pour se charger de nouvelleFin
         auto p = new Noeud{ *tete };
         p->succ = nouvelleTete;
         nouvelleFin = nouvelleTete = p;
         p = tete;
         tete = tete->succ;
         delete p;
      }
      while (tete) {
         auto p = new Noeud{ *tete };
         p->succ = nouvelleTete;
         nouvelleTete = p;
         p = tete;
         tete = tete->succ;
         delete p;
      }
      tete = nouvelleTete;
      fin = nouvelleFin;
   }
};
9 février
16 février
S06

Groupe (a) : 9 février,h à 10 h 45

Groupe (b) : 16 février,h à 10 h 45

Cours au P-042. Au menu :

  • Remise des exercices demandés à la séance S04. Je ramasserai les fonctions suivantes :
    • la fonction inverser_mots()
    • la fonction plus_longue_sequence(debut,fin,pred)
  • Gestion de la vie d'un objet, un tour d'horizon avec C++, Java et C#
  • Cycle de vie
  • Sémantique de valeur
  • Copie
  • Clonage
  • Sémantiques d'accès

Le code utilisé pour la classe Tableau, de même que le test qui met en relief l'impact du mouvement sur la vitesse du programme, suit :

#include <cstddef>
#include <algorithm>

class Tableau {
public:
   using value_type = int;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   pointer elems;
   size_type nelems,
             cap;
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems;
   }
   const_iterator begin() const noexcept {
      return elems;
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() noexcept : elems{}, nelems {}, cap{} {
   }
   Tableau(size_type n, const_reference init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      std::fill(begin(), end(), init);
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   ~Tableau() {
      delete [] elems;
   }
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      const auto new_cap = capacity()? capacity() * 2 : 42; // hum
      auto p = new value_type[new_cap];
      std::copy(begin(), end(), p);
      delete[]elems;
      cap = new_cap;
      elems = p;
   }
public:
   bool operator==(const Tableau &autre) const noexcept {
      return size() == autre.size() &&
             std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const noexcept {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) noexcept
      : elems{ autre.elems }, nelems{ autre.nelems }, cap{ autre.cap } {
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
   }
   Tableau& operator=(Tableau &&autre) noexcept {
      delete[] elems;
      elems = autre.elems;
      nelems = autre.nelems;
      cap = autre.cap;
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
      return *this;
   }
};


#include <vector>
#include <numeric>
#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;

vector<double> f(vector<double> v) {
   transform(begin(v), end(v), begin(v), [](double x) { return sqrt(x); });
   return v;
}

int main() {
   enum { N = 50'000'000 };
   vector<double> v(N);
   iota(begin(v), end(v), 1.0);
   {
      auto w = v;
      auto pre = high_resolution_clock::now();
      w = f(w);
      auto post = high_resolution_clock::now();
      cout << "Ecoule : " << duration_cast<microseconds>(post - pre).count()
           << " us." << endl;
   }
   {
      auto w = v;
      auto pre = high_resolution_clock::now();
      w = f(std::move(w));
      auto post = high_resolution_clock::now();
      cout << "Ecoule : " << duration_cast<microseconds>(post - pre).count()
           << " us." << endl;
   }
}

Dans sa version générique, avec gestion manuelle de mémoire :

#include <cstddef>
#include <algorithm>
#include <initializer_list>

template <class T>
class Tableau {
public:
   using value_type = T;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   pointer elems{};
   size_type nelems{},
             cap{};
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems;
   }
   const_iterator begin() const noexcept {
      return elems;
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() = default;
   Tableau(size_type n, const_reference init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      try {
         std::fill(begin(), end(), init);
      } catch(...) {
         delete [] elems;
         throw;
      }
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      try {
         std::copy(autre.begin(), autre.end(), begin());
      } catch(...) {
         delete [] elems;
         throw;
      }
   }
   ~Tableau() {
      delete [] elems;
   }
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      const auto new_cap = capacity()? static_cast<size_type>(capacity() * 1.5) : 128; // hum
      auto p = new value_type[new_cap];
      try {
         std::copy(begin(), end(), p);
         delete[]elems;
         cap = new_cap;
         elems = p;
      } catch(...) {
         delete [] p;
         throw;
      }
   }
public:
   bool operator==(const Tableau &autre) const {
      return size() == autre.size() &&
             std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) noexcept
      : elems{ autre.elems }, nelems{ autre.nelems }, cap{ autre.cap } {
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
   }
   Tableau& operator=(Tableau &&autre) noexcept {
      delete[] elems;
      elems = autre.elems;
      nelems = autre.nelems;
      cap = autre.cap;
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
      return *this;
   }
   template <class It>
      Tableau(It debut, It fin) : nelems{ std::distance(debut, fin) } {
         cap = size();
         elems = new value_type[capacity()];
         try {
            std::copy(debut, fin, begin());
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
   Tableau(std::initializer_list<value_type> lst) : nelems{ lst.size() }, cap{ lst.size() }, elems{ new value_type[lst.size()] } {
      try {
         std::copy(lst.begin(), lest.end(), begin());
      } catch(...) {
         delete [] elems;
         throw;
      }
   }
};

Dans sa version générique, avec gestion de mémoire par voie d'un pointeur intelligent :

#include <cstddef>
#include <algorithm>
#include <initializer_list>
#include <memory>

template <class T>
class Tableau {
public:
   using value_type = T;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   std::unique_ptr<value_type[]> elems{};
   size_type nelems{},
             cap{};
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems.get();
   }
   const_iterator begin() const noexcept {
      return elems.get();
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() = default;
   Tableau(size_type n, const_reference init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      std::fill(begin(), end(), init);
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   ~Tableau() = default;
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      const auto new_cap = capacity()? static_cast<size_type>(capacity() * 1.5) : 128; // hum
      std::unique_ptr<value_type[]> p { new value_type[new_cap] };
      std::copy(begin(), end(), p.get());
      elems.swap(p);
      cap = new_cap;
   }
public:
   bool operator==(const Tableau &autre) const {
      return size() == autre.size() &&
             std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) = default;
   Tableau& operator=(Tableau &&autre) = default;
   template <class It>
      Tableau(It debut, It fin) : nelems{ std::distance(debut, fin) } {
         cap = size();
         elems = std::unique_ptr<value_type[]>{ new value_type[capacity()] };
         std::copy(debut, fin, begin());
      }
   Tableau(std::initializer_list<value_type> lst) : nelems{ lst.size() }, cap{ lst.size() }, elems{ new value_type[lst.size()] } {
      std::copy(lst.begin(), lest.end(), begin());
   }
};

Dans les notes de cours :

  • Le cycle de vie des objets est discuté dans POO – Volume 00, pp. 91-114
  • Une discussion de l'importance des invariants est offerte dans POO – Volume 03, pp. 18-25
  • Les pointeurs intelligents sont présentés dans POO – Volume 02, pp. 120-135
  • Une réflexion sur les sémantiques d'accès est proposée dans POO – Volume 00, Appendice 00
  • Le clonage est décrit dans POO – Volume 01, pp. 195-205
  • Vous trouverez une réflexion sur le rôle des constantes dans POO – Volume 01, Appendice 01
13 avril
20 avril
S07

Groupe (b) : 13 avril,h à 10 h 45

Groupe (a) : 20 avril,h à 10 h 45

Cours au P-042. Au menu :

J'ai fait un exemple d'observateur sans délégués :

using System;
using System.Collections.Generic;

interface RéactionClavier
{
   void TouchePressée(char c);
}
class AfficheurTouche : RéactionClavier
{
   public void TouchePressée(char c)
   {
      Console.WriteLine("Touche pressée: {0}", c);
   }
}
interface Signaleur
{
   void Signaler();
}
class Surveillant : RéactionClavier
{
   private Signaleur signaleur;
   private char Touche { get; set; }
   public Surveillant(Signaleur sign, char c)
   {
      if (sign == null) throw new Exception("Signaleur manquant");
      signaleur = sign;
      Touche = c.ToString().ToLower()[0];
   }
   public void TouchePressée(char c)
   {
      if (c.ToString().ToLower()[0] == Touche)
         signaleur.Signaler();
   }
}
class FinDeProgramme : Signaleur
{
   public bool Complété { get; private set; }
   public FinDeProgramme()
   {
      Complété = false;
   }
   public void Signaler()
   {
      Complété = true;
   }
}
class GestionnaireClavier
{
   private List<RéactionClavier> Elems { get; set; }
   private static GestionnaireClavier singleton = null;
   static GestionnaireClavier()
   {
      singleton = new GestionnaireClavier();
   }
   private GestionnaireClavier()
   {
      Elems = new List<RéactionClavier>();
   }
   // note : propriété static
   public static GestionnaireClavier Instance
   {
      get { return singleton; }
   }
   public void Abonner(RéactionClavier réac)
   {
      Elems.Add(réac);
   }
   public void Désabonner(RéactionClavier réac)
   {
      Elems.Remove(réac);
   }
   public void Exécuter()
   {
      var c = Console.ReadKey(true); // n'affiche pas la touche
      foreach (RéactionClavier réac in Elems)
      {
         réac.TouchePressée(c.KeyChar);
      }
   }
}
class Program
{
   static void Main()
   {
      var ges = GestionnaireClavier.Instance;
      ges.Abonner(new AfficheurTouche());
      var fdp = new FinDeProgramme();
      ges.Abonner(new Surveillant(fdp, 'q'));
      while (!fdp.Complété)
      {
         ges.Exécuter();
      }
   }
}

... que j'ai ensuite un peu modernisé :

using System;
using System.Collections.Generic;

interface RéactionClavier
{
   void TouchePressée(char c);
}
class AfficheurTouche : RéactionClavier
{
   public void TouchePressée(char c)
   {
      Console.WriteLine($"Touche pressée: {c}"); // ICI : interpolation de chaînes de caractères
   }
}
interface Signaleur
{
   void Signaler();
}
class Surveillant : RéactionClavier
{
   private Signaleur signaleur;
   private char Touche { get; set; }
   public Surveillant(Signaleur sign, char c)
   {
      signaleur = sign ?? throw new Exception("Signaleur manquant"); // ICI
      Touche = c.ToString().ToLower()[0];
   }
   public void TouchePressée(char c)
   {
      if (c.ToString().ToLower()[0] == Touche)
         signaleur.Signaler();
   }
}
class FinDeProgramme : Signaleur
{
   public bool Complété { get; private set; } = false; // ICI
   public void Signaler()
   {
      Complété = true;
   }
}
class GestionnaireClavier
{
   private List<RéactionClavier> Elems { get; set; } = new List<RéactionClavier>(); // ICI
   static GestionnaireClavier()
   {
      singleton = new GestionnaireClavier();
   }
   private GestionnaireClavier()
   {
   }
   // note : propriété static
   public static GestionnaireClavier Instance { get { return singleton; } } = null; // ICI
   public void Abonner(RéactionClavier réac)
   {
      Elems.Add(réac);
   }
   public void Désabonner(RéactionClavier réac)
   {
      Elems.Remove(réac);
   }
   public void Exécuter()
   {
      var c = Console.ReadKey(true); // n'affiche pas la touche
      foreach (var réac in Elems) // ICI
      {
         réac.TouchePressée(c.KeyChar);
      }
   }
}
class Program
{
   static void Main()
   {
      var ges = GestionnaireClavier.Instance;
      ges.Abonner(new AfficheurTouche());
      var fdp = new FinDeProgramme();
      ges.Abonner(new Surveillant(fdp, 'q'));
      while (!fdp.Complété)
      {
         ges.Exécuter();
      }
   }
}

J'ai aussi fait un exemple d'observateur avec délégués :

using System;
using System.Collections.Generic;

delegate void RappelTouchePressée(char c); // ICI

class AfficheurTouche // plus besoin d'une interface
{
   public void TouchePressée(char c)
   {
      Console.WriteLine($"Touche pressée: {c}");
   }
}
delegate void RappelSignalement(); // ICI
class Surveillant // plus besoin d'une interface
{
   private RappelSignalement signaleur;
   private char Touche { get; set; }
   public Surveillant(RappelSignalement sign, char c)
   {
      signaleur = sign ?? throw new Exception("Signaleur manquant");
      Touche = c.ToString().ToLower()[0];
   }
   public void TouchePressée(char c)
   {
      if (c.ToString().ToLower()[0] == Touche)
         signaleur(); // ICI
   }
}
class FinDeProgramme // plus besoin d'interface
{
   public bool Complété { get; private set; } = false;
   public void Signaler()
   {
      Complété = true;
   }
}
class GestionnaireClavier
{
   private List<RappelTouchePressée> Elems { get; set; } = new List<RappelTouchePressée>(); // ICI
   static GestionnaireClavier()
   {
      singleton = new GestionnaireClavier();
   }
   private GestionnaireClavier()
   {
   }
   // note : propriété static
   public static GestionnaireClavier Instance { get { return singleton; } } = null;
   public void Abonner(RappelTouchePressée réac) // ICI
   {
      Elems.Add(réac);
   }
   public void Désabonner(RappelTouchePressée réac) // ICI
   {
      Elems.Remove(réac);
   }
   public void Exécuter()
   {
      var c = Console.ReadKey(true); // n'affiche pas la touche
      foreach (var réac in Elems)
      {
         réac(c.KeyChar); // ICI
      }
   }
}
class Program
{
   static void Main()
   {
      var ges = GestionnaireClavier.Instance;
      ges.Abonner(new AfficheurTouche().TouchePressée); // ICI
      var fdp = new FinDeProgramme();
      ges.Abonner(new Surveillant(fdp, 'q').TouchePressée); // ICI
      while (!fdp.Complété)
      {
         ges.Exécuter();
      }
   }
}

Voici petit exemple simple de programme affichant à la console des identifiants légaux en C++ (les règles sont les mêmes en C# ou en Java) pris d'un fichier source nommé z.cpp :

#include <locale>
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <fstream>
using namespace std;
/*
   Un identifiant dans la plupart des langages débute par une lettre ou un '_', suivi par une séquence de
   zéro ou plus lettres, chiffres ou '_'. Ça donne une expression régulière comme [_a-zA-Z][_a-zA-Z0-9]+
   si on l'écrit « manuellement » (car il existe des raccourcis dans bien des implémentations d'expressions
   régulières pour ce genre de truc)
   
   Le code qui suit n'est pas optimisé du tout; je l'ai écrit pour vous inspirer
*/
//
// retourne true seulement si c peut débuter un identifiant
//
bool peut_debuter_identifiant(char c) {
   return c == '_' || isalpha(c, locale{ "" });
}
//
// retourne true seulement si c peut poursuivre un identifiant (tout symbole
// pouvant occuper une position autre que la première dans un identifiant)
//
bool peut_poursuivre_identifiant(char c) {
   return c == '_' || isalnum(c, locale{ "" });
}
int main() {
   //
   // lire tout le fichier z.cpp dans le vecteur de char nommé texte
   //
   ifstream in{ "z.cpp" };
   vector<char> texte{
      istreambuf_iterator<char>{ in },
      istreambuf_iterator<char>{}
   };
   for (auto p = begin(texte); p != end(texte); ) {
      //
      // trouver le début du prochain identifiant
      //
      while (p != end(texte) && !peut_debuter_identifiant(*p))
         ++p;
      //
      // lire l'identifiant s'il y a lieu
      //
      if (p != end(texte)) {
         string s = *p;
         for (++p; p != end(texte) && peut_poursuivre_identifiant(*p); ++p)
            s += *p;
         if (!s.empty())
            cout << "Identifiant trouve : \"" << s << "\"" << endl;
      }
   }
}
  • Dictionnaires, Hash Maps et std::map

Voici un petit exemple d'un programme qui compte le nombre d'occurrences des « mots » (au sens de « séquences de caractères délimitées par des blancs », donc pas dans un sens aussi strict que celui du travail pratique proposé aujourd'hui) et affiche chaque mot accompagné de son nombre d'occurrences (dans le désordre), en C++. Le code est très semblable en C#, et l'est à peine moins en Java :

#include <string>
#include <map>
#include <fstream>
#include <iostream>
using namespace std;
int main() {
   map<string, int> mots;
   ifstream in { "z.cpp" };
   for (string s ; in >> s ; mots[ s ]++)
      ;
   for (auto & [mot, occ] : mots)
      cout << "Le mot " << mot << " apparait " << occ << " fois" << endl;
}

En vue d'une remise à la séance S09, je vous propose le travail pratique suivant :

  • Écrivez un programme dans le langage de votre choix (Java, C#, C++, autre si vous le souhaitez et si je vous donne mon accord) qui aura pour rôle de générer une page Web contenant une version formatée d'un fichier source
  • Plus précisément, votre programme réalisera la tâche suivante :
    • prendre en paramètre au démarrage (paramètres à main() / Main()) une séquence contenant des options et des noms de fichiers, peu importe l'ordre
    • les options doivent débuter par '-' ou par '/' (supportez les deux formats)
    • les noms de fichiers doivent être des fichiers sources dans un langage de votre choix
      • je vous suggère de prendre le même langage que celui dans lequel vous développerez le programme, c'est plus amusant comme ça
    • le programme consommera le contenu du fichier source, le transformera (sans modifier l'original) et écrira le résultat de la transformation dans un fichier portant le même nom que le fichier source, mais avec extension .html
    • les transformations qui seront faites doivent dépendre des options choisies à l'appel du programme. Si aucune option n'est choisie, alors le programme doit générer un fichier identique au fichier source, outre le code html qui fera du fichiers en sortie une page Web légale
  • Un exemple de lancement de votre programme (présumant qu'il se nomme cpp2web) serait :
cpp2web -stats -couleur a.cpp b.cpp c.cpp
  • ... qui générerait les fichiers a.cpp.html, b.cpp.html et c.cpp.html de même qu'un fichier de statistiques sur le travail accompli (le nom de ce fichier est à votre choix)

Pour un exécutable « exemple », vous pouvez essayer mon cpp2web.exe mais prenez soin de placer mots_cles.txt dans le même répertoire (le programme trouve ses mots clés dans ce fichier, alors ils doivent être au même endroit)

  • L'idée est de vous amuser avec des techniques nouvelles de programmation. Conséquemment, je vous recommande de faire le travail à l'aide de ce qui suit (ou de l'équivalent dans votre langage de prédilection) :
  • Je m'attends à ce que vous supportiez au moins les options suivantes :
    • une option qui permettra de demander à ce que les mots clés du langage de programmation choisi soient mis en relief (vous pouvez utiliser du CSS et des balises <span> pour y arriver, c'est probablement l'option la plus simple)
    • une option qui permettra de tirer des statistiques sur les données traitées. Je vous propose de compter les mots clés, de compter le nombre d'occurrences de chaque mot (ne comptez pas ce qui ne comprend pas au moins un caractère alphanumérique), et de compter le nombre de nombres (les littéraux comme 0, 3.14159 ou 85). Pas besoin de traiter les suffixes comme U, UL ou f (même si vous le pouvez si vous en avez envie)
      • les statistiques devront être générées dans un fichier texte à la fin de l'exécution du programme
      • les paires mot / nombre d'occurrences devront être listées en ordre décroissant de nombre d'occurrences, puis en ordre lexicographique dans le cas où plusieurs mots apparaissent aussi souvent les uns que les autres
    • évidemment, seules les options demandées par l'usager devront être mises en application (ça fait partie du plaisir)
  • Vous pouvez ajouter des options si vous le souhaitez, mais documentez ce que vous faites
  • Peu importe ce que vous choisirez, n'oubliez pas que certains symboles importants en programmation, comme <, > ou &, jouent un rôle spécial en html et devront être remplacés par leur métacaractère équivalent (donc &lt;, &gt; et &amp; respectivement pour ces trois symboles)
  • Profitez-en pour vous amuser et pour expérimenter!

À la séance S08, je vous laisserai travailler sur ce travail pratique, et je répondrai à vos questions. Nous pourrons discuter design, technique et autres trucs qui vous turlupinent. Évidemment, ce cours étant en partie électronique, vous pouvez m'écrire quand vous le souhaitez pour discuter de ces choses. Il se peut que je répondre publiquement (donc au groupe entier) si j'estime que votre question est d'intérêt public.

Vous aurez droit à un bonus si vous traitez correctement les cas suivants :

  • Les commentaires débutant par un // et se terminant par un saut de ligne
  • Les commentaires débutant par un /* et se terminant par un */
  • Les chaînes de caractères sous la forme "abc...def" et celles sous la forme L"abc...def" ou leur équivalent dans le langage que vous aurez choisi
  • Les chaînes de caractères sous la forme R"(abc ... def)" ou leur équivalent dans le langage que vous aurez choisi (en C#, par exemple, on parle de @"abc...def")

Attention, c'est plus croustillant, car il y aura des cas comme /*"allo" int i=3 */, "int /* " ou /* // yeah */ qui seront des tenir-compte, alors c'est un petit bonus par cas traité, et ne vous lancez là-dedans que si le programme de base fonctionne

27 avril
4 mai
S08

Groupe (a) : 27 avril,h à 10 h 45

Groupe (b) : 4 mai,h à 10 h 45

Cours au P-042. Au menu :

  • Brève explication des principes derrières le traitement d'un fichier source
  • Cette séance vous est offerte pour travailler sur la tâche à laquelle vous êtes convié(e)s depuis la fin de S07 et qui doit être complétée pour S09. Je vous invite à profiter de ces deux plages horaires si vous souhaitez discuter ou avoir du soutien technique de la part de votre chic prof.

Petit bonbon pour vous. Si vous essayez ce programme (ici, je présume que mon fichier source se nomme z.cpp et que j'applique le programme sur ses propres sources), il se peut que vous le trouviez charmant :

#include <regex>
#include <string>
#include <fstream>
#include <iostream>
#include <set>
#include <algorithm>
#include <iterator>
using namespace std;
int main() {
   set<string> ze_mots{ "string", "if", "for" }; // liste incomplète, pour fins d'illustration
   string pattern = "[_a-zA-Z][_a-zA-Z0-9]*";
   regex re{ pattern };
   ifstream in{ "z.cpp" };
   string texte{
      istreambuf_iterator<char>{ in },
      istreambuf_iterator<char>{}
   };
   auto debut = sregex_iterator{ begin(texte), end(texte), re };
   auto fin = sregex_iterator{};
   cout << "Nombre d'identifiants : " << distance(debut, fin) << endl;
   for_each(debut, fin, [](auto mot) {
      cout << mot.str() << endl;
   });
}

Ça ne tient pas compte des commentaires ou des chaînes de caractères, mais je pense que ça peut vous inspirer.

7 mai S09

Groupe (b) :h à 10 h 40 au P-152

Groupe (a) : 10 h 45 à 13 h 25 au P-116 (apportez-vous à manger!)

Au menu, une demande spéciale :

8 mai S10

Groupes (a) et (b) : 13 h 30 à 16 h 10

Puisque c'est une « classe sèche », je procéderai en mode « démonstration » alors pensez à vous apporter du matériel de prise de notes!

Cours au D-409. Au menu :

Présentation d'un dernier travail pratique pour la session, qui sera à remettre à la séance S17 : le problème des boîtes imbriquées. C'est un classique, mais il me semble que c'est le moment de vous proposer un truc comme ceci.

9 mai S11

Groupe (a) : 11 h 40 à 14 h 20

Groupe (b) : 14 h 25 à 17 h 5

Cours au P-116. Au menu :

14 mai
S12

Groupe (b) :h à 10 h 40 au P-152

Groupe (a) : 10 h 45 à 13 h 25 au P-116 (apportez-vous à manger!)

Au menu :

15 mai
17 mai
S13

Groupe (a) : 15 mai, 10 h 45 à 13 h 25

Groupe (b) : 17 mai, 14 h 25 à 17 h 5

Cours au P-042. Au menu :

  • Petit défi de programmation pour vous :

Par équipe de deux (ou trois, si tous s'impliquent) personnes, définissez une classe petite_chaine représentant une petite chaîne de caractères (susceptible d'être utilisée dans un système embarqué ou dans un jeu vidéo).

Vos contraintes sont les suivantes :

  • Essentiel : sizeof(petite_chaine)==256
  • Essentiel : aucune allocation dynamique de mémoire ne doit être faite dans les méthodes d'une petite_chaine
  • Essentiel : si s est une petite_chaine, alors s.size() retourne sa taille, exprimée en nombre de caractères, est en temps constant (complexité )
  • Invariant : la taille d'une petite_chaine, en nombre de caractères, doit être entre 0 et 255 inclusivement
  • Essentiel : la Sainte-Trinité doit être supportée par votre classe
  • Essentiel : les opérateurs suivants doivent être offerts : opérateurs relationnels (==, !=, <, <=, > et >=), opérateur [] en version const et non-const
  • Essentiel : offrir une méthode empty() retournant true seulement si la petite_chaine est vide et qui s'exécute en temps constant (complexité )
  • Invariant : une petite_chaine est vide si sa taille est zéro
  • Essentiel : un constructeur par défaut de petite_chaine doit construire une chaîne vide
  • Essentiel : offrir un constructeur prenant en paramètre un const char* (chaîne ASCIIZ)
  • Essentiel : offrir un constructeur prenant en paramètre un const std::string&

Quelques questions auxquelles je vous suggère de réfléchir :

  • Doit-on implémenter la Sainte-Trinité pour cette classe? Expliquez votre réponse
  • Doit-on implémenter le mouvement pour cette classe? Expliquez votre réponse
  • Comment gérerez-vous les constructeurs recevant un paramètre susceptible de violer vos invariants (p. ex. : une chaîne de plus de 255 caractères)?
  • Discussion des choix de design
  • Élaborer un code de test correct pour petite_chaine
22 mai
S14

Le cours n'a pas eu lieu car votre chic prof était à l'hôpital avec son amoureuse souffrante.

23 mai S15

Le cours n'a pas eu lieu car votre chic prof était à l'hôpital avec son amoureuse souffrante.

24 mai S16

Groupe (b) :h àh 45

Groupe (a) :h 50 à 11 h 35

Cours au P-152. Au menu :

  • Retour sur ListeEntiers. Trucs à retenir :
    • prendre soin d'initialiser tous les attributs d'une instance avant la fin d'un constructeur
    • s'assurer que pour toutes les fonctions « spéciales » (la règle du ), l'ensemble des attributs d'une instance aient été traités (il en va de la cohérence de vos objets)
    • ne pas exposer de manière publique des services qui ne sont pas appropriés (p. ex. : setTete(Noeud) ou une propriété publique Length{get;set;}), ou qui n'ont pas d'utilité a priori (considérez le principe YAGNI)
  • Retour sur Liste<T>. Trucs à retenir :
    • les remarques apportées à ListeEntiers s'appliquent ici aussi, étant pour l'essentiel des considérations générales d'hygiène
    • attention au code dépendant d'initialisations par défaut de vos variables. C'est une pratique fragile et non-transférable
    • prenez soin de respecter les usages et les pratiques associées à votre langage de choix
    • prenez soin de bien lire les consignes et de les respecter!
  • Retour sur plus_longue_sequence(debut,fin,pred). Trucs à retenir :
    • un prédicat est une opération booléenne, pas une variable...
    • prenez soin de bien lire les consignes et de les respecter!
    • prenez soin de bien tester vos algorithmes. Un mauvais jeu d'essai peut masquer un problème de fond. Comment aurions-nous pu bien tester cette fonction?
    • indentez votre code avec soin
    • attention aux noms dans vos programmes
    • faites des fonctions!
    • attention aux types. La plupart d'entre vous acceptez du code qui compile avec avertissements... C'est pas une bonne idée
  • Retour sur inverser_mots(s). Trucs à retenir :
    • vous êtes créatives et créatifs. J'ai vu passer beaucoup de bonnes idées... et beaucoup de code brisé, qui ne fonctionne qu'avec trois mots et moins
    • prenez soin de bien lire les consignes et de les respecter! En particulier, il y a d'autres sortes de blancs que le caractère ' '
    • prenez soin de bien tester vos algorithmes. Un mauvais jeu d'essai peut masquer un problème de fond. Comment aurions-nous pu bien tester cette fonction?
    • indentez votre code avec soin
    • attention aux noms dans vos programmes
    • faites des fonctions!
    • attention aux types. La plupart d'entre vous acceptez du code qui compile avec avertissements... C'est pas une bonne idée
  • Retour sur petite_chaine :
    • faisons-le ensembles, et examinons les enjeux de plus près...

Pour un possible solutionnaire des fonctions plus_longue_sequence() et inverser_mots(), voir 420KEL--sequences-Solutions.html

Pour um possible solutionnaire de petite_chaine, voir ../../../Sources/ChainePascal.html (si vous souhaitez une version avec support d'itérateurs, voir ../../../Sources/ChainePascalAvecIterateurs.html)

25 mai S17

Cours en format tutorat. Je serai disponible à mon bureau du F-314 de h 11 h  à 15 h pour répondre à vos questions et bavarder.

Remise du travail pratique le problème des boîtes imbriquées.

28 mai S18

Groupe (a) :h à 12 h

Groupe (b) : 13 h à 16 h

Cours au P-152. Au menu :

  • Chic examen final plein d'amour!

Documents sous forme électronique

Cliquez sur cette cible pour le plan de cours, sous forme électronique

Exercices de révision  : cliquez sur cette cible pour quelques exercices de révision.

Petits coups de pouces

Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous donner un petit coup de pouce occasionnel.

Comment accéder à du code .NET à partir d'un client C++ natif

Introduction aux templates

Introduction aux foncteurs

Introduction aux conteneurs et aux itérateurs

Programmation générique appliquée

Dans la plupart des cas, vous trouverez de l'aide précieuse dans les sections Divers – C++, Au secours Pat et Trucs scouts du site.

Solutionnaires

Les solutionnaires aux exercices auxquels vous avez été confrontés et qui ont déjà été remis sur publiés ci-dessous

Cliquez sur cette cible si vous souhaitez un solutionnaire pour les exercices de révision

Correction

Je corrige les programmes en appliquant des codes de correction. Vous trouverez ici la liste des codes les plus fréquents.

Ma stratégie de correction en tant que telle (pour le code, à tout le moins) est résumée ici.

Résultats des travaux pratiques

Ce qui suit collige les résultats des divers travaux pratiques réalisés au cours de la session. La moyenne est exprimée en pourcentage, peu importe l'échelle d'origine pour la correction des travaux en question. Les absent(e)s sont considéré(e)s avoir eu zéro.

La valeur de exprimée dans le tableau se veut un reflet du nombre de participant(e)s; cette valeur devrait normalement être équivalente au nombre d'étudiant(e)s inscrit(e)s au cours. En pratique, la colonne indique le nombre d'individus ayant remis le travail, alors que la colonne indique le nombre d'individus qui auraient dû remettre le travail. Malheureusement, un travail non remis signifie une note de zéro (il y en a trop souvent).

Travail Consignes Remise

à venir

S??

S??

à venir

S??

S??

à venir

S??

S??

à venir

S??

S??

à venir

S??

S??

à venir

S??

S??

Activité synthèse

S00

S17

Valid XHTML 1.0 Transitional

CSS Valide !