Verständnisproblem bei Programmierung, es klappt, aber ich weiß nicht warum

ich habe ein Verständnisproblem.

Anfänlich habe ich gelernt, das aktionen wie z.B. goto verwirren und nicht gern genommen werden.

Jetzt habe ich einen Programm-Teil gesehen, der Funktioniert, ich weiß nicht warum , scheint mirÄHNLICH goto zu sein, aber ich kenne den Hintergrund nicht.

Ich versuche , das mal hier abstrakt darzustellen:

Folgender Ablauf:

void loop()
{
Programmzeile 1 bis sagen wir 100
}

soweit klar

in Zeile x benötigen wir den Wert einer Variablen, der nicht immer gleich ist, sondern sich aus einer Berechnung zusammensetzt, die beispielsweise einen Sensorwert enthält, der sich schon mal ändert.

Zeile x lautet etwa

Wert = getVarialble();

!! UNTERHALB des Loops steht nun

void getVariable()
{

F = a/b* Y-400)/Bierflaschendurchmesser*Trinkgeschwindigkeit or what ever;
}

IST das nicht in etwa das selbe wie ein goto mit einem go wieder zurück?

was genau beideutet void getVariable()

Wobei ich eh das "void" nicht kapiere, wie auch das () nicht,

Es erschliesst sich mir einfach nicht. Das, ws ich darüber lesen konnte, war recht abstrkt und ging mir nicht ein.

Vielleicht kann mich hioer mal jemand aufklären.

void getVariable() ist eine Funktion. void bedeutet das kein Wert zurückgegeben wird. () bedeutet das kein Wert an die Funktion übergeben wird.

skyfox60:
...
void getVariable()
{
F = a/b* Y-400)/Bierflaschendurchmesser*Trinkgeschwindigkeit or what ever;
}

IST das nicht in etwa das selbe wie ein goto mit einem go wieder zurück?

Ja. In C/C++ wird mit Funktionen gearbeitet. Die werden aufgerufen und abgearbeitet, am Ende fährt das Programm dann mit der Zeile fort, die auf den Funktionsaufruf folgt. Der Funktionsaufruf entspricht dem gosub in Basic.

was genau beideutet void getVariable()

Der Kopf (man sagt auch „Signatur“) einer Funktion besteht aus den Informationen

  • was eine Funktion zurückgibt. „void“ steht für „nichts“. Wenn eine Funktion einen int-Wert zurückgibt, steht statt void eben int vor dem Funktionsnamen.
  • dem Namen der Funktion. Wenn dieser Name im Code auftaucht, wird quasi notiert, wo der Aufruf stattgefunden hat. Damit weiß der Prozessor, wo er nach dem Rücksprung weitermachen muss.
  • einer Klammer, in der die Parameter stehen, die die Funktion benötigt. Wenn die Funktion getVariable keine Parameter benötigt, ist sie eben leer. An der Klammer erkennt der Compiler, dass es sich nicht um den Namen einer Variable handelt sondern um den Namen einer Funktion.
void getVariable();

heißt also: Es gibt eine Funktion mit dem Namen getVariable, die keine Parameter benötigt und auch keinen Wert zurückgibt.

Ich hoffe, ich konnte ein bisschen Licht ins Dunkle bringen.

Gruß

Gregor

PS: Siehe hier.

skyfox60:
IST das nicht in etwa das selbe wie ein goto mit einem go wieder zurück?

Im Prinzip schon. Keine Programmiersprache kommt ohne Sprünge aus. Im Gegensatz zur goto Anweisung sind die Sprungziele hier aber klar definiert, und können nicht - wie beim goto - nahezu beliebig sein. Das Gleiche gilt auch bei der if-Anweisung oder bei Schleifen. Die Programmblöcke definieren hier die Sprungziele. Im Gegensatz zu goto kann man nicht 'kreuz und quer' springen. Dadurch bleiben die Programme übersichtlicher und klarer strukturiert.

Wert = getVarialble();

Der Prozessor springt hier zur Funktion getVariable(). Am Ende der Funktion springt er aber auf jeden Fall wieder genau zu dieser Stelle zurück an der die Funktion aufgerufen wurde und nirgendwo anders hin.

Die Hinweise der Schreiber vor mir zeigen dir (hoffentlich) auch, dass deine Beispielzeilen so nicht funktionieren können.
Die Zeile

Wert = getVarialble();

fordert zwingend dass deine Funktion einen Wert zurückgeben muss, der dann in der Variablen 'Wert' gespeichert wird.

Die Funktionsdefinition

void getVariable()

bedeutet aber durch das 'void' , dass die Funktion keinen Wert zurück gibt. Das passt nicht zusammen und würde auch vom Compiler angemeckert.
Umgekehrt geht es übrigens: Wenn die Funktion einen Wert zurückgibt, muss man den nicht verwenden. Den kann man beim Aufruf der Funktion auch ignorieren.

ok, halbwegs verstanden

ich bring noch etwas Licht ins Dunkel:

im Setup finde ich folgende zwei Zeilen:

bmp085_get_cal_data();

und danach

bmp085_read_temperature_and_pressure(&temperature,&pressure);

im Loop finde ich als erste Zeile:

bmp085_read_temperature_and_pressure(&temperature,&pressure);

weiter gehts dann mit folgendem:

flpressure = pressure;// move long type pressure into float type flpressure 
 altitude = (float)44330 * (1 - pow(((float) flpressure/p0), 0.190295));

usw. (Die Buchstabenkombination pow findet sich übrigens nirgendwo, anders, weder im Setup noch sonst wo, wird aber nicht angemeckert. Auch etwas, was ich nicht verstehe?!?

unter(hinter) dem Loop finde ich dann:

  1. hier steht dann was in Klammern, jedoch auch außerhalb meines begrenzten Programmierhintergrundes )
void bmp085_read_temperature_and_pressure(int* temperature, long* pressure) {
 int ut= bmp085_read_ut();
 long up = bmp085_read_up();
 long x1, x2, x3, b3, b5, b6, p;
 unsigned long b4, b7;

 //calculate the temperature
 x1 = ((long)ut - ac6) * ac5 >> 15;
 x2 = ((long) mc << 11) / (x1 + md);
 b5 = x1 + x2;
 *temperature = (b5 + 8) >> 4;

 //calculate the pressure
 b6 =

usw.usw, ziemlich lang, viele Zeilen

einzig mir einleuchtendend bis hierhin ist,, dass die komplizierte Rechnerei hier und Datenabfrag wohl der Tatsache geschuldet ist, dass es zu der Zeit, als das Programm geschrieben wurde wohl scheinbar für die BMP085( 180 ) noch keine eigene Library gab.

und dann finde darunter noch:

void bmp085_get_cal_data() {
 Serial.println("Reading Calibration Data");
 ac1 = read_int_register(0xAA);
 Serial.print("AC1: ");
 Serial.println(ac1,DEC);
 ac2 = read_int_register(0xAC);

usw usw usw.
auch das ist lang.

Das Programm endet dann mit:

void ledOn()
{
 digitalWrite(led,1);
}


void ledOff()
{
 digitalWrite(led,0);
}

ledOn und ledOff werden im Loop verwendet.

Stellt sich mir auch hier die Frage, was hat sich der Schreiber dabei gedacht.

Ist es ein Unterschied, hier das "digitalWrite Lampenpin an" im Programm durch z.B. ledOn zu ersetzen??

Hätte man da nicht zweckmäßigerweise gleich im Prgramm schreiben können digitalWrite Lampenpin an und aus, statt dem erst hier einen Namen zu geben und dann den Namen als nächsten Programmschritt aufzuführen?

Macht das die Sache nicht unnötig kompliziert, oder habe ich da wieder mal ein Problem beim Denken?

Setze Deinen Code bitte in Codetags (</>-Button oben links im Forumseditor oder [code] davor und [/code] dahinter ohne *).
Dann ist er auch auf mobilen Geräten besser lesbar.
Das kannst Du auch noch nachträglich ändern.

Gruß Tommy

usw. (Die Buchstabenkombination pow findet sich übrigens nirgendwo, anders, weder im Setup noch sonst wo, wird aber nicht angemeckert. Auch etwas, was ich nicht verstehe?!?

Jetzt wirds schwierig für dich: pow()

Tipp:
Die Sprache, welche du da verwendest, nennt sich C++.

Die C++ Grundlagen, welche du für dein Verständnis benötigst, finden sich z.B. in schönen dicken C++ Büchern.
Gerne haben diese Bücher über 1000 Seiten.

Nur kurz hierzu:

Die Buchstabenkombination pow findet sich übrigens nirgendwo, anders, weder im Setup noch sonst wo, wird aber nicht angemeckert. Auch etwas, was ich nicht verstehe?!?

pow() ist eine Standardfunktion. Die ist quasi fest eingebaut wie if(), switch() usw.

Gruß

Gregor

ah, hab was gefunden

Calculate the value of x raised to the power of y:

z = pow(x, y)

raised to the power of??
läßt sich das etwa übersetzen im obigen Beispiel und deutschem Sprachgebrauch

z = x hoch y?

A = pow(b,2) demnach a=b² ??

ja

Hi

Und: Wenn man Was nicht findet und Es trotzdem klappt - vll. weiß Onkel Google dazu ja was.
Kombiniert mit dem Wort 'Arduino' wäre wohl ebenfalls ein Link auf die Referenz erschienen.

WARUM macht man das digitalWrite nicht direkt in loop()?
Das ist eigentlich recht einfach:
Man lagert Code aus, damit loop() lesbar bleibt.
Auch hat man so nur EINEN Punkt, wo man was ändern muß, wenn sich am Prozedere, die LED ein zu schalten etwas ändert.
Vll. will man statt der LED ein Relais aktivieren, Das ist aber leider LOW-Aktiv ... anstatt nun an dreiundfünfzig Stellen im Code dieses digitalRead zu suchen, nimmt man diese eine Funktion.
Auch: Wenn das Setzen des Pin mehr als 2x ... 3x gebraucht wird, ist eine Funktion 'billiger'.
Das gilt übrigens für ALLES, was so ein Arduinio treibt - wenn man irgend einen Code mehrfach braucht, ist Auslagern der Tipp schlechthin.
Wenn die Code-Teile, Die man mehrfach nutzt sich in Kleinigkeiten unterscheiden, übergibt man der gebauten Funktion Parameter, um die verschiedenen Fälle abzudecken.
Einen Code-Block, Der von zig Stellen aufgerufen wird und universell einsetzbar ist machen das Programm schlank und lesbarer.

MfG

Normal holt man sich Sachen wie pow() durch das Einbinden von sogenannten Header Dateien, in denen diese Sachen beschrieben sind. In diesem Fall steht es in math.h:
https://www.nongnu.org/avr-libc/user-manual/group__avr__math.html

Bei der Arduino Software wird da schon sehr viel im Hintergrund gemacht. Die verwendet selbst diese Mathematik Blibiothek. Dadurch musst du das nicht per Hand Einbinden

Bei der LED on/off Sache hat man durch den Funktions-Namen gleich eine Beschreibung was da gemacht wird. Man muss nicht erst an Hand des Funktionsarguments (0 oder 1) entziffern was mit der LED geschieht

ok, soweit verstanden.

Warum nicht immer Onkel Google?
Das ist auch ganz einfach.

Es kommt immer darauf an, wie man etwas erklärt.
Und ja, bevor ich hier die Frage stellte, habe ich das getan.

Beispiel:
Ich habe eine Erklärung für "void" gefunden.

Die war so toll, das ich davon wirklich nichts, nada, null verstanden habe.
Nach dem Lesen war ich genau so schlau wie vorher, dafür verwirrter als vorher.

Eure Antworten sind einfach in solchen Fällen so viel besser und verständlicher, das ich es dann kapiere.
Und was man einmal kapiert hat, das kann man auch nachvollziehen.

Was die beiden Zeilen angeht, die weiter oben befragt wurden, hab ich das immer noch nicht verstanden.

z.B

bmp085_read_temperature_and_pressure(&temperature,&pressure);

finde ich einmal im Setup, und einmal im Loop

Kommentiere ich eines von beiden aus, funktioniert das alles nicht mehr.
Weiß aber nich wieso und warum.

Einzig konnte ich mir zunächst vorstellen, das man im Setup etwas aufruft, was man in einer constanten Variablen speichert, und was sich im im weiteren Verlauf aber immer ändern wird.
Das hab ich, ander gelöst, auch schon mal gemacht, dafür gibt es Sinnvolle Anwendung.

Der Gedanke widersprach hier jedoch der Tatsache, das diese Zeile im Loop gleich als allererstes kam, man also das Ergebnis aus dem Setup ja noch vorliegen hat.

Nach Gewinn dieser Erkenntnis, kam wieder Verwirrung pur.

Ich würde das nur einfach gern verstehen.

skyfox60:
Der Gedanke widersprach hier jedoch der Tatsache, das diese Zeile im Loop gleich als allererstes kam, man also das Ergebnis aus dem Setup ja noch vorliegen hat.

Abgesehen von schlampiger Programmierung kann ich mir auch vorstellen, dass der erste gelesene Wert des Sensors (oder worum es da geht) immer falsch ist und daher verworfen wird. Sowas gibt's, hatte ich schon.

Gruß

Gregor

Ah, ok,
danke Gregor.

Vermutlich ist es der Messwert, um den es da geht.

Fehlt jetzt nur noch eine kleine Erläuterung zu dem "komischen" Inhalt in der Klammer:

bmp085_read_temperature_and_pressure(&temperature,&pressure);

Wie oben schon ausgeführt, kann man Funktionen Parameter mitgeben, um deren internen Ablauf zu steuern oder aber als Eingangswerte für eine Berechnung - wie es z.B. bei pow() der Fall ist.

Hier kommt eine Aufrufmethode namens "call by value" zur Anwendung:

c = pow(a,2)

Vereinfacht: Der Wert der Variablen a (das was an der durch a bezeichneten Speicherstelle steht), die Zahl 2 und die Adresse nach dem Funktionsaufruf (das ist die Stelle, an der es danach weitergeht) werden auf den Stack gepackt und dann zur Funktion gesprungen. Die holt sich das benötigte vom Stack herunter, verarbeitet es und springt dann zurück.

Bei dem Aufruf oben wird "call by reference" verwendet.
Wieder vereinfacht: Statt den Wert selbst an die Funktion zu übermitteln, wird die Adresse, an der der Wert gespeichert ist übergeben. Die Funktion kann dann aus dieser Speicherstelle den Wert auslesen und verarbeiten. In diesem Fall landen also "Adresse von temperature" und "Adresse von pressure" und die Rücksprungadresse auf dem Stack. Soweit ist aber eigentlich nix gewonnen.
Jetzt kommt der Trick:
Die Funktion kann dann auch Ergebnisse von Berechnungen an diese Speicherstellen schreiben. In diesem Fall stehen nach dem Aufruf der Funktion vielleicht aktualisierte Werte für Temperatur und Luftdruck zur Verfügung.
Damit hat man hier sozusagen zwei Rückgabewerte der Funktion.

Das kann man im Prinzip auch anders lösen; Stichwort zum weiterforschen ist "structure"; man kann auch Strukturen als Rückgabewert von Funktionen verwenden. Das kann aber je nach Größe der Struktur nicht wirklich die brauchbare Lösung sein (zu viel Hin- und Herkopierei und Stackbelastung).

Zum guten Schluß: Gerne genommen (jedenfalls bei mir) sind auch Strukturen als Parameter "by reference"; das hält die Parameterlisten kurz und schafft die Möglichkeit, auch große Ansammlungen von zusammengehörigen Variablen zu beackern.

Gruß Walter

gregorss:
Nur kurz hierzu:

pow() ist eine Standardfunktion. Die ist quasi fest eingebaut wie if(), switch() usw.

Gruß

Gregor

Blos hinkt Dein Beispiel weil if und switch keine Funktionen sondern Kontrollstrukturen sind.

Grüße Uwe

uwefed:
Blos hinkt Dein Beispiel ...

Samstags ist hinken erlaubt :stuck_out_tongue:

Gruß

Gregor

Bei dem Aufruf oben wird "call by reference" verwendet.

Sicher??

Ich glaube dir nicht!
Dort wird ein Zeiger(Adresse) per Value übergeben.

Referenzen sind "ganz" was anderes.

Ein Programm zur Anschauung:

int hochdrei_1(int x) {  // x ist eine Kopie von b
  int y = x * x * x;
  x = 0;                 // diese Zeile ist sinnlos, da x am Ende der Funktion zerstört wird
  return y;
}

int hochdrei_2(int &x) {  // x ist eine Referenz auf b, keine Kopie
  int y = x * x * x;
  x = 0;                  // dies verändert auch b
  return y;
}

void hochdrei_3(int &x, int &y) {  // x und y sind Referenzen und verändern auch b und c
  y = x * x * x;                   // dies verändert auch c
  x = x * x;                       // dies verändert auch b
}

void setup() {
  Serial.begin(115200);
  Serial.println("Anfang");
  int b = 3;
  int c = hochdrei_1(b);
  Serial.print("b ");
  Serial.print(b);
  Serial.print("\tc: ");
  Serial.println(c);

  b = 3;
  c = hochdrei_2(b);
  Serial.print("b ");
  Serial.print(b);
  Serial.print("\tc: ");
  Serial.println(c);

  b = 3;
  hochdrei_3(b, c);
  Serial.print("b ");
  Serial.print(b);
  Serial.print("\tc: ");
  Serial.println(c);
}

void loop() {}

Die Funktion hochdrei_1 ist der Klassiker, denn es werden ein oder mehrere Werte in den Klammern übergeben und genau einer kommt als Ergebnis zurück. Alle lokalen Variablen werden am Ende der Funktion zerstört.

Bei Zeigern (*) und Referenzen (&) wird in der Funktion keine Kopie der Variablen erstellt, sondern die originale Variable unter dem lokalen Namen verwendet und ggf. auch verändert. Will man wie in Deinem Beispiel zwei Werte zurückgeben, so macht das Sinn. Ebenso werden Zeiger gerne bei Feldern verwendet.

Ich hoffe, es hilft beim Verständnis.

EDIT 20200315: Kommentar in hochdrei_3 korrigiert.