Go Down

Topic: Meinen Arduino Code verstehen. Was ist was? (Read 2177 times) previous topic - next topic

Serenifly

Praktisch verwende ich struct nur für eine Ansammlung von Variablen und bei Bedarf einen Konstruktor

Natürlich kann man da in C++ auch Methoden reinschreiben und andere OOP Dinge tun, aber in dem Fall schreibe ich dann immer class

MicroBahner

Natürlich kann man da in C++ auch Methoden reinschreiben und andere OOP Dinge tun, aber in dem Fall schreibe ich dann immer class
C++ hat da ähnliche Defizite wie C: Man kann eine Menge Dinge tun, die man eigentlich besser bleiben lässt um guten und übersichtlichen Code zu schreiben.
Andere OOP Sprachen sind da wesentlich restriktiver.
Gruß, Franz-Peter

combie

#77
Jun 21, 2018, 04:43 pm Last Edit: Jun 21, 2018, 07:19 pm by combie
Natürlich sollte man nur, was unbedingt für die Schnittstelle zum Objekt sichtbar sein muss auf public setzen.

Eigenschaften/Attribute, oder im Volksmund, die Variablen einer Klasse, sollten IMMER private sein.
Es mag einige wenige Ausnahmen geben, wo protected sinnvoll ist.

Bei den Methoden/Member (die Funktionen einer Klasse) ist dann protected schon häufiger nötig/sinnvoll

Mein default Sichtbarkeitsbegrenzer ist "private".


Für diesen Schnellschuss schien es mir ausreichend struct zu verwenden.
Kleine Änderung, und sofortiges testen möglich.


Ich schätze mal, dass man über das Sichtbarkeits-Thema auch ein paar Seiten quatschen kann.... bis da Konsens herrscht.
Global, Lokal, Anweisungsblock
Private, Protected, Public
Namespaces



Quote
Ganz unausweichlich ist es aber nicht, sonst hätten Schulen und Kursanbieter ein großes Problem entsprechende Lehrkräfte zu finden
In meinem Bekanntenkreis, gibts einen guten Lehrer, welcher im Bereich Schreinern und Bootsbau unterrichtet.
Dieser steht eindeutig auf Kompetenzlevel 3. Er weiß genau wie es geht, und kann es sehr gut vermitteln, hat aber 10 Daumen, an 2 linken Händen. Keine funktionierenden handwerklichen Automatismen.
Kompetenzlevel 3 scheint für Lehrer optimal zu sein.
Wer es kann, der tut es.
Wer es nicht kann, unterrichtet es.
Und, wer gar keine Ahnung hat, bestimmt wo es lang geht.

Whandall

#78
Jun 21, 2018, 05:33 pm Last Edit: Jun 21, 2018, 05:35 pm by Whandall
Es mag einige wenige Ausnahmen geben, wo protected sinnvoll ist.
Protected ist fast immer sinnvoll, wenn man eine Klassenhierarchie hat,
aber sonst ist private ja auch gleichbedeutend mit protected.
Kinder haben halt mehr Zugriff als Fremde und das ist ja auch sinnvoll.
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

combie

#79
Jun 21, 2018, 07:01 pm Last Edit: Jun 21, 2018, 07:07 pm by combie
Quote
Protected ist fast immer sinnvoll, wenn man eine Klassenhierarchie hat,
Meine Intuition ist da recht klar!
Eigenschaften privat, Methoden protected.

Mein Mantra ist:
Die Sichtbarkeit, soweit wie möglich einschränken.

Und jetzt denke ich darüber nach, warum meine Intuition so zielt.....

// ----- denk ------



Das Problem ist fix erklärt, glaube ich....

AutorA erstellt eine BasisKlasse.
AutorB erstellt eine AbgeleiteteKlasse

Wenn  AutorB eine protected Eigenschaft von BasisKlasse verändern darf, dann muss er wissen, was die BasisKlasse mit der Eigenschaft anstellt, und welche Werte die Eigenschaft annehmen darf.

Das kann AutorB feststellen, wenn er den Quellcode von AutorA liest.
Aber niemand kann ihn daran hindern, in eine Integer Eigenschaft 4711 rein zu schreiben, obwohl da nur Winkel von 0 bis 360° Grad erlaubt sein sollten.

AutorB muss bei jeder Änderung/Update, von dem AutorA seiner BasisKlasse, prüfen, ob sich nichts von dem inneren Verhalten geändert hat, obwohl die Vererbungsschnittstelle doch gleich geblieben ist.


Wenn jetzt eine Eigenschaft zu der Vererbungs Schnittstelle gehört(als protected deklariert ist), dann ist es kaum noch möglich die Innereien der BasisKlasse zu ändern, ohne die Schnittstelle zu gefährden.

Aus diesem Grund scheint es mir sehr wichtig, Eigenschaften möglichst tief zu vergraben.
Den Zugriff bevorzugt nur über Methoden zu erlauben.


2 Jahre, und 4 Versionsänderungen, weiter und vieles ist anders!
Das einzige was gleich geblieben sein sollte sind die Schnittstellen.

Schränkt man die Sichtbarkeit auf private ein, dann hat auch die BasisKlasse die 100%ige  Gewähr, dass nur gültige Werte in den Eigenschaften stehen können.

Ändert sich die Schnittstelle, dann schreit der Kompiler.
Ändert sich die Implementierung, und man ist davon abhängig, sucht man sich doof und dusselig.


Für AutorA gilt das Mantra:
Die Sichtbarkeit, soweit wie möglich einschränken.

Für AutorB gilt das Mantra:
Programmiere immer nur gegen die Schnittstelle, und nie gegen die Implementierung.

(eigentlich stimmt das nicht, denn beide Mantras gelten für beide)

Wer es kann, der tut es.
Wer es nicht kann, unterrichtet es.
Und, wer gar keine Ahnung hat, bestimmt wo es lang geht.

Tommy56

Programmiere immer nur gegen die Schnittstelle, und nie gegen die Implementierung.
Das kann ich voll unterschreiben, auch wenn es manches Mal verlockend ist.

Gruß Tommy
"Wer den schnellen Erfolg sucht, sollte nicht programmieren, sondern Holz hacken." (Quelle unbekannt)

agmue

#81
Jun 21, 2018, 07:35 pm Last Edit: Jun 21, 2018, 07:37 pm by agmue
Wie in #68 angekündigt, nun ein Programm mit drei mit verschiedener Frequenz blinkenden LEDs, zunächst mit C&P (gemeint ist, was für eine LED funktioniert, dreimal kopiert):

Code: [Select]
const byte led1 = 10;
const byte led2 = 11;
const byte led3 = 12;
const uint32_t blinkIntervall1 = 200;
const uint32_t blinkIntervall2 = 111;
const uint32_t blinkIntervall3 = 500;
uint32_t aktMillis, blinkMillis1, blinkMillis2, blinkMillis3;

void setup()
{
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);
}

void loop()
{
  aktMillis = millis();
  if (aktMillis - blinkMillis1 >= blinkIntervall1)
  {
    blinkMillis1 = aktMillis;
    digitalWrite(led1, !digitalRead(led1));
  }
  if (aktMillis - blinkMillis2 >= blinkIntervall2)
  {
    blinkMillis2 = aktMillis;
    digitalWrite(led2, !digitalRead(led2));
  }
  if (aktMillis - blinkMillis3 >= blinkIntervall3)
  {
    blinkMillis3 = aktMillis;
    digitalWrite(led3, !digitalRead(led3));
  }
}

Nun - Achtung combie - bringe ich zusammen, was zusammen gehört, nämlich Variablen gleicher Bedeutung in Feldern:

Code: [Select]
const byte leds[] = {10, 11, 12};
const uint32_t blinkIntervall[] = {200, 111, 500};
const byte anzahl = sizeof(leds);
uint32_t aktMillis, blinkMillis[anzahl];

void setup()
{
  for (byte led = 0; led < anzahl; led++)
  {
    pinMode(leds[led], OUTPUT);
  }
}

void loop()
{
  aktMillis = millis();
  for (byte led = 0; led < anzahl; led++)
  {
    if (aktMillis - blinkMillis[led] >= blinkIntervall[led])
    {
      blinkMillis[led] = aktMillis;
      digitalWrite(leds[led], !digitalRead(leds[led]));
    }
  }
}

Zum Schluß dann noch die OOP-Variante1), wo die Werte einer LED zusammenstehen:

Code: [Select]
uint32_t aktMillis;

struct Blink
{
  public:
    Blink(const byte led, const uint32_t blinkIntervall): led(led), blinkIntervall(blinkIntervall), blinkMillis(0) {}
    void init()
    {
      pinMode(led, OUTPUT);
    }
    void run()
    {
      if (aktMillis - blinkMillis >= blinkIntervall)
      {
        blinkMillis = aktMillis;
        digitalWrite(led, !digitalRead(led));
      }
    }
  private:
    const byte led;
    const uint32_t blinkIntervall;
    uint32_t blinkMillis;
};

Blink kanal[]
{ // {led, blinkIntervall}
  {10, 200},
  {11, 111},
  {12, 500},
};

void setup()
{
  for (Blink &k : kanal) k.init();
}

void loop()
{
  aktMillis = millis();
  for (Blink &k : kanal) k.run();
}

Ich bin gespannt, was nun daraus werden soll :)

---
Anm.: 1) Mein erstes Programm bewußt mit OOP geschrieben.

Serenifly

#82
Jun 21, 2018, 07:53 pm Last Edit: Jun 21, 2018, 08:04 pm by Serenifly
:)

Man kann 2. und 3. halt auch kombinieren. Indem man die Variablen in einem struct zusammenfasst und dann ein Array daraus anlegt. Aber die Funktionen noch außerhalb hat. Für mich ist das völlig selbstverständlich.  structs gab es schließlich auch schon in C (und anderen Sprachen aus dieser Zeit), lange bevor sich OOP durchgesetzt hat. Allerdings da ohne Konstruktor.
Der Umweg über eine Reihe getrennter Arrays sieht für einen erfahrenen Programmierer einfach unstrukturiert aus. Es gibt natürlich viele Fälle wo man statt durchnummerierter Variablen ein Array wählen sollte, aber was hier als erstes zusammengehört sind alle Variablen die zusammen ein Objekt beschreiben/darstellen. Und danach erst fasst man die Objekte zusammen

Tommy56

Dem kann ich beipflichten. Wir haben über viele Jahre in unserer Applikation (ANSI-C) alle Daten eines Datensatzes als struct benutzt und nur Pointer darauf an die (Select-/Update-/Insert-)Funktionen übergeben. Structs sind nichts Neues, nur die Methoden in Structs waren mir neu. Das kam erst mit C++.

Gruß Tommy
"Wer den schnellen Erfolg sucht, sollte nicht programmieren, sondern Holz hacken." (Quelle unbekannt)

combie

Quote
Zum Schluß dann noch die OOP-Variante, wo die Werte einer LED zusammenstehen:
....
Ich bin gespannt, was nun daraus werden soll :)
Ob das jetzt ein riesen Zugewinn wird, ka...

Aber hier mal mit Zeitabhandlung, aus Blink herausdestiliert.



Timer!
Die Mutter, das Arbeitstier, aller weiteren Zeitabhandlungen.

Eine schlichte Schnittstelle:
  • Methode abgelaufen(interval), sie liefert ein bool Flag, ob der Timer seine Ablaufzeit erreicht hat.
  • start() Setzt den Timer von abgelaufen auf den Zustand running.


Obwohl ein Automat mit 2 Zuständen, tut es keine Not den Zustand per enum auszuformulieren.
Ein bool reicht als Zustand.

Der Timer wird immer als abgelaufen initialisiert.
Viele Experimente haben gezeigt, dass es so am besten ist.
Zumindest fühlt es sich für mich am Besten an.

Die Intervallzeit mit in den Timer aufzunehmen ist eher nicht nötig.
Das kann man den Klassen überlassen, welche den Timer nutzen.


Code: [Select]
   class Timer
    {
      private:
      uint32_t timeStamp;      // Zeitmerker
      bool reached;            // default Status: timer abgelaufen
    
      public:
      Timer():timeStamp(0),reached(true){}
      
      void start()
      {
        timeStamp   = millis();
        reached     = false;
      }
    
      bool abgelaufen(const uint32_t interval)
      {
        if(!reached) reached = millis() - timeStamp >= interval;
        return reached;
      }
    };


struct Blink
{
  public:
    Blink(const byte led, const uint32_t blinkIntervall): led(led), blinkIntervall(blinkIntervall), blinkMillis(0) {}
    void init()
    {
      pinMode(led, OUTPUT);
      //timer.start();
    }
    
    void run()
    {
      if (timer.abgelaufen(blinkIntervall))
      {
        timer.start();
        digitalWrite(led, !digitalRead(led));
      }
    }
  private:
    Timer timer;
    const byte led;
    const uint32_t blinkIntervall;
    uint32_t blinkMillis;
};

Blink kanal[]
{ // {led, blinkIntervall}
  {10, 200},
  {11, 111},
  {12, 500},
};

void setup()
{
  for (Blink &k : kanal) k.init();
}

void loop()
{
  for (Blink &k : kanal) k.run();
}
Wer es kann, der tut es.
Wer es nicht kann, unterrichtet es.
Und, wer gar keine Ahnung hat, bestimmt wo es lang geht.

combie

#85
Jun 21, 2018, 08:25 pm Last Edit: Jun 21, 2018, 09:17 pm by combie
Quote
Der Umweg über eine Reihe getrennter Arrays sieht für einen erfahrenen Programmierer einfach unstrukturiert aus. Es gibt natürlich viele Fälle wo man statt durchnummerierter Variablen ein Array wählen sollte, aber was hier als erstes zusammengehört sind alle Variablen die zusammen ein Objekt beschreiben/darstellen. Und danach erst fasst man die Objekte zusammen
Ach, irgendwie auch ein bisschen gut...
Schön, dass hier die Varianten aufgeführt sind.

So kann dann jeder entscheiden, was ihm lieber ist
Und außerdem übt das!

Übung!
Das ist das Zauberwort.

Auch das denken in anderen Bahnen will geübt werden.

Wer es kann, der tut es.
Wer es nicht kann, unterrichtet es.
Und, wer gar keine Ahnung hat, bestimmt wo es lang geht.

volvodani

@agmue
Quote
Code: [Select]
for (Blink &k : kanal) k.init();
Wo kommt der Zeiger auf k her das mit k.ini() in das struct kommt müsste nicht am Ende von dem strukt der Bezug auf k gemacht werden?
Ich habe da echt keine Ahnung und würde das gerne Verstehen.
Gruß
DerDani
(Der nur SPS mit SCL richtig kann)
0x2B | ~ 0x2B = 0xFF  
(Shakespeare)

Wie stelle ich mich hier richtig an

combie

#87
Jul 05, 2018, 02:00 pm Last Edit: Jul 05, 2018, 02:14 pm by combie
Kein Zeiger!
Eine Referenz.

Für alle Elemente von kanal, bilde eine Referenz k vom Type Blink.
Range based for C++

Quote
Wo kommt der Zeiger auf k her das mit k.ini()
k wird im Statement definiert...

Vergleichbar mit dem Ausdruck:

Code: [Select]
for(int i=0; i<anzahl;i++)
{
  Blink & k = kanal[i];
  k.init(); 
}


Ungetestet, aber du solltest erkennen können, was gemeint ist.
Wer es kann, der tut es.
Wer es nicht kann, unterrichtet es.
Und, wer gar keine Ahnung hat, bestimmt wo es lang geht.

Go Up