Quelques raccourcis :

420-KBB-LG – Programmation orientée objet avancée

Ceci est un petit site de support pour le cours 420-KBB-LG – Programmation orientée objet avancée.

Vous trouverez aussi des liens sur divers langages (dont C#, notre outil de prédilection dans ce cours) un peu partout dans http://h-deb.clg.qc.ca/. Portez une attention particulière à ../../../Sujets/Divers--cdiese/index.html.

Les diverses sections de cette page (en fonction desquelles vous trouverez quelques liens dans l'encadré à droite) vous mèneront elles-aussi sur des pistes qui vous permettront d'explorer un peu plus par vous-mêmes, de valider vos acquis et d'enrichir votre apprentissage.

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

Pratiques de 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.

Cliquez sur cette cible pour les normes appliquées dans ce cours, en ce qui a trait au pseudocode

Quelques trucs pour demander de l'aide plus efficacement

Détail des séances en classe

Puisque nous serons en quelque sorte laboratoire à la fois pour les séances théoriques et les séances de laboratoire, j'ai fait le choix de construire le cours sous forme de 30 séances (de S00 à S29) plutôt que sous forme de 15 séances théoriques et 15 séances de laboratoire. Le dosage prévu de temps en théorie et de temps en laboratoire (soit environ moitié-moitié) devrait être respecté.

Date Séance Détails

23 août

S00

Au menu :

  • Présentation du cours et du plan de cours
  • Les outils dont nous aurons besoin :
    • Nous utiliserons C# 9.0
    • Nous ferons des projets .NET 5
    • Assurez-vous que votre version de Visual Studio soit à jour!
  • Échange sur le contenu du cours, les modalités, les attentes
    • Il est possible que nous tenions une séance « en ligne » pour « roder la mécanique » au cas où la pandémie mettrait du sable dans l'engrenage (personne ne le souhaite, mais mieux vaut être prudentes et prudents)
  • Réponses aux questions de la classe :
  • Présentation d'une petite activité formative : 420KBB--Consignes-activite-revision.pdf

Si vous souhaitez le code du programme principal à partir duquel vous devrez démarrer, vous pouvez le prendre du fichier PDF ou encore le prendre ci-dessous (parfois, copier / coller d'un PDF...) :

// ...
static void Main(string[] args)
{
   List<Orque> orques = new List<Orque>();
   try
   {
      for(string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
      {
         orques.Add(new Orque(s));
         Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
      }
   }
   catch(NomInvalideException ex)
   {
      Console.WriteLine(ex.Message);
   }
   if(Trier(ref orques, out int nbPermutations))
   {
      Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
   }
   else
   {
      Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
   }
   Console.Write("La tribu d'orques est :");
   foreach (Orque orque in orques)
      Console.Write($" {orque.Nom}");
}

À titre de référence, le code produit lors de cette séance pour démontrer le clonage était :

using System;
class Program
{
   enum Couleur { Rouge, Vert, Bleu }
   // il existe une interface ICloneable, qui expose une méthode Clone
   // ... mais ne l'utilisez pas :)
   abstract class Image
   {
      public Couleur Teinte { get; set; } 
      public abstract void Dessiner();
      protected Image(Couleur teinte)
      {
         Teinte = teinte;
      }
      protected Image(Image autre)
      {
         Teinte = autre.Teinte;
      }
      public abstract Image Cloner();
   }
   class Jpeg : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Jpeg de couleur {Teinte}");
      }
      public Jpeg(Couleur teinte) : base(teinte)
      {
      }
      // constructeur "de copie" (écart de langage)
      protected Jpeg(Jpeg autre) : base(autre)
      {
      }
      public override Image Cloner()
      {
         return new Jpeg(this);
      }
   }
   class Png : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Png de couleur {Teinte}");
      }
      public Png(Couleur teinte) : base(teinte)
      {
      }
      protected Png(Png autre) : base(autre)
      {
      }
      public override Image Cloner()
      {
         return new Png(this);
      }
   }
   class Bmp : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Bmp de couleur {Teinte}");
      }
      public Bmp(Couleur teinte) : base(teinte)
      {
      }
      protected Bmp(Bmp autre) : base(autre)
      {
      }
      public override Image Cloner()
      {
         return new Bmp(this);
      }
   }
   static Image ModifierPeutÊtre(Image img)
   {
      Image backup = img.Cloner(); // schéma de conception (Design Pattern :) )
//       Image backup = img; // 1) faire une copie de sauvegarde (backup) de img (oups!)
      Console.Write("Avant : ");
      img.Dessiner();
      img.Teinte = Couleur.Rouge; // 2) modifier img
      Console.Write("Après : ");
      img.Dessiner();
      // 3) demander à l'usager s'il veut conserver la modif
      Console.Write("Conserver la modification? (o/n) "); 
      char c = char.Parse(Console.ReadLine());
      if (char.ToLower(c) == 'o')
         return img; // 4) si oui, retourner l'entité modifiée
      return backup; // 5) sinon, retourner la copie de sauvegarde
   }
   static void Main()
   {
      Image img = new Jpeg(Couleur.Bleu);
      img = ModifierPeutÊtre(img);
      Console.Write("Résultat : ");
      img.Dessiner();
   }
}

26 août

S01

Note importante : il est probable que votre chix prof arrive avec un peu de retard ce matin (rentrée scolaire du plus jeune!), mais il arrivera éventuellement, alors profitez du moment d'attente pour faire progresser votre travail et vos réflexions.

Au menu :

  • Retour sur la petite activité formative proposée à S00
  • Discussion de divers aspects techniques et architecturaux associés à cette activité
  • Avenues de raffinement ou d'optimisation
  • Quelques explorations qui nous mèneront vers notre premier travail pratique, le TP00

À titre de référence, le code d'aujourd'hui est le suivant (ce code est perfectible; nous ferons bien mieux plus tard dans la session) :

using System;
using System.Collections.Generic;

class Program
{
   class Algos
   {
      //
      // Rappel : quand une fonction se limite à « return » suivi d'une
      // expression, les deux formes suivantes sont équivalentes (c'est
      // pour vous encourager à écrire de petites fonctions qui font une
      // et une seule chose!)
      //
      public static bool EstEntreInclusif(int val, int min, int max) =>
         min <= val && val <= max;
      //public static bool EstEntreInclusif(int val, int min, int max)
      //{
      //   return min <= val && val <= max;
      //}
   }
   static class OutilsTexte
   {
      private static List<char> voyelles = new List<char>(){ 'a', 'e', 'i', 'o', 'u', 'y' };
      public static bool EstVoyelle(char c) => voyelles.Contains(char.ToLower(c));
      public static int CompterVoyelles(string s)
      {
         int nb = 0;
         foreach (char c in s)
            if (EstVoyelle(c))
               ++nb;
         return nb;
      }
   }
   class NomInvalideException : Exception
   {
      public NomInvalideException(string nom) : base($"Nom invalide : {nom}")
      {
      }
   }
   class Orque
   {
      private static bool EstNomValide(string s)
      {
         const int MIN_CAR = 1,
                   MAX_CAR = 4;
         const int MAX_VOYELLES = 1;
         return s != null && Algos.EstEntreInclusif(s.Length, MIN_CAR, MAX_CAR) &&
                             OutilsTexte.CompterVoyelles(s) <= MAX_VOYELLES;
      }
      private string nom;
      public string Nom
      {
         get => nom;
         // private init
         // {
         //    if (!EstNomValide(value))
         //       throw new NomInvalideException(value);
         //    nom = value;
         // }
         private init
         {
            nom = !EstNomValide(value) ? throw new NomInvalideException(value) : value;
         }
      }
      public Orque(string nom)
      {
         Nom = nom;
      }
   }
   static void Permuter(ref Orque a, ref Orque b)
   {
      Orque temp = a;
      a = b;
      b = temp;
   }
   static bool Trier(ref List<Orque> lst, out int nbPerm)
   {
      nbPerm = 0;
      Orque[] tab = lst.ToArray();
      // tri à bulles... très, très naïf
      for (int i = 0; i < tab.Length - 1; ++i)
         for (int j = i + 1; j < tab.Length; ++j)
            //
            // s'ils ne sont pas dans l'ordre, les permuter
            //
            // deux versions du même test : méthode d'instance (en commentaire)
            // et méthode de classe (pas en commentaire). L'avantage de la méthode
            // de classe est qu'elle fonctionnera aussi dans le cas où l'un des deux
            // paramètres est null
            //
            //if (tab[i].Nom.CompareTo(tab[j].Nom) > 0)
            //
            if(string.Compare(tab[i].Nom, tab[j].Nom) > 0)
            {
               Permuter(ref tab[i], ref tab[j]);
               ++nbPerm;
            }
      lst = tab.ToList();
      return nbPerm == 0;
   }
   static void Main(string[] args)
   {
      List<Orque> orques = new List<Orque>();
      try
      {
         for (string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
         {
            orques.Add(new Orque(s));
            Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
         }
      }
      catch (NomInvalideException ex)
      {
         Console.WriteLine(ex.Message);
      }
      if (Trier(ref orques, out int nbPermutations))
      {
         Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
      }
      else
      {
         Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
      }
      Console.Write("La tribu d'orques est :");
      foreach (Orque orque in orques)
         Console.Write($" {orque.Nom}");
   }
}

En espérant que cela vous soit utile!

Nous avons aussi discuté sommairement du clonage. À titre de référence, le code produit lors de cette séance pour démontrer le clonage était :

using System;
class Program
{
   enum Couleur { Rouge, Vert, Bleu }
   // il existe une interface ICloneable, qui expose une méthode Clone
   // ... mais ne l'utilisez pas :)
   abstract class Image
   {
      public Couleur Teinte { get; private set; } 
      public void Modifier(Couleur teinte)
      {
         Teinte = teinte;
      }
      public abstract void Dessiner();
      protected Image(Couleur teinte)
      {
         Teinte = teinte;
      }
      protected Image(Image autre)
      {
         Teinte = autre.Teinte;
      }
      public abstract Image Cloner();
   }
   class Jpeg : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Jpeg de couleur {Teinte}");
      }
      public Jpeg(Couleur teinte) : base(teinte)
      {
      }
      // constructeur "de copie" (écart de langage)
      protected Jpeg(Jpeg autre) : base(autre)
      {
      }
      public override Image Cloner() => new Jpeg(this);
   }
   class Png : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Png de couleur {Teinte}");
      }
      public Png(Couleur teinte) : base(teinte)
      {
      }
      protected Png(Png autre) : base(autre)
      {
      }
      public override Image Cloner() => new Png(this);
   }
   class Bmp : Image
   {
      public override void Dessiner()
      {
         Console.WriteLine($"Je suis un Bmp de couleur {Teinte}");
      }
      public Bmp(Couleur teinte) : base(teinte)
      {
      }
      protected Bmp(Bmp autre) : base(autre)
      {
      }
      public override Image Cloner() => new Bmp(this);
   }
   static Image ModifierPeutÊtre(Image img)
   {
      Image backup = img.Cloner(); // schéma de conception (Design Pattern :) )
//       Image backup = img; // 1) faire une copie de sauvegarde (backup) de img (oups!)
      Console.Write("Avant : ");
      img.Dessiner();
      img.Modifier(Couleur.Rouge); // 2) modifier img
      Console.Write("Après : ");
      img.Dessiner();
      // 3) demander à l'usager s'il veut conserver la modif
      Console.Write("Conserver la modification? (o/n) "); 
      char c = char.Parse(Console.ReadLine());
      // 4) si oui, retourner l'entité modifiée
      // 5) sinon, retourner la copie de sauvegarde
      return char.ToLower(c) == 'o'? img : backup;
   }
   static void Main()
   {
      Image img = new Jpeg(Couleur.Bleu);
      Console.Write("Avant : ");
      img.Dessiner();
      img = ModifierPeutÊtre(img);
      Console.Write("Après : ");
      img.Dessiner();
   }
}

30 août

S02

Au menu :

  • Propriétés : get, set et init
  • Créer une bibliothèque à liens dynamiques : création d'un petit système client / serveur
    • schéma de conception Interface et son implémentation en C#
    • implémentation(s) de cette interface
    • schéma de conception Fabrique
  • Écriture d'un client pour ce service
  • Utilité de ce type d'architecture
  • Présentation du TP00 (voir #tp pour les consignes de travaux pratiques en général)
  • Petit bonbon : introduction aux uplets
  • Travail sur le TP00

2 sept.

S03

Au menu :

Je vais revenir sur using, que je n'ai pas couvert aujourd'hui, et finally, le tout quand nous aurons rencontré des cas qui les rendent pertinent (ça s'en vient)

6 sept.

s/o

Pas de cours aujourd'hui (Fête du travail)

9 sept.

S04

Au menu :

  • Petite introduction à la complexité algorithmique et à la consommation de ressources :
    • pour un générateur séquentiel :
    • pour un générateur recycleur :
    • pour un générateur aléatoire naïf :
    • raffiner le générateur aléatoire
  • Examen du problème de la génération des statistiques :
    • conceptualiser les paires
    • introduction aux dictionnaires
    • traduction des paires clés / valeurs en uplets
  • Travail sur le TP00

N'oubliez pas de remettre votre TP00 avant  23 h 59 le 10 septembre

13 sept.

S05

Au menu :

À la demande générale, le code (incomplet mais fonctionnel) de l'observateur de clavier avec interfaces est :

using System;
using System.Collections.Generic;

namespace z
{
   
   class Program
   {
      //
      // Deux schémas de conception (Design Patterns)
      // ce matin : Singleton, Observateur
      //
      interface IRéactionClavier
      {
         void Réagir(ConsoleKeyInfo clé);
      }
      class Afficheur : IRéactionClavier
      {
         public void Réagir(ConsoleKeyInfo clé)
         {
            Console.Write(clé.KeyChar);
         }
      }
      interface ISignalFin
      {
         void Signaler();
         bool Terminé();
      }
      class CroqueMort : ISignalFin
      {
         bool Fin { get; set; } = false;
         public void Signaler() => Fin = true;
         public bool Terminé() => Fin;
      }
      class Terminateur : IRéactionClavier
      {
         ISignalFin Signaleur { get; init; }
         public Terminateur(ISignalFin p)
         {
            Signaleur = p;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == ConsoleKey.Q /*ConsoleKey.Escape*/)
               Signaleur.Signaler();
         }
      }
      class GesClavier
      {
         static GesClavier singleton = null;
         List<IRéactionClavier> Abonnés { get; } =
            new List<IRéactionClavier>();
         public void Abonner(IRéactionClavier p)
         {
            Abonnés.Add(p);
         }
         GesClavier()
         {
         }
         public static GesClavier GetInstance()
         {
            //
            // note : dangereux si multithreading
            //
            if (singleton == null)
               singleton = new GesClavier();
            return singleton;
         }
         public void Exécuter()
         {
            var clé = Console.ReadKey(true);
            foreach (var p in Abonnés)
               p.Réagir(clé);
         }
      }
      //
      // diantre, beaucoup de répétition de code n'est-ce
      // pas? mais on va dormir là-dessus pour le moment :)
      //
      class ÀDroite : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀDroite(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Est");
         }
      }
      class EnHaut : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnHaut(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Nord");
         }
      }
      class ÀGauche : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀGauche(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Ouest");
         }
      }
      class EnBas : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnBas(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Sud");
         }
      }
      static void Main()
      {
         var ges = GesClavier.GetInstance();
         var croc = new CroqueMort();
         ges.Abonner(new Terminateur(croc));
         ges.Abonner(new ÀDroite(ConsoleKey.D));
         ges.Abonner(new EnHaut(ConsoleKey.W));
         ges.Abonner(new ÀGauche(ConsoleKey.A));
         ges.Abonner(new EnBas(ConsoleKey.S));
         // ges.Abonner(new Afficheur());
         while (!croc.Terminé())
            ges.Exécuter();
      }
   }
}

À la demande générale, le code (incomplet mais fonctionnel) de l'observateur de clavier avec délégiés est :

using System;
using System.Collections.Generic;

namespace z
{
   
   class Program
   {
      // Deux schémas de conception (Design Patterns)
      // ce matin : Singleton, Observateur
      //interface IRéactionClavier
      //{
      //   void Réagir(ConsoleKeyInfo clé);
      //}
      delegate void RéactionClavier(ConsoleKeyInfo clé);
      class Afficheur // : IRéactionClavier
      {
         public void Afficher(ConsoleKeyInfo clé) // note: nom :)
         {
            Console.Write(clé.KeyChar);
         }
      }
      interface ISignalFin
      {
         void Signaler();
         bool Terminé();
      }
      class CroqueMort : ISignalFin
      {
         bool Fin { get; set; } = false;
         public void Signaler() => Fin = true;
         public bool Terminé() => Fin;
      }
      class Terminateur // : IRéactionClavier
      {
         ISignalFin Signaleur { get; init; }
         public Terminateur(ISignalFin p)
         {
            Signaleur = p;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == ConsoleKey.Q /*ConsoleKey.Escape*/)
               Signaleur.Signaler();
         }
      }
      class GesClavier
      {
         static GesClavier singleton = null;
         List<RéactionClavier> Abonnés { get; } =
            new List<RéactionClavier>();
         public void Abonner(RéactionClavier p)
         {
            Abonnés.Add(p);
         }
         GesClavier()
         {
         }
         public static GesClavier GetInstance()
         {
            // note : dangereux si multithreading
            if (singleton == null)
               singleton = new GesClavier();
            return singleton;
         }
         public void Exécuter()
         {
            var clé = Console.ReadKey(true);
            foreach (var réac in Abonnés)
               réac(clé);
         }
      }
      // diantre, beaucoup de répétition de code n'est-ce
      // pas? mais on va dormir là-dessus pour le moment :)
      class ÀDroite // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀDroite(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Est");
         }
      }
      class EnHaut // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnHaut(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Nord");
         }
      }
      class ÀGauche // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public ÀGauche(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Ouest");
         }
      }
      class EnBas // : IRéactionClavier
      {
         ConsoleKey Touche { get; init; }
         public EnBas(ConsoleKey clé)
         {
            Touche = clé;
         }
         public void Réagir(ConsoleKeyInfo clé)
         {
            if (clé.Key == Touche)
               Console.WriteLine("Sud");
         }
      }
      static void Main()
      {
         var ges = GesClavier.GetInstance();
         var croc = new CroqueMort();
         // ges.Abonner(new Afficheur().Afficher);
         ges.Abonner(new Terminateur(croc).Réagir);
         ges.Abonner(new ÀDroite(ConsoleKey.D).Réagir);
         ges.Abonner(new EnHaut(ConsoleKey.W).Réagir);
         ges.Abonner(new ÀGauche(ConsoleKey.A).Réagir);
         ges.Abonner(new EnBas(ConsoleKey.S).Réagir);
         // ges.Abonner(new Afficheur());
         while (!croc.Terminé())
            ges.Exécuter();
      }
   }
}

16 sept.

S06

Au menu :

20 sept.

S07

Au menu :

  • Retour sur Q00
  • Retour sur le TP00
    • Mes pratiques de correction et mes codes de correction
    • Comment j'ai ajusté la pondération à votre avantage (pour MOD, NRC et NFP en particulier)
    • Sources de confusion :
      • Exceptions (où mettre un try? Type ou message?)
      • Qualifications d'accès (protected, public, internal...)
      • Sens du mot static
      • Quand afficher à la console... et quand ne pas le faire?
      • Rôle d'un constructeur (ou : que se passe-t-il quand un attribut d'instance – en particulier une string – n'est pas initialisé en C#)
  • Quelle police utiliser quand un imprime du code?
  • Travail sur le TP01

23 sept.

S08

À venir

27 sept.

S09

À venir

30 sept.

S10

À venir

4 oct.

S11

À venir

7 oct.

S12

À venir

11 oct.

s/o

Pas de cours aujourd'hui (Action de grâce)

14 oct.

s/o

Pas de cours aujourd'hui (journée de mise à niveau)

18 oct.

S13

À venir

21 oct.

S14

À venir

25 oct.

S15

Je serai absent cette semaine car je donne des conférences à CppCon. Vous pourrez suivre mes aventures sur (lien à venir)

28 oct.

S16

Je serai absent cette semaine car je donne des conférences à CppCon. Vous pourrez suivre mes aventures sur (lien à venir)

1 nov.

S17

À venir

4 nov.

S18

À venir

8 nov.

S19

À venir

11 nov.

S20

À venir

15 nov.

S21

À venir

18 nov.

S22

À venir

22 nov.

S23

À venir

25 nov.

S24

À venir

29 nov.

S25

À venir

2 déc.

S26

À venir

6 déc.

S27

À venir

9 déc.

S28

À venir

13 déc.

S29

À remettre :

  • La PFI de la session A2021

Chic examen final

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.

Vous trouverez aussi des exemples de code C# dans la section Divers – C# du site, mais notez que je n'ai pas nécessairement harmonisé ces exemples (écrits pour des cours plus avancés, sous forme de survols) aux standards de programmation appliqués dans le présent cours. À lire avec prudence et discrimination, donc.

Consignes des travaux pratiques

Les consignes des travaux pratiques suivent.

Consignes Détails supplémentaires

Activité de révision

Voir S00 et S01

TP00

Pour un programme de test simpliste, voir TP00-code-test-simpliste.html

TP01

Pour un exécutable de démonstration : RaymondCharles.exe

Ils vous faudra aussi, dans le même dossier :

Pour essayer ce démonstrateur, une fois ces quatre fichiers dans un même dossier, allez à la ligne de commande, déplacez-vous dans ce dossier et tapez :

RaymondCharles.exe CarteText.txt

... et ça devrait démarrer le tout.

TP02a

À venir

TP02b

À venir

PFI

À venir

Solutionnaires

Quelques solutionnaires suivent.

Travail

À venir

À venir

À venir

À venir

En espérant que ça vous aide à organiser vos idées!


Valid XHTML 1.0 Transitional

CSS Valide !