Benutzer-Werkzeuge

Webseiten-Werkzeuge


skript:klassen-arduino

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen gezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
skript:klassen-arduino [2018/10/07 19:46]
d.golovko
skript:klassen-arduino [2021/06/11 11:28] (aktuell)
d.golovko
Zeile 7: Zeile 7:
 ====1. Deklaration==== ====1. Deklaration====
  
-Jeder Song hat zwei Eigenschaften:​ die Liste von abzuspielenden Noten in Form eines ''​int''​-Arrays und der Pin, welcher mit dem Piezo verbunden ist. +Jeder Song soll zwei Eigenschaften ​haben: die Liste von abzuspielenden Noten in Form eines ''​int''​-Arrays und der Pin, welcher mit dem Piezo verbunden ist. 
  
 Eine Klasse in C++ besteht in der Regel aus zwei Dateien: einer Header-Datei ''​.h'',​ die alle Variablen, Konstruktoren und Methoden benannt werden, und einer ''​.cpp''​-Datei,​ die die eigentliche Impementieriung der Methoden beihnaltet. In unserem Beispiel kann die Klasse so aussehen: Eine Klasse in C++ besteht in der Regel aus zwei Dateien: einer Header-Datei ''​.h'',​ die alle Variablen, Konstruktoren und Methoden benannt werden, und einer ''​.cpp''​-Datei,​ die die eigentliche Impementieriung der Methoden beihnaltet. In unserem Beispiel kann die Klasse so aussehen:
Zeile 14: Zeile 14:
 class Song { class Song {
   public:   public:
-    Song(); // Konstruktor, muss noch mit Parametern versehen werden+    Song(int noteList[], int arraySize, int pinNumber); // Konstruktor
     void play(); // Methode zum Abspielen der Melodie     void play(); // Methode zum Abspielen der Melodie
   private:   private:
-    int notes[]; // Noten-Frequenzen in Hz+    int *notes; // Noten-Frequenzen in Hz; ist ein Zeiger, da Array-Laenge noch nicht bekannt 
 +    int arraySize; // Laenge des Noten-Arrays
     int pin; // Piezo-Pin     int pin; // Piezo-Pin
 }; };
Zeile 24: Zeile 25:
 <file cpp Song.cpp>​ <file cpp Song.cpp>​
 #include "​Song.h"​ #include "​Song.h"​
-Song::​Song() {+Song::Song(int noteList[], int arraySize, int pinNumber) {
   // TODO: Implementierung des Konstruktors fehlt   // TODO: Implementierung des Konstruktors fehlt
 } }
Zeile 36: Zeile 37:
  
 Ihr sieht oben, dass die Header-Datei zwei Blöcke hat, die mit ''​public''​ und ''​private''​ anfangen. Diese Schlüsselworte weisen auf die [[https://​de.wikipedia.org/​wiki/​Sichtbarkeit_(Programmierung)|Sichtbarkeit]] der Variablen und Funktionen hin. ''​Public''​ bedeutet, dass die Variable/​Funktion nur innerhalb der Klasse sichtbar ist, ''​protected''​ innerhalb der Klasse und ihrer Unterklassen,​ ''​public''​ überall. (Ähnliche Ebenen gibt es übrigens auch in Java.) Der Faustregel ist, die Sichtbarkeit möglichst begrenzt zu wählen. Die Begründung ist die gleiche wie bei globalen und lokalen Variablen: Je mehr Stellen haben Zugriff auf die Variable, desto mehr fehleranfällig ist der Code. Wir machen den Konstruktor und die Funktion ''​play()''​ ''​public'',​ damit wir darauf von unserem Haupt-Tab zugreifen können. Die beiden Variablen sind ''​private'',​ sie sollen nur innerhalb der Klasse verfügbar sein. Es ist üblich, in der Header-Datei den ''​public''​-Block ganz oben zu platzieren. ​ Ihr sieht oben, dass die Header-Datei zwei Blöcke hat, die mit ''​public''​ und ''​private''​ anfangen. Diese Schlüsselworte weisen auf die [[https://​de.wikipedia.org/​wiki/​Sichtbarkeit_(Programmierung)|Sichtbarkeit]] der Variablen und Funktionen hin. ''​Public''​ bedeutet, dass die Variable/​Funktion nur innerhalb der Klasse sichtbar ist, ''​protected''​ innerhalb der Klasse und ihrer Unterklassen,​ ''​public''​ überall. (Ähnliche Ebenen gibt es übrigens auch in Java.) Der Faustregel ist, die Sichtbarkeit möglichst begrenzt zu wählen. Die Begründung ist die gleiche wie bei globalen und lokalen Variablen: Je mehr Stellen haben Zugriff auf die Variable, desto mehr fehleranfällig ist der Code. Wir machen den Konstruktor und die Funktion ''​play()''​ ''​public'',​ damit wir darauf von unserem Haupt-Tab zugreifen können. Die beiden Variablen sind ''​private'',​ sie sollen nur innerhalb der Klasse verfügbar sein. Es ist üblich, in der Header-Datei den ''​public''​-Block ganz oben zu platzieren. ​
 +
 +Wenn in C++ Arrays als Parameter an eine Funktion übergeben werden, wird nur der Zeiger auf das erste Array-Element übergeben. D.h. die Funktion bekommt keine Kopie des ganzen Arrays, sondern nur die Adresse des ersten Elementes. Eine Lösung ist, die Array-Länge als einen weiteren Parameter (''​arraySize''​ in unserem Beispiel) zu übergeben. In der Liste der Membervariablen wird ebenso nur ein Zeiger ''​int *notes''​ verwendet, da die Deklaration eines Arrays ohne seiner Länge nicht möglich ist. 
  
 ====2. Implementierung==== ====2. Implementierung====
Zeile 46: Zeile 49:
 // Konstruktor;​ noteList: Notenabfolge,​ arraySize: Laenge des Noten-Arrays,​ pin: Piezo-Pin // Konstruktor;​ noteList: Notenabfolge,​ arraySize: Laenge des Noten-Arrays,​ pin: Piezo-Pin
 Song::​Song(int noteList[], int arraySize, int pinNumber) { Song::​Song(int noteList[], int arraySize, int pinNumber) {
-  ​for (int i 0; i < arraySize; ​i++) { +  ​this->​arraySize ​= arraySize;​ 
-    this->​notes[i] = noteList[i]; +  this->​notes = noteList;
-  }+
   this->​pin = pinNumber;   this->​pin = pinNumber;
 } }
 +
 //[...] //[...]
 </​file>​ </​file>​
- 
-Dementsprechend müssen wir die Konstruktordeklaration auch in Zeile 3 der Header-Datei ändern: \\ 
-''​Song(int noteList[], int arraySize, int pinNumber);''​ 
  
 Genauso wie in Java wird ''​this''​ benutzt, um auf die Instanz der Klasse (d.h. auf die gerade erzeugte Objekt vom Typ ''​Song''​) zu zeigen. Achtung: in C++ wird statt Punkt ein Pfeil verwendet.  ​ Genauso wie in Java wird ''​this''​ benutzt, um auf die Instanz der Klasse (d.h. auf die gerade erzeugte Objekt vom Typ ''​Song''​) zu zeigen. Achtung: in C++ wird statt Punkt ein Pfeil verwendet.  ​
Zeile 63: Zeile 63:
 #include "​Arduino.h"​ #include "​Arduino.h"​
 //[...] //[...]
 +// Spielt die Melodie ab
 void Song::​play() { void Song::​play() {
-  for (int i = 0; i < sizeof(notes) / sizeof(notes[0]); i++) { +  for (int i = 0; i < arraySize ​; i++) { 
-    tone(pin, notes[i]500);+    tone(pin, notes[i]); 
 +    delay(500);
   }   }
 +  noTone(pin);​
 } }
 //[...] //[...]
 </​file>​ </​file>​
  
 +<hidden Komplette Datei Song.cpp>​
 +<file cpp Song.cpp>​
 +#include "​Song.h"​
 +#include "​Arduino.h"​
 +
 +// Konstruktor;​ noteList: Notenabfolge,​ arraySize: Laenge des Noten-Arrays,​ pin: Piezo-Pin
 +Song::​Song(int noteList[], int arraySize, int pinNumber) {
 +  this->​arraySize = arraySize;
 +  this->​notes = noteList;
 +  this->​pin = pinNumber;
 +}
 +
 +// Spielt die Melodie ab
 +void Song::​play() {
 +  for (int i = 0; i < arraySize ; i++) {
 +    tone(pin, notes[i]);
 +    delay(500);
 +  }
 +  noTone(pin);​
 +}
 +</​file>​
 +</​hidden>​
 +\\
  
 ====3. Aufruf==== ====3. Aufruf====
  
-In C++ gibt es die Unterscheidung zwischen Referenzen und Zeigern. ​Kurz gefasst sind Referenzen die Objekte ​an sich, und Zeiger ​sind ihre AdressenDie beiden sind oft +Nun werden wir einen Song im Haupt-Tab erstellen und aufrufen, z.B. so (Piezo auf Pin 2): 
 + 
 +<file cpp variante1>​ 
 +#include "​Song.h"​ 
 + 
 +void setup() { 
 +  int notes[] = {262, 294, 330, 349, 392}; // Notenfrequenzen 
 +  Song mySong (notes, sizeof(notes) / sizeof(notes[0]),​ 2); // oder: Song mySong(notes,​ 5, 2); 
 +  mySong.play();​ 
 +
 + 
 +void loop() {  
 +
 +</​file>​ 
 + 
 +Achtung: wir müssen dem Compiler mit ''#​include Song.h''​ Bescheid sagen, dass eine Klasse aus einer anderen Datei verwendet wird. Falls wir auf die Variable ''​mySong''​ sowohl von ''​setup()''​ als auch von ''​loop()''​ zugreifen wollen, können wir diese inkl. Konstruktoraufruf auch global haben: 
 +<file cpp variante2>​ 
 +#include "​Song.h"​ 
 +int notes[] = {262, 294, 330, 349, 392}; 
 +Song mySong (notes, sizeof(notes) / sizeof(notes[0]),​ 2); // oder: Song mySong(notes,​ 5, 2); 
 + 
 +void setup() { 
 +  mySong.play();​  
 +
 + 
 +void loop() { 
 +  mySong.play();​ 
 +
 +</​file>​ 
 + 
 +Der Konstruktor hier wird aufgerufen, bevor ''​setup()''​ anfängt. Das kann ungünstig sein im Fall, wenn der Konstruktor Parameter hat, deren Werte am Anfang des Programms noch nicht bekannt sind (z.B. wenn sie von Sensormessungen abhängen, die in ''​setup()''​ gemacht werden). Anders als in Java, können wir nicht einfach ''​Song mySong;''​ in Zeile 3 schreiben. Das liegt daran, dass ''​mySong''​ eine Referenz ist, und Referenzen dürfen nicht gleich NULL sein.  
 + 
 +<​note>​ 
 +Die Unterscheidung zwischen Referenzen und Zeigern ​(Englisch: pointers) in C++ ist ein separates großes Thema -- bei Interesse lesst [[https://​www.geeksforgeeks.org/​pointers-vs-references-cpp/​|diesen Link]] oder sucht Informationen dazu online. Wir werden dieses Thema hier nicht angehen.  
 +</​note>​ 
 + 
 +Wir werden stattdessen einen //Zeiger// nutzen -- man kann das so verstehen, dass, statt des Objektes ​an sich, haben wir nur einen Verweis darauf, wo dieses Objekt sich befindet. Ein Zeiger ​wird mit einem ''​*''​ markiert, und der Zugriff auf Funktionen und Variablen geht über ''​->''​. 
 + 
 +<file cpp variante3>​ 
 +#include "​Song.h"​ 
 +Song* mySong; // Zeiger 
 + 
 +void setup() { 
 +  int notes[] = {262, 294, 330, 349, 392}; 
 +  mySong = new Song(notes, sizeof(notes) / sizeof(notes[0]),​ 2); // oder: Song mySong(notes,​ 5, 2); 
 +  mySong->​play();​  
 +
 + 
 +void loop() { 
 +  mySong->​play();​ 
 +
 +</​file>​ 
skript/klassen-arduino.1538934361.txt.gz · Zuletzt geändert: 2018/10/07 19:46 von d.golovko