[résolu] Classes et variables de classe

Explications dans le dernier message
voir : [résolu] Classes et variables de classe - #8 by 68tjs - Français - Arduino Forum


Bonjour à toutes et à tous,

J'avais déjà joué avec des classes mais avec passages de paramètres dans les méthodes et ça fonctionne.
Là j'ai essayé avec des variables définies dans le fichier entête de la classe (qui est importé dans le fichier main.cpp) et j'ai une bordée d'injures.

Le décor pour commencer : comme j'ai identifié un problème de "syntaxe/comment_faire" j'ai abandonné la prog avec un avr pour régler le pb dans du classique : Code::blocks et sortie console.
Pourquoi Code::blocks : parce que c'est le seul IDE que j'ai trouvé qui me propose directement une sortie console sans me prendre la tête.

Donc voici les messages d'erreurs :

test.cpp |ligne 4| définitions multiples de « Minute »|
main.cpp |ligne10| défini pour la première fois ici|
test.cpp |ligne4|  définitions multiples de « Heure »|
main.cpp |ligne10| défini pour la première fois ici|
||=== Build finished: 4 errors, 0 warnings ===|

Le programme principal :

#include <iostream>
#include <stdio.h>
#include "test.h"

using namespace std;

Test machin;

int main()
{
  printf("\n \nSans passage de paramètre \n");
  machin.affiche_direct();
  printf("\n \nAvec passage de paramètre \n");
  machin.affiche(10,30,59);
  printf("\n \n\n");
  return 0;
}

Le fichier entête de la classe : test.h

 #ifndef TEST_H
#define TEST_H

#include <inttypes.h>

uint8_t Seconde ;
uint8_t Minute  ;
uint8_t Heure   ;

class Test
{
  public:
    Test();
    //~Test();
    void affiche_direct() ;
    void affiche(uint8_t _Heure, uint8_t _Minute, uint8_t _Seconde);
	
private:
    uint8_t _Heure  ;
    uint8_t _Minute ;
    uint8_t _Seconde;
};
#endif // TEST_H

Le fichier test.cpp

#include "test.h"
#include <stdio.h>

Test::Test()
{
  _Seconde  = Seconde ;
  _Minute   = Minute ;
  _Heure    = Heure ;
}

void Test::affiche_direct()
{
  _Heure   = 1 ;
  _Minute  = 1 ;
  _Seconde = 1 ;

	printf("il est %d h %d minutes %d secondes", _Heure, _Minute,_Seconde);
}

void Test::affiche(uint8_t h, uint8_t m, uint8_t s)
{
	printf("il est %d h %d minutes %d secondes ", h, m, s);
}

Ce que je ne comprend pas :

définitions multiples de « Minute »
définitions multiples de « Heure»

et pas d'indication de problème sur Seconde,
Dans les déclarations de variable j'ai inversé l'ordre en mettant Seconde en premier c'est pareil tjs Heure et Minute.
Craignant des mots clés j'ai essayé minuscule, majuscule, pluriel : que neni.

Cela fait 2 jours que je tourne en rond, google ne veux rien me dire et ma boule de cristal est en "mouvement revendicatif", je suis sec.

PS : vous noterez que les variables Heure, Minute, Seconde ne sont jamais initialisées, la raison est que je n'arrive pas à passer la phase de compilation, j'ai prévu de les utiliser une fois que le pb de compilation sera réglé.

Merci aux âmes charitable qui pourront m'indiquer ma(mes) conn**rie(s).

Tu n'as pas le droit d'allouer des variables globales dans un .h

Mais tu peux les déclarer en extern et les allouer une seule fois ailleurs

Dans le .h :

extern uint8_t Heure;

et ajouter dans le .cpp de la lib

uint8_t Heure;

Merci pour la réponse.

En fait je suis sur la comparaison Nano/WiringArduino et STM32/Mbed sur l'I2C.
L'objet I2C est un DS3231.

J'ai regardé ce qu'il existait comme bibliothèques.
Coté Arduino elles me semble monstrueuses et compliquées pour le plaisir.
J'ai écrit un programme arduino vite fait pour régler et obtenir l'heure et la date --> la bête est ok.

Avec la bibliothèque MBed DS3231 j'ai vu que dans le fichier h l'auteur déclarait une structure et faisait hériter sa classe de la classe I2C (équivalent MBed de Wire).
Cela m'a paru intéressant (jamais joué avec l'héritage) et j'ai pour but de créer deux bibliothèques "quasi" identiques pour arduino et pour MBed pour que la comparaison soit la plus valable possible.

Les liens :

  1. vers le programme exemple :
    DS3231 Real Time Clock RTC Integrated Crystal, 2 Alarms, Extremely Accurate High Precision | Mbed

  2. vers la bibliothèque DS3231:
    ds3231 - Library for DS3231 RTC | Mbed

Tu notera que la bibliothèque a été écrite par Maxim, preuve de l'implication de Maxim dans MBed.

Ce serait sympa si tu pouvais y jeter un œil, moi je n'ai rien vu mais en programation moi ce n'est que moi.

Edit 1 : il y a peut-être un pb de pointeur ?

Edit 2 : j'ai fais comme tu dis et cela fonctionne. Mais c'est incompatible avec le principe d'une bibliothèque où dans le programme principal on ne doit rien d'autre avoir à faire que d'importer des fichier.h.

Bonjour

Désolé je ne comprends pas très bien ce que tu cherches à faire, car je ne pratique pas STM32 ni mbed.

Mon rayon, c'est plutôt la programmation.

Pour le extern je suis sûr de mon coup.
La compilation des librairies et du programme principal se font séparément. Le tout est ensuite assemblé par l'édition de liens pour générer l'exécutable unique.
Dans ton premier exemple, ta variable Heure se retrouvait à la fois dans le compilé du programme principal et dans celui de la librairie.
Du coup, une erreur était générée à l'édition de liens, pour cause de doublon.

Pourquoi voudrais-tu avoir un programme principal qui n'importe que des fichiers .h ?

Cela ne me semble pas incompatible avec l'usage de extern sur une variable ou un objet, qui peut alors très bien être partagé par plusieurs librairies.

Pb de pointeur de l'Edit 1 : comprends pas à quoi ça fait référence.

J'ai regardé la lib ds3231 que tu indiques.
Ouais c'est sûr elle me semble un peu lourdingue.
Mais elle adresse toutes les fonctionnalités offertes par un DS3231, incluant la gestion d'alarmes et l'accès à sa sonde de température interne.

Sa principale spécificité de codage par rapport à une classe faite pour Arduino, c'est qu'elle s'appuie sur une classe I2C au lieu de s'appuyer sur un objet Wire (classe twoWire).

Si tu veux seulement lire et écrire la date / heure dans le DS3231, tu n'as besoin que de deux fonctions.

Côté Arduino, tu peux t'appuyer sur ce bout de code qui fonctionne aussi bien avec un DS3231 qu'un DS1307.

Sa "traduction" mbed n'est pas très compliquée. Il suffit de remplacer les Wire.* par leur équivalent mbed.
En regardant le .cpp on voit que ça a l'air très simple : la classe I2C dispose des méthodes write et read.

Après tu peux toujours fusionner les deux en une seule librairie, avec des #ifdef Arduino_h, mais je ne vois pas trop l'objectif.

La comparaison Wiring/Mbed c'était pour fixer le décor. Ce que je voulais faire ne fonctionne pas avec avr-gcc, je suis passé sur GCC et cela ne fonctionne pas mieux. Il me faut éclaircir ce schmilblic avant de reprendre ma comparaison.

La programmation à la mode arduino d'un DS3231 ne me pose pas de problème.
Comme je l'ai déjà écrit j'ai pondu un petit programme qui gére la date et l'heure (réglages et lectures).
L'utilisation d'export j'avais déjà vu (même si sur le coup je n'avais pas pensé à l'essayer :smiling_imp: ).

Je furète un peu partout et je suis tombé sur cette bibliothèque écrite par Maxim sur Mbed qui défini les variables dans son fichier h et qui utilise l'héritage.

L'un comme l'autre m'intéresse : non pas que ce soit indispensable mais je n'ai jamais pratiqué donc je cherche à comprendre.

J'avais compris que l'avantage d'une bibliothèque c'est que l'on n'a à placer dans le fichier principal que des includes vers des fichiers h.
Si en plus, dans le fichier principal, il faut définir manuellement les variables utilisées par la classe je ne comprend plus l'intérêt d'une bibliothèque.
Je suis peut-être inconscient et naïf mais je n'imagine pas que ce soit le fonctionnement normal avec une bibliothèque.
Parce que si c'était cela il devrait y avoir systématiquement une documentation de classe qui précise quelles variables il faut déclarer.

Les variables j'avais aussi tenté de les déclarer en public à l'intérieur de la classe. Cela me parraissait devoir fonctionner : toujours ko mais je ne garantirai jamais mon interface chaise/clavier.

Donc s'il y a une manière de faire je suis preneur.

Autre point :
J'ai fait une bidouille :
Dans test.h les variables sont toujours déclarées ""extern""
J'ai créé un autre fichier test_main.h dans lequel je déclare classiquement les variables :
uint8_t Heure;
uint8_t Minute ;
etc

Ce qui fait que dans le programme princ j'ai successivement :
#include "test_main.h"
#include "test.h"

Donc le compilateur voit uint8_t Heure puis extern uint8_t Heure .
Cela fonctionne mais est-ce un fonctionnement prévu ou est-ce tombé en marche ?

Sauf erreur de ma part :

Si ton test_main.h n'est utilisé que par le programme principal, et que tu n'as pas déclaré uint8_t Heure ailleurs (par exemple dans test.cpp) c'est normal.

Le test.h ne fait qu'ajouter, dans toutes les unités qui l'utilisent, la ligne :
extern uint8_t Heure;

Par unités, j'entends le programme principal mais aussi les librairies (y compris test.cpp), puisque compilées séparément.

Donc toutes ces unités ont une variable Heure dont l'adresse réelle en RAM ne sera fournie qu'au moment de l'édition de lien.

Derrière, il faut que le uint8_t Heure; soit défini une seule fois quelque part.
D'habitude, et c'est le plus logique, cette définition intervient dans le .cpp associé au .h où se trouve le extern.
Mais au vu de ton résultat je conjecture que cela peut être ailleurs.

De manière plus générale, vouloir mélanger des variables globales et des instances de classe me paraît assez douteux.

Le but de la programmation objet est justement d'encapsuler un certain nombre de méthodes et propriétés, en masquant leur complexité sous-jacente. C'est pas pour laisser un jeu de variables globales à disposition du programme principal, dont la modification aura un effet sur le fonctionnement de l'objet.

Chaque instance de classe (= objet -> tu fais bien la différence entre classe et objet?) doit porter ses propres données, utilisées par ses méthodes.

Typiquement, pour une librairie dédiée au DS3231, et sachant que celui-ci est forcément unique dans le circuit, je ferais :

dans le .h

//Définition de la classe
class DS3231_c
{
  public :
...
  private :
...
};

//Cette classe ne peut avoir qu'une seule instance
//Donc autant la créer ici
extern DS3231_c DS3231;

Et dans le .cpp

...
   les méthodes de la classe DS3231_c
...
  DS3231_c DS3231;

Jy vois plus clair :
La solution c'est bien les variables publiques de classe et Il y avait un bug dans mon interface chaise clavier pour l'accès aux variables publiques de classe.

Pour ceux que ça intéresse :

  1. Cas où le programme est simplement découpé en plusieurs fichiers, sans avoir en tête la notion de bibliothèque.
    Si on veut utiliser des variables globales, le plus simple est de déclarer les variables dans le programme principal et de les rappeller dans chaque fichier en les faisant précéder du mot clé "extern".

  2. Cas on le but est de créer une bibliothèque.
    Toujours si on veut pouvoir accéder de n'importe où aux variables la méthode précédente fonctionne mais n'est pas optimale. La classe doit apporter la déclaration de ses variables propres .
    La solution est de déclarer les variables de classes dans la zone "public".
    Pour être plus complet imaginons qu'on ait préalablement défini une structure :

typedef struc
{
int membre_A ;
float membre_B ;
} structure_exemple;

La déclaration des variables publiques se fait ici :

class classe_de_test
{
public
int bidule ;
structure_exemple truc ;
void affiche();

Dans le programme on instancie un objet "machin" sur "classe_de_test"

Pour accéder aux méthodes il faut écrire :
machin.affiche() ;
Pour accéder aux variables :
machin.bidule = 10 :
Pour accéder aux membres d'une variable structure :
machin.truc.membre_B = 3.0125 ;

Cela parait évident, mais comme souvent plus c'est gros moins cela se voit.

Désolé de n'avoir pu t'aider mieux, pas sûr d'avoir compris où ça coinçait :stuck_out_tongue:

Juste un point complémentaire :

Dans l'exemple ci-dessus, la donnée "int bidule;" est une propriété de l'objet.
Si le programme comporte plusieurs objets, chacun aura sa donnée bidule à des adresses RAM différentes.

classe_de_test toto;
classe_de_test titi;

toto.bidule n'est pas la même variable que titi.bidule

Dans les cas où on souhaite avoir l'équivalent d'une variable globale unique, il faut la déclarer en "static int bidule;".
Là, bidule est alors une donnée de la classe, partagée entre tous les objets.

Du coup, pour l'initialiser, on ne peut pas lui affecter une valeur dans le constructeur d'objet.
Le plus simple est de l'initialiser de manière statique dans le .cpp associé à la classe.
int classe_de_test::bidule = 1;

Ok
Merci