Variablenübergabe an Unterprogramme

Hi,

ich komm eigentlich aus der Steuerungstechnik, und da ich mit Arduino/C++ eher selten etwas programmiere, stehe ich immer wieder vor dem selben Problem:

Wie kann ich an ein Unterprogramm Variablen übergeben. - IN - OUT - IN/OUT

Ich kämpf da jedesmal. Einmal muss ein '*' davor, einmal ein '&'. Dann ist es mal ne Referenz, einmal ein Pointer. Meistens bin ich dann zu faul, und programmier alles weils schnell gehen soll mit globalen Variablen. Aber das finde ich alles andere als toll. Meistens hauen mich dann Serenifly & Co. wieder mit geduldiger Hilfe raus, aber ich will irgendwann dazu mal auf eigenen Beinen stehen.

Gibt es da irgendwo eine gute Seite/Tutorial, wo es für Dumies erklärt ist. Dann zieh ich mir da mal ein Bookmark und gut ist (hoffentlich).

Referenzen und Zeiger sind fast das gleiche. Im Hintergrund sind sie identisch. Beides mal wird eine Adresse übergeben. Der Unterschied ist wie folgt: 1.) Referenzen können nicht NULL sein 2.) Referenzen kann man nur einmal eine Adresse zuweisen. Nach der Initialisierung sind die fest 3.) Du kannst nicht die Adresse einer Referenz bekommen wie bei Zeigern (Zeiger auf Zeiger), da sie selbst keinen Speicher belegen 4.) Mit Referenzen ist keine Zeiger-Arithmetik möglich 5.) const Referenzen können auf temporäre Variablen zeigen. Zeiger nicht

Mit Referenzen kann man also bis auf einen Fall weniger machen. Das macht sie aber auch sicherer! Mit Zeigern kann man auch viel Unfug machen und schnell reinfallen

Was hier eher wichtig ist ist ob du überhaupt call-by-reference machst. Wenn man einfache Variablen übergibt reicht call-by-value völlig aus. Referenzen nimmt man in zwei Fällen: 1.) Wenn der übergebene Wert ein komplexer Datentyp ist. Also z.B. structs oder ein Arduino String Objekt. Damit verhindert man dass eine Kopie erstellt wird. 2.) Wenn man den Parameter als Rückgabe-Wert verwendet. Also wenn eine Zuweisung in der Funktion nach außen hin sichtbar sein muss.

Am besten du gewöhnst dir vielleicht Referenzen an. Die sind einfacher zu handhaben und man muss nicht groß anders schreiben (also keine Dereferenzierung mit * oder ->). Auf die Fälle wo es ohne Zeiger nicht geht wirst du dann noch stoßen.

Und ansonsten eben call-by-value verwenden wenn es sich um einfache Datentypen wie Integer handelt

Jedes C-Buch sollte die Thematik ausgiebig behandeln.

http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/009_c_funktionen_014.htm#mjf226932554e147030a8b6a27568f5620

Mit der Einschränkung dass es Referenzen in C nicht gibt. Die sind ein C++ Feature :)

Der Unterschied call-by-reference/call-by-value (um den es hier eher geht) sollte natürlich behandelt werden

Das heisst, wenn ich aus einem Unterprogramm Werte zurückgeben will, dann müssen das Referenzen sein?

Können wir das mal an einem konkreten Beispiel machen.
Z.B. eine Funktion schreiben, bei der ich ein Byte als IN gebe, und dann als OUT 8Bits bekomme. Also eine bitweise Zerlegung von einem Byte

Passt das dann so?

bool b0,b1,b2,b3,b4,b5,b6,b7;
byte mybyte;

void byte_to_bit (byte mybyte, &b0, &b1, &b2, &b3, &b4, &b5, &b6, &b7) {
  b0 = bitRead (mybyte,0);
  .
  .
  b7 = bitRead (mybyte,7);
}

Und der Aufruf wäre dann:

byte_to_bit (byte, b0,b1,b2,b3,b4,b5,b6,b7);

hk007: Das heisst, wenn ich aus einem Unterprogramm Werte zurückgeben will, dann müssen das Referenzen sein?

Wenn du einen einzelnen Wert hast nimmt man normalerweise den Rückgabe-Wert:

int increment(int value)
{
   return value + 1;
}

Erstellt eine Kopie von value, addiert 1 und gibt den Wert zurück

Wenn man mehrere Werte zurückgeben will, dann Referenzen (oder Zeiger)

void byte_to_bit (byte mybyte, &b0, &b1, &b2, &b3, &b4, &b5, &b6, &b7)

Am besten du schreibt es so (wobei das vielleicht sogar geht):

void byte_to_bit (byte mybyte, byte& b0, byte& b1, ...)

Ist m.M.n. deutlicher und lesbarer

void byte_to_bit (byte mybyte, byte& b0, byte& b1, ...)

Das versteh ich jetzt wieder gar nicht. Was soll das 'byte' vor dem & b0...?

Und was meinst du mit ....wobei das vielleicht sogar geht....

Einmal muss ein '*' davor, einmal ein '&'. 
Dann ist es mal ein Pointer, mal ne Referenz.

Na, so rum stimmts doch schon. Jedenfalls bei Funktionsparametern ;)

In der Praxis tauchen Referenzen nur als Funktionsparameter auf, sag ich jetzt mal frech. Da tritt das von Serenifly beschriebene Problem, dass man nach der Initialisierung die Referenz-Adresse selbst nicht ändern kann, gar nicht erst auf.

struct mytype{int x; int y;}; // Demo: ein eigener Datentyp


int myfunc( mytype & myvar) { // Parameter ist eine Referenz
myvar.x = myvar.y; // kann gelesen und verändert werden,  (ohne  * oder ->)
return myvar.x+myvar.y;
}

mytype test = {1,2};

Serial.println(myfunc(test) ); // Im Funktionsaufruf wird nur der Variablenname (ohne &) verwendet
Serial.println(test.x); // 2: Die Variable wurde verändert !

michael_x:
In der Praxis tauchen Referenzen nur als Funktionsparameter auf, sag ich jetzt mal frech.

Oder als Rückgabe-Wert. Halt wie bei Zeigern bloß nicht auf lokale Variablen! Man sollte schon genau wissen was man tut :slight_smile:

Das Standard Beispiel ist der C++ Output Stream. Der gibt eine Referenz auf sich selbst zurück damit man Aufrufe aneinanderketten kann:

cout << var1 << var2;

Die Arduino Streaming Bibliothek funktioniert genauso

Oder der andere Standardfall: Das Überladen des Subskript Operators . Der gibt eine Referenz auf den Wert zurück damit man ihn ändern kann

hk007: Das versteh ich jetzt wieder gar nicht. Was soll das 'byte' vor dem & b0...?

Gerade mal probiert. Geht gar nicht anders. Der Datentyp muss hier dazu

Das ist nicht wie bei normalen Variablen Deklaration wo man die Option hat. Aber auch da muss man vorsichtig sein:

int* var1, var2;

var2 ist ein int! Kein Zeiger auf int. Das ist eine der Stellen wo es logischer ist wenn der Stern (und auch &) am Variablennamen steht. Persönlich bevorzuge ich ihn aber am Datentyp. Das ist aber Geschmackssache.

Serenifly: Gerade mal probiert. Geht gar nicht anders. Der Datentyp muss hier dazu

Aber dann void byte_to_bit (byte mybyte, bool& b0, bool& b1, ...) oder???

Ja. Ist sinnvoller als byte. Aber es ging darum dass die überhaupt mal irgendeinen Typ brauchst :)

Ja, ne is klar (Zitat A.S.) :wink:

Also dann muss meine Funktion so ausschauen:

void byte_to_bit (byte mybyte, bool& b0, bool& b1, bool& b2, bool& b3, bool& b4, bool& b5, bool& b6, bool& b7) {
  b0 = bitRead (mybyte,0);
  .
  .
  b7 = bitRead (mybyte,7);
}

Nach deinem Beispiel:

int* var1, var2;

könnte man das dann auch so verkürzen?

void byte_to_bit (byte mybyte, bool &b0, &b1, &b2, &b3, &b4, &b5, &b6, &b7)

Nein. Das geht bei Funktionen eben nicht. Da braucht jede Variablen explizit ihren Typ. Ich dachte dass du sowas gemeint hast, aber du hast dir wohl was anderes dabei gedacht.

OK verstanden,

aber ich hätte ja noch die Option ein array zu übergeben, oder?

void byte_to_bit (byte mybyte, bool& b[7])

sorry...zu referenzieren

Nein. Das wäre ein Array aus Referenzen auf bool. Was nicht geht. Siehe Punkt 3

Array Variablen sind schon Zeiger. Also entweder so:

void byte_to_bit (byte mybyte, bool b[7])

Oder so:

void byte_to_bit (byte mybyte, bool* b)

Himmel hilf :o

Immer wenn ich meine etwas verstanden zu haben, dann gibts wieder eine Ausnahmeregelung oder so....

Da ich deine beiden Varianten in einer Woche eh wieder nicht mehr lesen und verstehen kann, mach ich es lieber auf die ausführliche Tour.

Trotzdem DANKE für das Aufzeigen der Alternativen. Ich hau mich jetzt in die Koje.

Gute n8

Arrays sind halt eine Welt für sich :)