PROFDINFO.COM

Votre enseignant d'informatique en ligne

Révision des notions avancées de la programmation orientée objets

Version C#

1) Constructeurs et destructeurs

Voici la représentation UML de quatre classes CVehicule,  CAvion , CAutomobile et CDecapotable:
 

uml

 

La classe CDecapotable dérive de la classe CAutomobile qui elle-même dérive de la classe CVehicule.  Lors de la création d’un objet de la classe CDecapotable, Il y a donc trois constructeurs impliqués dans la création de l’objet.   De façon analogue, lorsqu’un objet CDecapotable est détruit, il y a trois destructeurs impliqués. 

  1. Déterminer l’ordre d’appel des constructeurs et l’ordre d’appel des destructeurs.

    L'ordre d'appel des constructeurs et des destructeurs est le même en C# qu'en C++: on construit de la classe de base vers la classe dérivée et on détruit dans l'autre sens.

  2. De qu’elle façon allez-vous procéder afin de déterminer ces séquences d’appel?

    De la même façon qu'en C++!

  3. Qu’est-ce qui a motivé les concepteurs du C# à opter pour un ordre précis? 

    Le même raisonnement s'applique. Jusqu'à maintenant, tout est pareil.

2) Pointeurs de base

Il n'y a pas de pointeurs explicites en C#. Les symboles *, & et -> ne sont donc pas utilisés. Le principe derrière reste exactement le même toutefois. Remarquez également que les parenthèses () sont obligatoires en C#.

  1. Quels sont les lignes pour lesquels le compilateur génère une erreur?
  2.  

    CDecapotable  Obj1   = new CDecapotable();
    CDecapotable  Obj2   = new CAutomobile();
    CDecapotable  Obj3   = new CAvion(); 
    CDecapotable  Obj4   = new CVehicule(); 
    CAutomobile   Obj5   = new CDecapotable();
    CAutomobile   Obj6   = new CAutomobile();
    CAutomobile   Obj7   = new CAvion();
    CAutomobile   Obj8   = new CVehicule(); 
    CAvion        Obj9   = new CDecapotable();
    CAvion        Obj10  = new CAutomobile();
    CAvion        Obj11  = new CAvion();
    CAvion        Obj12  = new CVehicule(); 
    CVehicule     Obj13  = new CDecapotable();
    CVehicule     Obj14  = new CAutomobile();
    CVehicule     Obj15  = new CAvion();
    CVehicule     Obj16  = new CVehicule(); 

     

  3. Quelle conclusion tirez-vous de cette expérience?

  4. Évidemment, la validité des lignes reste la même qu'en C++: on peut faire pointer un pointeur (on dira plutôt: "une variable de type référence") vers un objet du même type ou d'un type dérivé. Du coup, les lignes en rouge sont invalides.

3) Redéfinition de fonctions

Une classe peut implémenter une méthode et hériter de la même méthode de sa classe de base. 

  1. Pour chacun des programmes suivants, déterminez la version de la méthode Deplacer qui sera utilisée.

  2. CDecapotable Obj1 = new  CDecapotable();
    CAutomobile  Obj2 = new  CDecapotable();
    CAutomobile  Obj3 = new  CAutomobile();
    CAvion       Obj4 = new  CAvion();
    CVehicule    Obj5 = new  CDecapotable();
    CVehicule    Obj6 = new  CAutomobile();
    CVehicule    Obj7 = new  CAvion();
    CVehicule    Obj8 = new  CVehicule(); 
    
    Obj1.Deplacer(1.0f);
    Obj2.Deplacer(1.0f);
    Obj3.Deplacer(1.0f);
    Obj4.Deplacer(1.0f);
    Obj5.Deplacer(1.0f);
    Obj6.Deplacer(1.0f);
    Obj7.Deplacer(1.0f);
    Obj8.Deplacer(1.0f);


    Mise à part la syntaxe, cet exercice est le même que son homologue C++.


  3. De qu’elle façon le programme détermine-t-il la version de la méthode à utiliser?

    C'est la même chose en C# qu'en C++, lorsqu'aucun polymorphisme n'est utilisé (ce qui est le cas ici), le programme utilise la méthode associée au type de pointeur, peu importe sur quoi il pointe.

 

4)  Fonctions polymorphes

En C++, on disait: "Le mot-clé virtual a été ajouté devant toutes les méthodes Deplacer."

L'effet cette fois-ci n'est pas le même puisque cette façon de faire n'est pas correcte en C#. On doit mettre le mot-clé virtual devant la méthode de la classe de base, puis le mot-clé override devant toutes les méthodes dérivées pour obtenir l'effet équivalent.

En effet, avec virtual, on déclare que la méthode est polymorphique. Lorsqu'on veut effectivement faire une version différente de la méthode héritée, on utilise override. Le mot-clé new, quant à lui, permet de masquer une fonction non polymorphique pour la remplacer par une autre. Par exemple, dans CVehicule, je pourrais avoir une méthode Deplacer() sans virtual. Dans CAutomobile, je peux en faire une autre version avec le mot-clé new. Cela revient exactement au même que de ne mettre aucun mot-clé ni dans CVehicule ni dans CAutomobile, toutefois si on ne met rien, le compilateur nous donnera un avertissement d'être plus explicite, ce qui n'est effectivement pas une mauvaise idée.

  1. Pour chacun des programmes de l’exercice précédent, déterminez la version de la méthode Deplacer qui sera utilisée.
  2. De qu’elle façon le programme détermine-t-il la méthode à utiliser lorsque les méthodes sont virtuelles ?

    Une fois la syntaxe des virtual/override éclaircie, le fonctionnement reste le même.

 

5) Bubu, Toto et Titi

Soit les déclarations et définitions suivantes:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            Toto to = new Toto(1, 2);
            Titi ti = new Titi(3, 4, 5);
        }
    }

    public class Bubu
    {
        public int x;

        public Bubu(int a)
        {
            Console.WriteLine("++Bubu début");
            x = a;
            Console.WriteLine("++Bubu x = {0}", x);
            Console.WriteLine("++Bubu fin");
        }

        ~Bubu()
        {
            Console.WriteLine("--Bubu debut x = {0}", x);
            Console.WriteLine("--Bubu fin");
        }
    }

    public class Toto
    {
        public Bubu pbu;
        public int y;

        public Toto(int a, int b)
        {
            Console.WriteLine("++Toto début");
            y = b;
            Console.WriteLine("++Toto y = {0}", y);
            pbu = new Bubu(a);
            Console.WriteLine("++Toto fin");
        }

        ~Toto()
        {
            Console.WriteLine("--Toto debut y = {0}", y);
            pbu = null;  // Absolument facultatif!
            Console.WriteLine("--Toto fin");
        }
    }

    public class Titi : Toto
    {
        public int z;

        public Titi(int a, int b, int c)
            : base(a, a + b)
        {
            Console.WriteLine("++Titi début");
            z = c;
            Console.WriteLine("++Titi z = {0}", z);
            Console.WriteLine("++Titi fin");
        }

        ~Titi()
        {
            Console.WriteLine("--Titi début z = {0}", z);
            Console.WriteLine("--Titi fin");
        }
    }
}
  1. Quelle est la sortie du programme?

    La sortie du programme est la même au départ: les constructeurs sont appelés dans le même ordre et produisent la même sortie. La différence est au niveau des destructeurs puisque c'est le collecteur de déchets (Garbage Collector) qui décide dans quel ordre détruire les objets. Toutefois, quand un Titi est détruit, il sera détruit avant son Toto de base.

    ++Toto début
    ++Toto y = 2
    ++Bubu début
    ++Bubu x = 1
    ++Bubu fin
    ++Toto fin
    ++Toto début
    ++Toto y = 7
    ++Bubu début
    ++Bubu x = 3
    ++Bubu fin
    ++Toto fin
    ++Titi début
    ++Titi z = 5
    ++Titi fin
    (la fin peut changer)

  2. On ajoute la fonction membre imprimer() dans la classe Toto et on modifie le main():

  3. public class Toto
        {
            ...
            public void imprimer()
            {
                Console.WriteLine("Toto::imprimer: {0} {1}", pbu.x, y);
            }
            ...
        }
        
            static void Main()
            {
                Toto to = new Toto(6, 7);
                Titi ti = new Titi(8, 9, 0);
                to.imprimer();          // Instruction A
                ti.imprimer();          // Instruction B
            }

    Donnez la sortie des instructions A et B.

    Aucune différence avec le C++.

  1. On ajoute la fonction membre imprimer() dans la classe Titi :

  2. public class Toto
        {
            ...
            public void imprimer()
            {
                Console.WriteLine("Toto::imprimer: {0} {1}", pbu.x, y);
            }
            ...
        }
        
            static void Main()
            {
                Toto to = new Toto(6, 7);
                Titi ti = new Titi(8, 9, 0);
                to.imprimer();          // Instruction A
                ti.imprimer();          // Instruction B
            }

    Donnez la sortie des instructions A et B de la question précédente.

    Aucune différence avec C++

  3. On modifie le programme principal :
  4.  

    static void Main()
    {
        Toto to = new Toto(44, 55);
        Titi ti = new Titi(11, 22, 33);
        Toto pto = to;
        Titi pti = ti;
    
        pto.imprimer();
        pti.imprimer();
        pto = pti;
        pto.imprimer();
    }

    Donnez la sortie des instructions A, B et C.

    Aucune différence avec le C++

  5. On ajoute le mot-clé virtual devant la fonction membre imprimer de la classe Toto :

  6. public class Toto
        {
            ...
            public virtual void imprimer()
            {
                Console.WriteLine("Toto::imprimer: {0} {1}", pbu.x, y);
            }
            ...
        }


    Que doit-on faire de plus pour que la sortie des instructions A, B, C de la question précédente soit la même que dans l'exercice correspondant en C++?

6) Les ballons

Voici un programme principal :

using System;
using System.Collections.Generic;
using System.Text;

namespace khh_ballons
{
// Les classes sont déclarées ici!
    class Program
    {
        static void Main()
        {
            Objet o;
            Porcelaine p;
            Ballon b;
            BallonRugby bR;
            BallonFoot bF;

            o.rebondir();
            p.rebondir();
            b.rebondir();
            bR.rebondir();
            bF.rebondir();

            Objet po = o;
            po.rebondir();
            po = p;
            po.rebondir();
            po = b;
            po.rebondir();
            po = bR;
            po.rebondir();
            po = bF;
            po.rebondir();
        }
    }
}

Donnez l'équivalent en C# des six (6) déclarations de classes données dans l'exercice correspondant en C++, de façon à ce qu'elles produisent les mêmes sorties:

a)

??
??
??
??
??
??
??
??
??
??

b)

??
:(
:)
:)
:)
??
??
??
??
??
                                             

c)

??
:(
:)
()
O
??
??
??
??
??

d)

??
:(
:)
()
O
??
??
??
??
??

e)

??
:(
:)
()
O
??
??
??
??
??

f)

??
:(
:)
()
O
??
:(
:)
()
O