LOCODUINO

Le monde des objets

Le monde des objets (2)

Des objets partout !

.
Par : Dominique, Thierry

DIFFICULTÉ :

Nous avons introduit dans l’article précédent la programmation « objet » dans l’IDE Arduino, les principes du C++ et ses avantages. Continuons d’en explorer les affriolantes capacités.

Visibilité, encapsulation

Nous allons maintenant développer une petite classe qui va nous servir d’exemple très simple à comprendre, mais qui fera partie d’un ensemble plus ambitieux dans les chapitres suivants...

Imaginons une Led, qui doit être initialisée avec un numéro de broche et qui peut s’allumer et s’éteindre, donc avec une variable qui stockera son état. Le clignotement est un développement ultérieur possible...

La déclaration pourrait ressembler à ça :

class Led
{
  // Données
  byte pin;
  byte etat;
  // Méthodes
  void Setup(byte aPin);  // initialisation de la broche pour la led.
  void Allumer();
  void Eteindre();
};

pin stocke le numéro de broche dont on aura besoin à chaque changement d’état, lequel est mémorisé dans etat. Les trois méthodes Setup() Allumer() et Eteindre() permettent respectivement d’initialiser la classe et ses données, d’allumer ou d’éteindre la diode.

Petite remarque syntaxique : le ’;’ est vraiment important à la fin de la définition de la classe. Il est obligatoire, tout comme pour un enum ou un struct en C.

Comme nous l’avons vu dans le premier chapitre, l’encapsulation consiste à regrouper sous un même nom tout ce qui concerne un type d’objet, données et méthodes. Il permet également de ne laisser accessible que ce qui doit l’être vu de l’extérieur. Il y a trois niveau de protection du contenu d’une classe : privé, protégé et public.

  • Si rien n’est précisé comme dans notre exemple Led, tout est privé. Ce qui est privé n’est visible que des objets de la classe elle même. Cela veut dire par exemple que cette première version de la classe ne peut pas fonctionner. Tout est privé, ce qui empêche quiconque d’accéder aux méthodes Setup() Allumer() et Eteindre() ! Voilà une classe qui perd un peu de son intérêt.
  • Ce qui est déclaré protected (protégé) est visible par la classe elle même, mais aussi par les classes dérivées, concept que nous aborderons un peu plus tard.
  • Enfin les éléments publics sont visibles par tous.

Ce sont les rôles de deux nouveaux mot-clés private et public illustrés dans la classe Led.

class Led
{
private:
  byte pin;
  byte etat;
 
public:
  void Setup(byte aPin);  // initialisation de la broche pour la led.
  void Allumer();
  void Eteindre();
};

Leur sens est évident : private cache les données et les méthodes aux yeux du monde, mis à part les objets de la classe Led, et public les rend visibles et modifiables par tout le monde.
Par exemple si on utilise la classe Led ci-dessus, le code suivant est incorrect :

setup()  // setup général du .ino
{
  Led maled;
  maled.pin = 10;  // Erreur de compilation, pin n'est pas accessible.
}

Ce qui est logique ici, puisque l’on veut que l’utilisateur de la classe passe forcément par Setup() pour être sûr de bien initialiser la broche... Notez l’utilisation du ’.’ pour accéder aux membres d’un exemplaire de la classe comme ’maled’ ici. C’est la même syntaxe, le même principe que pour une structure.

Allons plus loin. Pour une DEL, selon la façon dont elle est câblée, un état haut (HIGH) de la broche peut l’allumer ou l’éteindre ! Voir notamment à la fin de l’article consacré aux leds.

Pour que le classe Led fonctionne dans les deux cas, il faut définir le type de montage de la led : normal (broche reliée à l’anode et fournissant le courant) ou inversé (broche reliée à la cathode et absorbant le courant), afin d’être sûr que lorsque l’on appelle la méthode Allumer(), le résultat est bien celui espéré... Pour simplifier le codage, passons alors par une fonction privée Rafraichir qui fera l’interprétation voulue.

/// Déclaration de la classe Led

class Led
{
private:
  byte pin;
  byte etat;  // HIGH pour allumé, LOW pour éteint, 
              // quelque soit la valeur de montageInverse
  bool montageInverse; // true si il faut LOW pour allumer !
  void Rafraichir();
 
public:
  void Setup(byte aPin, bool aMontageInverse = false);  // initialisation de la broche
                                                        // pour la led.
  void Allumer();
  void Eteindre();
};
 
// Définition des méthodes.
void Led::Setup(byte aPin, bool aMontageInverse)
{
  montageInverse = aMontageInverse;
  pin = aPin;
  pinMode(aPin, OUTPUT);

  // Commençons diode éteinte...
  Eteindre();
}
 
void Led::Allumer()
{
  etat = HIGH; // Allumé
  Rafraichir();
}
 
void Led::Eteindre()
{
  etat = LOW; // Eteint
  Rafraichir();
}
 
void Led::Rafraichir()
{
  byte vraiEtat = etat;         // on prend l'état demandé
  if (montageInverse == true)  // si c'est un montage inversé
    vraiEtat = ! vraiEtat;      // on inverse
  digitalWrite(pin, vraiEtat);
}
 
// Programme Arduino
 
Led maled;
 
void setup()
{
  maled.Setup(10, false);
}

Petite remarque syntaxique : la compilation du source commence en haut, et va vers le bas. Il est donc important que la déclaration précède la définition. En clair, la définition des méthodes doit se faire après la déclaration de la classe !

Les méthodes Allumer() et Eteindre() se contentent de mettre à jour etat, puis la méthode privée Rafraichir() est appelée et s’occupe de modifier l’état matériel de la broche en fonction de l’état demandé et du type de montage. Noter la valeur par défaut de l’argument dans la déclaration de la méthode Setup :

void Setup(byte aPin, bool aMontageInverse = false);

Le fait de donner une valeur initiale à aMontageInverse permet d’omettre cet argument en appelant Setup() quand sa valeur est false. Cela permet d’écrire soit maled.Setup(10); et aMontageInverse est initialisé automatiquement à false, soit maled.Setup(10, false);, sinon il faut spécifier un état inverse avec maled.Setup(10, true);.

Résultat de tout cela, on a ajouté une fonctionnalité à la classe sans toucher au reste du programme, et avec l’assurance d’un minimum de perturbations. Personne n’a besoin de savoir comment se fait le changement d’état, ni quelle méthode a été appelée. Rendre privé une partie du code et des données rend l’objet plus simple vu de l’extérieur, malgré une complexité intérieure qui peut être très importante. L’interface avec le reste du code reste humainement compréhensible...

On pourrait écrire la méthode privée Rafraichir() de cette autre façon :

void Led::Rafraichir()
{
  digitalWrite(pin, etat ^ montageInverse);
}

^ est l’opérateur du OU Exclusif qui donne les résultats suivants :

etat montageInverse résultat
1 (HIGH) 1 (true) 0 (LOW)
1 (HIGH) 0 (false) 1 (HIGH)
0 (LOW) 1 (true) 1 (HIGH)
0 (LOW) 0 (false) 0 (LOW)

Reportez-vous à « Calculer avec l’Arduino (2) » pour des informations complémentaires sur le OU Exclusif.

Constructeurs...

Une classe n’est pas un objet. Ce serait un peu comme dire que la table des matières est le livre ! Une classe, c’est la description de ce qu’un objet de ce type doit contenir et comment il doit se comporter. A un moment, il faut donc créer l’objet proprement dit, c’est à dire mettre en place en mémoire les variables de l’objet et les initialiser. On parle d’instanciation.

Ainsi lorsque l’on écrit

Led maled;

on fait appel implicitement à une fonction très particulière : le constructeur. C’est la première méthode de l’objet à être exécutée. La définition de cette méthode spéciale n’est pas obligatoire (si vous ne souhaitez pas initialiser les données par exemple) dans la mesure où une version par défaut de ce constructeur sans arguments existe toujours, automatiquement créée par le compilateur si elle n’est pas fournie par le programmeur, mais dans ce cas le contenu des données membres de l’objet créé est indéterminé. Typiquement, les entiers ne contiendront pas forcément 0 après la création de l’objet, mais plutôt n’importe quoi !

Cette méthode particulière est facilement identifiée par le fait que son nom est celui de la classe, et qu’elle n’accepte pas de valeur de retour :

class Led
{
private:
  byte pin;
  byte etat;
  bool montageInverse;
  void Rafraichir();
 
public:
  Led(); // Constructeur
 
  void Setup(byte aPin, bool aMontageInverse = false);  // initialisation de la broche
                                                        // pour la led.
  void Allumer();
  void Eteindre();
};
 
Led::Led()
{
  pin = 0;
  Eteindre();
}

Plutôt que d’avoir un constructeur qui va tout mettre à 0, puis un Setup qui va vraiment faire l’initialisation, répartissons mieux les rôles. On peut ajouter à la classe un constructeur avec des arguments :

Led::Led(int aPin, bool aMontageInverse = false)
{
  pin = aPin;
  montageInverse = aMontageInverse;
}

void Led::Setup()
{
  pinMode(pin, OUTPUT);

  Eteindre();
}

L’initialisation d’une nouvelle instance est ainsi modifiée :

Led maled(10);
maled.Setup();

Les rôles sont mieux répartis entre le constructeur qui remplit les variables locales de la classe, et le Setup() qui les utilise pour initialiser l’objet au bon moment.

... et destructeurs

Par symétrie avec le constructeur, il y a aussi un destructeur optionnel, dont le nom commence par un caractère ~ (ça se prononce ’tilde’ en français). Il suffit de savoir que c’est possible en C++, même si son usage est assez limité sur Arduino.

Code complet

Avant de clore ce chapitre, voici le code complet de la petite classe Led :

/// Déclaration de la classe Led
 
class Led
{
private:
  byte pin;
  byte etat;  // HIGH pour allumé, LOW pour éteint,
              // quelque soit la valeur de montageInverse
  bool montageInverse; // true si il faut LOW pour allumer !
  void Rafraichir();
  
public:
  Led(byte aPin, bool aMontageInverse = false);  // constructeur complet
  void Setup();
  void Allumer();
  void Eteindre();
};
 
// Définition des méthodes. Par convention, on commence par le constructeur
Led::Led(byte aPin, bool aMontageInverse)
{
  pin = aPin;
  montageInverse = aMontageInverse;
}

// Et le reste, dans l'ordre de définition de la classe pour s'y retrouver...
void Led::Setup()
{
  pinMode(pin, OUTPUT);  
  // Je décide de commencer dans un état éteint...
  Eteindre();  
}
 
void Led::Allumer()
{
  etat = HIGH; // Allumé
  Rafraichir();
}
 
void Led::Eteindre()
{
  etat = LOW; // Eteint
  Rafraichir();
}
 
void Led::Rafraichir()
{
  byte vraiEtat = etat;         // on prend l'état demandé
  if (montageInverse == true)  // si c'est un montage inversé
    vraiEtat = ! vraiEtat;     // on inverse
  digitalWrite(pin, vraiEtat);
 
  // ou la ligne unique suivante :
  // digitalWrite(pin, etat ^ montageInverse);
}
/// Fin de la classe Led
 
/// Partie classique du Sketch
// On crée 2 leds
Led rouge(10);  // pin 10
Led verte(11);  // pin 11
 
void setup()
{
  rouge.Setup();
  verte.Setup();
}
 
void loop()
{
  rouge.Allumer();
  delay(1000);
  rouge.Eteindre();
  delay(1000);
  verte.Allumer();
  delay(1000);
  verte.Eteindre();
  delay(1000);
}

Le premier gain de ce type d’écriture C++, c’est la lisibilité du code. La partie croquis classique setup+loop est vraiment réduite à l’essentiel et se concentre sur le comportement général. Le vieux proverbe ’Diviser pour régner’ est très utilisé en informatique et permet de simplifier les problèmes complexes en les réduisant en somme de problèmes simples. C’est exactement ce que permet l’objet et réduisant les méthodes à des rôles très simples. L’assemblage dans le croquis devient limpide !

Un autre avantage est la réutilisation. Au delà de la création de bibliothèque qui pourrait reprendre la classe, il est très simple de transférer Led dans un autre croquis. On est sûr de ne pas en emmener trop, et au pire le nettoyage (enlever le traitement du montage inverse, par exemple) n’est pas compliqué.

Enfin, et contrairement à une idée répandue, un gain de mémoire programme est très probable. Le principe de dérivation dont il sera question dans le troisième volet pousse à une réutilisation du code existant et à une rationalisation des données. Il en résulte souvent une économie notable pour la mémoire programme. Par contre, la mémoire vive, la SRAM, peut être impactée par les fonctions virtuelles, mais ça c’est une autre histoire !

Si c’est toujours clair, nous pourrons passer au chapitre suivant. Sinon, reprenez depuis le début !

23 Messages

  • Le monde des objets (2) 2 février 2015 20:38, par DDEFF

    Commentaire.Allumer()
    Merci Dominique et Thierry,
    Les esprits chagrins pourraient dire : tout ça pour allumer une LED...
    Mais, justement, parce que le problème est connu de tous, sa solution par les classes est très claire et facile à comprendre. Bravo !
    Commentaire.Eteindre()

    Répondre

    • Le monde des objets (2) 2 février 2015 22:29, par Dominique

      Je dois humblement avouer que c’est Thierry qui a tout écrit.
      Mon statut de co-auteur est minuscule !

      Répondre

  • Le monde des objets (2) 2 février 2015 20:42, par Thierry

    Merci.

    PS : tu as oublié d’initialiser ta classe avec Led Commentaire(13) ! :))

    Répondre

  • Le monde des objets (2) 22 avril 2017 07:55, par Denis

    Bonjour, je suis très en retard au vue de la publication mais je suis complètement séduit par cette approche de la programmation objet en C++.
    Bravo à vous.
    Juste une remarque pour voir si j’ai bien compris, pour la méthode Rafraichir() lorsque vous écrivez
    digitalWrite(pin, Etat ^ aMontageInverse) ne faut-il pas écrire plutôt
    digitalWrite(pin, etat ^ aMontageInverse) i.e la variable "état" sans majuscule ??
    Et deuxième petite question, est-ce une bonne pratique de commencer toutes les méthodes par des majuscules (Raffraichir(), Eteindre(), etc...) ?? ou ne doit-on pas réserver ceci au constructeur de la classe ?
    Merci encore pour cet article et les autres bien sur.

    Répondre

    • Le monde des objets (2) 13 janvier 2018 10:32, par Thierry

      Bonjour

      Avec presque trois ans de retard, je vais quand même répondre aux questions !

      La ligne devrait en fait être
      digitalWrite(pin, etat ^ montageInverse);
      Merci de me l’avoir fait remarquer. C’est corrigé dans l’article.

      Pour les majuscules sur les noms de fonction, c’est une question de conventions. Il n’y a pas de règle établie, vous faites ce qui vous semble juste. De mon côté, les fonctions ont toujours une majuscule pour commencer. Les seuls noms qui commencent par une minuscule sont les variables locales (internes à une fonction) et les données membres privées :

      class test
      {
       public:
         int Toto;
      
       private:
         int titi;
      
       public:
         void Fonction(int aTutu);
      
       private:
         void FonctionInterne();
      };
      
      void test::Fonction(int aTutu)
      {
        int tete = 0;
      
        tete = aTutu;
        titi = tete * 2;
      
      // ou mieux
      
        this->titi = tete * 2;
      }

      Tout est question d’habitudes, de conventions, de règles entre développeurs lorsque vous faites partie d’un groupe comme c’est mon cas au boulot ou ici. L’intérêt de ces règles est de permettre une relecture facile et une compréhension plus rapide du code lu.

      Répondre

      • Le monde des objets (2) 6 février 2020 00:54, par eddymaue

        bonjours ... ques années plus tard

        c’est vrai que tout est convention et les règles on les établis pour soi même en premier lieu et si on travail en groupe ... le tout est décidé en groupe... je viens du monde Visual foxpro et j’ai travaillé dans ce merveilleux environnement plus de 20 ans.... Ce que j’ai retenu de cette communauté c’est
        que la préfixation a été adopté comme commune à tous. Que nous soyons français, anglais, allemand, italien, chinois, japonais ou latino d’Amérique latine ... on a tous adopté la meme convention... et ca c’est grace leader de la communauté Visual Foxpro.

        Dans le monde de l’arduino il n’y a aucune convention autre que la déclaration global des objets, des variables et des entêtes... Ce qui fait que 500 ligne plus loin la variable Pin, comme exemple, est de type integer sans savoir s’y elle est propre à une classe, une structure ou d’un type déclaré. On sait que Pin est integer parce qu’on l’a déclaré 5 ligne plus hautes... j’appelle cela du code anarchique

        voilà de quoi à méditer ... et si vous l’essayer vous risquez de l’adopter

        si j’écris
        int giPin1 = 5

        ’g’ pour globale
        parce que accessible globalement partout dans le code et, bien sur, cette variable est déclarée avant void setup()

        ’i’ pour integer
        représente le type déclaré de la variable

        char gcChoix : global et caractère
        comme exemple un choix de menu

        dans mon cas tout ce qui est dans une classe
        g pour global
        p pour private ou protégé

        et a cause de l’intellisence de PIO ou VS communauty dans mes classe je fais comme suivant

        je préfixe comme suivant
        a pour attribut
        f pour fonction


        class test

        public :
        int giToto ;

        private :
        int a_piTiti ;

        public :
        void fgMaFonction(int tiTutu) ;

        private :
        void fpMaFonctionInterne() ;
         ;

        void test::fgMaFonction(int tiTutu) // nous avons préfixé par ’t’ pour paramètre pour ne pas confondre avec ’p’ qui veut dire privé ou protégé

        int liTete = 0 ; l pour local

        liTete = tiTutu ;
        a_piTiti = liTete * 2 ;

        // ou mieux

        this->a_piTiti = litete * 2 ;

        bon je nouveau dans le monde arduino, 1 an. Il s’est tant écrit avant moi, et il s’en écrira beaucoup après moi

        je n’ose croire que je réussisse à influencer qui que ce soit... c’est la vie. Mais si un jour je tombais sur du code usant d’une préfixation .... je lèverai un pouce

        Répondre

        • Le monde des objets (2) 6 février 2020 08:44, par jean-luc

          Bonjour.
          je ne connais pas visual foxpro et j’ai donc cherché en quoi consiste ce langage.
          visiblement il s’agit d’un langage de script non typé. Je comprends donc que vous prefixiez à tout va car c’est le seul moyen de retrouver ses petits.
          C et C++ ne sont pas des parangons du typage mais ils font raisonnablement le travail et le compilateur vous signalera des mélanges de pinceaux dans les variables de types différents.
          On peut préfixer pour noter le scope mais préfixer pour le type de données n’est pas utile.
          Par ailleurs je ne suis pas fan de ce que j’appellerai la sur-notation comme par exemple l’usage de this->. Certes cela permet de s’assurer que la variable qui nous intéresse est bien membre de l’objet et n’est pas une globale mais avec de la lourdeur qui nuit à la lisibilité du code, tout comme les préfixes qui sont de plus d’une lettre. D’ailleurs si on programme sans variable globale, le problème n’existe pas.

          Répondre

  • Le monde des objets (2) 1er septembre 2017 09:12, par debutante69

    Bonjour,
    Je découvre Arduino et ses subtilités et merci pour vos articles très bien expliqués.
    .
    Cependant, est-ce que l’ accolade point-virgule à la ligne 17
    ne devraient-ils pas être
    à la ligne 56 /// fin de la classe Led , avant le commentaire ?
    .
    PS : je ne suis pas arrivé à représenter l’accolade

    Répondre

    • Le monde des objets (2) 1er septembre 2017 14:26, par Thierry

      Il est en effet possible de coder toutes les fonctions directement dans la déclaration de la classe, mais ce n’est pas la façon de faire adoptée ici. Si la classe avait été incluse dans un fichier hpp et utilisée à divers endroits, le premier source compilé n’aurait pas posé de problème, mais le deuxième et les suivants auraient rouspété en signalant que les fonctions de la classe décrite et codées dans le hpp existaient déjà ! En séparant la classe de ses fonctions, on évite le problème. Et puis une fonction incluse dans une déclaration de classe est potentiellement ’inline’ au bon vouloir du compilateur, c’est un risque que je n’ai pas voulu courir. Une fonction ’inline’ est une fonction dont l’appel sera remplacé par son code avant la véritable compilation. Mais si le code en question est un peu lourd, le multiplier par autant d’appels va grever la mémoire programme, et ce peut être un problème.

      Répondre

  • Le monde des objets (2) 17 décembre 2017 20:18, par Eric

    Bonjour,

    Tout d’abord, je "bosse" sur raspberry, mais je ne vois pas de grosses différences avec l’arduino, excepté la fonction setup() et loop() dont on ne peut se passer sur celui ci.
    Grace a vous, j’ai pu enfin adapter mon code c, qui gère les interruptions, en POO.
    J’ai donc deux types de classes, une Actionneur et une Capteur.
    Au niveau de la classe Capteur, je souhaiterai ajouter une propriété privée qui est l’adresse de la routine d’interruption, qui est passée en paramètre lors de la création de l’objet.
    Mais je ne sais pas comment m’y prendre.
    Pour que mon programme fonctionne je suis obligé de passer l’adresse de la routine d’interruption dans la méthode conf qui elle est publique.
    je ne sais pas d’où vient le problème mais aléatoirement lorsque j’allume un relai (avec un bouton), une led qui est utilisée dans mon programme pour s’allumer lorsqu’il n’y a plus de lumière s’illumine.
    Je serais ravis de pouvoir vous montrer mon code afin d’avoir un avis sur la question

    Voir en ligne : Mon petit site sur raspberry

    Répondre

  • Le monde des objets (2) 18 décembre 2017 10:39, par Thierry

    Bonjour

    Votre problème ne traite d’aucun des sujets que nous souhaitons ici, l’Arduino et le modélisme ferroviaire. Je vous conseille donc de faire des recherches ciblées sur les pointeurs de fonction en C++. Vous ne devriez pas avoir de problème à trouver des réponses, les callback sont légion en développement.

    Répondre

  • Le monde des objets (2) 12 janvier 2018 23:36, par trimarco232

    Bonjour,

    si j’ai bien suivi ?
    dans le 1er /// Déclaration de la classe Led
    ligne 45 :
    il y a ’if (aMontageInverse == true)’
    il faut ’if (montageInverse == true)’

    cela semble se confirmer dans le Code complet, ligne 49

    Répondre

    • Le monde des objets (2) 13 janvier 2018 10:34, par Thierry

      C’est vrai, c’est corrigé. Merci de votre attention !

      Répondre

      • Le monde des objets (2) 13 janvier 2018 13:37, par trimarco232

        l’erreur fut sans conséquence
        l’intérêt est des plus mérités : je recommande vivement la lecture de tes articles !

        Répondre

  • Le monde des objets (2) 20 avril 2018 00:00, par BoBillier Eric

    Bonjour merci pour cet article. Juste une petite coquille. Dans le bout de code après le texte :
    Plutôt que d’avoir un constructeur qui va tout mettre à 0, puis un Setup qui va vraiment faire l’initialisation, répartissons mieux les rôles. On peut ajouter à la classe un constructeur avec des arguments :
    Vous avez oublié "Led: :" devant le constructeur.
    Cette erreur est par contre corrigée dans le code complet.
    Cordialement
    Eric

    Répondre

  • Le monde des objets (2) 20 avril 2018 09:24, par Bobillier Eric

    Bonjour. Je m’interroge aussi sur un point de votre exposé.Vous indiquez que le constructeur n’accepte pas de valeur de retour, et vous déclarer Led::Led() sans même de type void devant.
    Cependant dans le chapitre suivant (Le monde des objets(3)) , vous déclarez dans le bout de code après le texte " Cette led particulière ayant deux pins différentes à piloter, il faut en ajouter une à celle déjà présente dans la classe de base, c’est pin2, et l’initialiser dans le constructeur :",le constructeur de la classe ledBicouleur avec un type void devant. D’où ma question : est-ce que les 2 écritures sont valides (avec ou sans type void devant), ou s’agit-il d’une petite coquille ?
    Cordialement
    Eric

    Répondre

  • Le monde des objets (2) 20 avril 2018 14:10, par Thierry

    Non, c’est bien une coquille qui vient d’être corrigée... A ma connaissance, il ne faut jamais spécifier de valeur de retour sur un constructeur, même void !

    Répondre

  • Le monde des objets (2) 17 avril 2020 12:05, par Nans

    Bonjour,

    Tout d’abord merci pour ses explications.
    J’ai bien, il me semble fait tout ce qu’y est expliqué dans cet article cependant quand je veux l’instancier mon objet, j’ai un message du compilateur qui me dit " error :’NameObjet’ does not name a type."

    La déclaration de la class doit-elle ce faire avant l’instanciation dans les sketch ?

    Je précise que ce n’est pas votre exemple mais mon propre objet.

    Cordialement.
    Nans

    Répondre

    • Le monde des objets (2) 17 avril 2020 13:12, par Thierry

      Oui je confirme, le C++ ne peut utiliser que ce qu’il connait déjà, c’est à dire que tout ce qui est utilisé doit être déclaré avant. Par contre il est possible de mettre une ligne ’class toto’ qui indique au compilateur que ça va venir plus tard, mais ça ne fonctionne que pour des déclarations de donnée de ce type là, comme un ’toto MaVariable ;’ ou un argument de fonction de ce type.

      Répondre

  • Le monde des objets (2) 1er avril 2023 18:15, par Benoit92

    Bonjour,
    Je ne suis pas informaticien et je me pose une question (certainement bête !) :
    Pourquoi y a t-il :
    byte pin ;
    bool montageInverse ;
    au début du programme
    et aPin et aMontageInverse dans la suite du programme
    Par exemple : Led(byte aPin, bool aMontageInverse = false) ; // constructeur complet
    Merci.
    Cordialement

    Répondre

    • Le monde des objets (2) 1er avril 2023 18:32

      Bonjour

      pin et aPin sont deux variables différentes. pin est une donnée membre de la classe Led. C’est à dire qu’une Led est définie par sa broche ’pin’ et son état ’etat’ . aPin est un argument (d’où le ’a’) d’une fonction.
      Dans le constructeur, qui est aussi une fonction comme une autre, aPin est passée en argument par la déclaration Led maLed(10) . aPin reçoit alors 10, et le construteur peut en faire ce qu’il veut. Par exemple fixer la broche de la led à ’aPin’, soit 10, comme c’est le cas dans le constructeur décrit dans l’article avec la ligne ’pin = aPin’, mais il aurait pu faire le choix de fixer la broche de la led à 20, avec un ’pin = aPin + 10’ . Pourquoi ? Ben pourquoi pas ! En fait bien souvent, les arguments du constructeur ne consituent pas les données membres du nouvel objet, mais permettent de le construire d’une manière particuliére.
      On aurait pu imaginer avoir une liste de pin broches[5], et donner au constructeur l’indice de la broche à utiliser, et pas la broche elle même :

      int broches[5] = { 10,11,12,13,14 };
      
      Led::Led(int aIndex)
      {
      	pin = broches[aIndex];
      }

      Les arguments d’un constructeur ne présagent forcément pas du contenu de l’instance de classe créée.

      Répondre

  • Le monde des objets (2) 1er avril 2023 19:24, par Benoit92

    Merci,
    Je comprends pas tout, mais je m’accroche !.
    Le format de aPin et aMontageInverse est implicitement défini par :
    pin = aPin ;
    montageInverse = aMontageInverse

    Donc, pin étant déclaré en format byte (8 bits),
    aPin est la variable qui peut potentiellement varier entre 0 et 1024.
    C’est donc aPin qui est appelé dans certaines des fonctions.

    Répondre

Réagissez à « Le monde des objets (2) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Programmation »

Les derniers articles

Les articles les plus lus