Funktion mehrere Werte zurück geben

Im Post #1 wird da sehr viel mit den Variablen gemacht. Ich sollte es Kürzer zusammen fassen. In #7 habe ich extra geschrieben

Und darum geht es mir. Die Funktion readRainPixel() soll das Array füllen. In der Funktion rain() soll auf das Array zugegriffen werden können. Hatte ich versucht in #1 zu erläutern

Dann zeige ich dir mal die teure Version

#include <Streaming.h> // die Lib findest du selber ;-)
Stream &cout = Serial; // cout Emulation für "Arme"

struct Datensatz
{
 uint8_t rainPixel[12];
  int8_t changer[4];
  
};

Datensatz readRainPixel(uint8_t /*site*/)
{
  Datensatz d; 
  for(uint8_t i = 0; i < 4; i++)
  {
    for(uint8_t j = 0; j < 3; j++)
    {
      d.rainPixel[i * j] = 1 + i + j;
    }
    uint8_t upsite = 1;
    d.changer[i] = upsite + i;
  }
  return d;
}


template<typename T>void zeigsMir(T &array)
{
  for(auto data : array)cout << data << endl;
}


void setup()
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;

  Datensatz d = readRainPixel(42);

  
  cout << F("rainPixel: ") << endl;  
  zeigsMir(d.rainPixel);
  cout <<  endl;
  
  cout << F("changer: ") << endl;  
  zeigsMir(d.changer);
  cout <<  endl;
}

void loop()
{
}

1 Like

Und jetzt die billige:

#include <Streaming.h> // die Lib findest du selber ;-)
Stream &cout = Serial; // cout Emulation für "Arme"

struct Datensatz
{
  uint8_t rainPixel[12];
  int8_t changer[4];

};

Datensatz &readRainPixel(Datensatz &d)
{
  for(uint8_t i = 0; i < 4; i++)
  {
    for(uint8_t j = 0; j < 3; j++)
    {
      d.rainPixel[i * j] = 1 + i + j;
    }
    uint8_t upsite = 1;
    d.changer[i] = upsite + i;
  }
  return d;
}

template<typename T>void zeigsMir(T &array)
{
  for(auto data : array)cout << data << endl;
}

void rain()
{
  Datensatz d;
  readRainPixel(d);
  
  cout << F("rainPixel: ") << endl;
  zeigsMir(d.rainPixel);
  cout <<  endl;
  
  cout << F("changer: ") << endl;
  zeigsMir(d.changer);
  cout <<  endl;
}

void setup()
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;
  rain();
}

void loop()
{
}

1 Like

Ahh, weil die Struktur es zusammen fasst kann man es so gesammelt übergeben.

Was ist an dem einen teurer als an dem anderen?

Beim ersten wird die Struktur kopiert.
Sie ist bis zur Kopie Eigentum von readRainPixel()
setup() arbeitet mit der Kopie

Beim zweiten ist die Struktur Eigentum von rain()
Sie wird per Referenz an readRainPixel() übergeben.

Danke. Werde demnächst versuchen umzusetzen. Heute nicht mehr, die Zeitmaschine ruft.

Ja, struct dient dazu, Sachen zusammen zu fassen, welche zusammen gehören. Unterschiedliche Dinge. So hat man alles an einem Haken.
Arrays verwendet man für gleichartige Dinge.
Zudem kann struct noch viel mehr! (es ist der etwas naive Bruder von class)

Da readRainPixel() sowieso nur mit solchen Datensätzen arbeiten kann, habe ich es, schwups, in die Struktur integriert. So dass sich der Datensatz selber konstruiert, mit Daten füllt.
Zudem ist der Datensatz jetzt in der Lage sich selber auszugeben. Dadurch, dass es von Printable erbt, ist es ausdruckbar, bzw. kann es sich selber drucken. Es benötigt nur die eine Abhängigkeit "Wohin soll ich es drucken?"
Diese Abhängigkeit ist im Grunde global, da unser Serial global ist. Aber das weiß die Klasse nicht. Sie bekommt die Schnittstelle per "Parameter Injection" übergeben. Sie kann sich so auf JEDER Schnittstelle ausgeben, welche Print implementiert.

#include <Streaming.h> // die Lib findest du selber ;-)
Stream &cout = Serial; // cout Emulation für "Arme"

struct Datensatz: Printable
{
  uint8_t rainPixel[12];
  int8_t  changer[4];

  Datensatz() // Constructor
  {
    for(uint8_t i = 0; i < 4; i++)
    {
      for(uint8_t j = 0; j < 3; j++)
      {
        rainPixel[i * j] = 1 + i + j;
      }
      uint8_t upsite = 1;
      changer[i] = upsite + i;
    }
  }

  virtual size_t printTo(Print &p) const override
  {
    size_t len {0}; // anzahl ausgegebener Zeichen
    len += p.println(F("rainPixel: "));
    for(const uint8_t &data : rainPixel) len += p.println(data);
    len += p.println();
    len += p.println(F("changer: "));
    for(const int8_t &data : changer) len += p.println(data);
    len += p.println();
    return len;
  }
};




void rain(Stream &outStream)
{
  /* lang version wahlweise
  Datensatz d;
  // outStream.print(d);
  // d.printTo(outStream);
  outStream << d << endl;
  */

  // kurzversion wahlweise
  // outStream.print(Datensatz());
  // Datensatz().printTo(outStream);
  outStream << Datensatz() << endl;
}

void setup()
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;
  rain(cout);
}

void loop()
{
}

Inzwischen habe ich verstanden was du meintest. Ich habe die Funktion aufgerufen, ohne einen Typ davor zu schreiben. Da ich so aber nur einen Wert über return zurück geben kann, hatte ich es anders versucht. Vielleicht war der Titel irreführend. Denn ich gebe ja, so wie ich es ausführen wollte, keine Variablen zurück sondern ändere sie in der Funktion. Sorry für das Missverständnis. Aber als Anfänger findet man ab und zu nicht die richtigen Worte.

combie, deine Beispiele sind wahrscheinlich sehr gut, aber da fehlt mir noch viel Verständnis. Befehle wie:

auto
template
typename
cout
endl
F

sagen mir noch nicht wirklich viel. Da muss ich mich erst nochmal vertiefen. auto erstellt glaube ich den Variablentyp automatisch anhand des übergeben Wertes. cout und endl sind glaube ich Ausgabe für die Konsole. Kann man das in der Arduino IDE verwenden? Hab ich nicht probiert. In Videos mit Visuel Studio Code hatte ich das öffters gesehen, und da wird das in einem Fenster ausgegeben. F kenne ich von Serial.print damit der Text nicht im RAM sondern Progmem geschrieben wird. template und typename sind mir noch völlig unbekannt.

So wie die Zeit es her gab (und das ist oft nicht viel), habe ich mich mit Zeigern beschäftigt. Einiges gelesen, einige Videos angeschaut. Und ja, ich habe es jetzt hinbekommen. Danke für das schupsen Richtung Zeiger. Obwohl es da wahrscheinlich noch viel Übung braucht, bis es sitzt wann & und * verwendet wird. Ich haue das oft durcheinander, bzw. ist es schwierig zu folgen ob Adresse oder Wert gebraucht wird.

void setup() {
  Serial.begin(9600);
  rain(1);
}

void loop() {
}

void readRainPixel(uint8_t site, uint8_t *ptrRainPixel, int8_t *ptrChanger){
  for(uint8_t i=0; i<4; i++){                           
    for(uint8_t j=0; j<3; j++){                         
      *(ptrRainPixel + i*3+j) = (1 + i + j);           
    }
    uint8_t upsite = site;                              
    *(ptrChanger + i) = (upsite + i);                    
  }
}                                                    

void rain(byte site){
  uint8_t rainPixel[12];                            
  int8_t changer[4];                          
  readRainPixel(site, &rainPixel[0], &changer[0]); 
  for(uint8_t i=0; i<12; i++){
    Serial.print("rainPixel "), Serial.print(i), Serial.print(": ");Serial.println(rainPixel[i]);
  }
  for(uint8_t i=0; i<4; i++){
    Serial.print("Changer "), Serial.print(i), Serial.print(": ");Serial.println(changer[i]);
  }
} 

Ich habe in rain() zwei Variablen Arrays die ich in readRainPixel() ändern kann.

Ich weis das ich
readRainPixel(site, &rainPixel[0], &changer[0]);
auch so schreiben kann
readRainPixel(site, rainPixel, changer);
da der Name des Arrays gleichzeitig der Zeiger auf den ersten Wert des Arrays ist. Da ich dann aber vielleicht in ein paar Monaten vor der Zeile stehe, und das nicht mehr weis, habe ich mich für die ausführliche Schreibweise entschieden. Zumindest bis sich das erstmal gefestigt hat.

Danke für eure investierte Zeit und das schupsen.

Wenn es in dem kurzen Code Beispiel ein paar Dinge gibt, die man nicht so machen sollte, wäre es nett, wenn ihr mich darauf hinweisen könntet, und vielleicht auch erwähnen könntet warum man das so macht.

Gruß

Mit den Zeigern, hast du dir wohl die komplizierteste und risikoreichste Möglichkeit ausgesucht.

Frei nach dem Motto:
Wenn es dem Programmierer zu gut geht, dann macht er mit Zeigern.
(so wie der Esel auf dem Eis)

1 Like

Es geht im ganz großen bei mir ja auch um das Speicher sparen (anderer Thread). Und ich suche Wege Speicher frei zu geben.

Die Arrays lokal statt global zu machen ist ein Ansatz. Mit Struktur geht es auch, das hast du ja gezeigt.

Ja, in die Zeiger hinein zu denken ist schwierig.

Entstehen, so wie es jetzt gelöst hatte, Speicherleaks? Sobald rain(), und damit auch readRainPixel(), verlassen wird, sind die Speicherbereiche ja wieder frei gegeben. Und somit sollte kein Speicher verloren gehen, bzw. sich anhäufen, oder?

Ja, nee...
Wenn mir ein fataler Schnitzer aufgefallen wäre, dann hätte ich dir das schon gesagt.

Wobei:
Mir gefallen die magischen Zahlen nicht so.....

Was meinst du mit magischen Zahlen? Das was dort ausgegeben und berechnet wird, ist nicht das, was dann im fertigen Programm berechnet und ausgegeben wird. Der kurze Code ist nur zum testen und verstehen gewesen.

Naja, wer möchte denn raten, was i und j ist und warum ausgerechnet 3 und 4 - 12 habe ich mir aus rainPixel[12] abgeleitet :wink:

Ok - aber darum gehts ja nicht.
Ich wollte Dir das F näher bringen.

Nimm Deinen Code und ersetze

Serial.print("rainPixel ")

durch

Serial.print(F("rainPixel "))

Kompiliere beide Codes und schau, was mit der Speichernutzung passiert.

Erklärt:

runterscrollen.
Besser erklärt in #2 und #5:

Ich sehe nur das, was ich sehe.
Und das ist genau das, was du zeigst.

Ja, im Zusammenhang mit Serial.print ist mir F schon bekannt. Mit F wird es im Flash gespeichert und nimmt keinen RAM weg. Ich weis nur nicht, ob und wie das hier mit F zusammen hängt:

Aber ich denke, das die cout Ausgabe dann dort auch nur in den Flash geschrieben wird.

i und j sind hier eigentlich nur Zähler, um beide Arrays mit 12 bzw. 4 Werten zu füllen. Das schreibe ich mir eigentlich ziemlich genau in Kommentaren dahinter. Bei längeren Pausen habe ich selber sonnst Probleme noch zu wissen warum.
Meist sind es auch Formeln zum wandeln gewisser Werte.
Beispiel:

  switch (kante)
  {
    case oben :
      result = 8 - index;
      break;
    case unten :
      result = index;
      break;
    case rechts :
      if (index == 0) result = 2;
      else if (index == 1) result = 5;
      else result = 8;
      break;
    case links :
      if (index == 0) result = 6;
      else if (index == 1) result = 3;
      else result = 0;
  }

Macht das gleiche wie

switch (kante)
  {
    case Oben:   //0
      result = 8 - index;
      break;
    case Unten:  //3
      result = 0 + index;
      break;
    case Rechts: //1
      result = 2 + index * 3;
      break;
    case Links:  //2
      result = 6 - index * 3;
      break;
  }

Spart aber das if -> if else -> else Konstrukt und somit etwas Speicher.

In dem aktuellen Beispiel ermittelt eine schon bestehende Funktion die NachbarPixel. Ich suche nun gezielt nach den 12 Nachbarpixel einer Fläche, daher die 12. Und jeweils 3 dieser Nachbarpixel in Folge, gehören zu einer Seite. Und um die darunter liegenden Pixel zu berechnen brauche ich, abhängig von der oben liegenden Seite, eine Verrechnungszahl mit den Werten von entweder -3 oder -1 oder 1 oder 3.

Das ist ziemlich komplex und braucht etwas Zeit um sich da rein zu denken, hat aber noiasca sehr gut angestoßen. Auch von ihm kommt die Funktion callPixel, die ich nur leicht angepasst habe.

Diese magischen Zahlen jetzt in Konstanten zu packen würde wieder einiges an Bytes kosten, die ich ja anderweitig evtl. gebrauchen kann. Aber ich weis jetzt was damit gemeint ist. Danke

Das geht aber jetzt schon weit weg vom Thema was ich eigentlich lernen wollte. Wie ich lokale Werte in "untergeordneten Funktionen" (ist das jetzt das richtige Wort dafür?) ändere.

Wenn das im großen Code umgesetzt ist, kommt der ganze Code im Thread https://forum.arduino.cc/t/code-aufraumen-verbessern-speicherplatz-freigeben/

Und es sieht gut aus es werden einige hundert Byte eingespart. Danke

Ja das ist klar. Wusste nur mit dem Begriff "magische Zahlen" nichts anzufangen. Hatte es schon mal gehört. Musste mich dann erstmal belesen, Wiki hat geholfen. Ich weis jetzt was du damit meinst. Danke.

1 Like

Die beiden F sind identisch.
Wie sollte es auch anders sein,.....

Die Streaming Schreibweise, mit ihren <<, spart die Tipparbeit von einigen Serial.println(irgendwas) Kaskaden ein.
Es gibt keine Notwendigkeit das zu nutzen.

1 Like

Du unterschätzt den selbstoptinierenden Kompiler, der solche Gedankengänge gerne durchkreuzt. Außerdem kann es nützlich sein, die Berechnung der Anzahl der Feldelemente zu überprüfen, weil sonst Speicher unerlaubt überschrieben werden kann, was C++ zur Laufzeit nicht abfängt.

Mein Vorschlag:

void setup() {
  Serial.begin(9600);
  Serial.println(F("Start"));
  rain(1);
}

void loop() {}

void readRainPixel(uint8_t site, uint8_t arrRainPixel[], const uint8_t sizRainPixel, int8_t arrChanger[], const uint8_t sizChanger) {
  for (uint8_t i = 0; i < sizChanger; i++) {
    for (uint8_t j = 0; j < (sizChanger - 1); j++) {
      if (i * (sizChanger - 1) + j < sizRainPixel) arrRainPixel[ i * (sizChanger - 1) + j ] = 1 + i + j;
    }
    uint8_t upsite = site;
    arrChanger[i] = upsite + i;
  }
}

void rain(byte site) {
  const uint8_t anzRainPixel = 12;
  uint8_t rainPixel[anzRainPixel];
  const uint8_t anzChanger = 4;
  int8_t changer[anzChanger];
  readRainPixel(site, rainPixel, anzRainPixel, changer, anzChanger);
  for (uint8_t i = 0; i < anzRainPixel; i++) {
    Serial.print(F("rainPixel ")), Serial.print(i), Serial.print(": "); Serial.println(rainPixel[i]);
  }
  for (uint8_t i = 0; i < anzChanger; i++) {
    Serial.print(F("Changer ")), Serial.print(i), Serial.print(": "); Serial.println(changer[i]);
  }
}

Ob sizChanger bei der Berechnung überall richtig eingesetzt ist, überlasse ich Deiner Beurteilung :slightly_smiling_face:

Eine Funktion liefert ja ein Objekt zurück. Das kann alles mögliche sein.

Du rufst beim Getränkeservice an und bestellst eine Flasche Bier. Die wird im Wagen (=Objekt) geliefert. Entspricht einer Variablen.

Jetzt bestellst Du einen Kasten Bier, das ist ein Feld gleichartiger Elemente.

Oder Du bestellst einen Kasten Bier und eine Flasche Korn, das wäre eine Struktur aus einem Feld (Kasten Bier) und einer Variablen (Korn), alles in einem Objekt (Auto) geliefert.

Prost! Oder #27 unters Kopfkissen legen!

Stimmt, wenn ich das Array in der Größe in rain() ändere weis die For-Schleife in readRainPixel() dass nicht. Da kann es dann zu Problemen kommen, mit den Zeigern schreibe ich dann irgendwo hin, wo eigentlich was völlig anderes hinterlegt ist, und das ganze Programm hängt sich auf.

Ich habe schon Überlegt das ganze, was alles zu einem Pixel gehört (Index, Seite, Kante, Ausrichtung, Nachbarpixel, Nachbarflächen) in eine Klasse bzw. Struktur zu legen, wie schon vorgeschlagen. Ich denke das macht das dann auch übersichtlicher, und lässt sich einfacher darauf zugreifen. Aber das ist dann ja wieder ein anderes Thema. Das mit den Zeigern war ein schöner Exkurs.

Danke

das sollte so möglich sein:
Du weist auf welcher Fläche du bist
Du weist alle 4 Partnerflächen und deren Partnerkante
Es gibt eine Funktion um einen Pixel je Kante zu ermitteln - das musst du halt 3 mal aufrufen.
am Ende hast die 12 pixel rund um die Fläche.

Oder einfach nur stur 6 Flächen mal 12 pixel in einem Array halten - was ja auch nur 72 Byte sind.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.