Kann man Funktionen mit einem variablen Namen aufrufen?

Ich hoffe der Titel hat nicht zu sehr verwirrt. Wusste nicht, wie ich es am besten beschreiben soll. Also, wenn ich abhängig von einer Variable immer ein anderes Programm aufrufen will:

  switch (x) {
    case 1:
      SUB_1(); break;
    case 2:
      SUB_2(); break;
    case 3:
      SUB_3(); break;
.......

Geht das irgendwie einfacher? In etwa so?

SUB_x();

Arrays aus Funktionszeigern. Ob das einfacher ist sei aber mal dahingestellt. Gerade bei deinen Kenntnissen eher nicht. Das wird für dich eher weniger lesbar und man macht leichter katastrophale Fehler.

Mit dem Begriff “Funktionszeiger” hab ich mal Tante Google angeworfen.

Du meinst vom Prinzip her so was?

typedef void (*pFunc)();
void Func1()
{
    //...
}
void Func2()
{    
    //...
}
int main()
{
    pFunc Arr[2];
    Arr[0] = &Func1;
    Arr[1] = &Func2;
    int i;
    for (i = 0; i < 2 ; i++)
    {
       Arr[i]();
    }
    return 0;
}

Ja. Wobei das Array eher global ist

typedef void(*funcPtr)();

funcPtr functions[] = { func1, func2 };

void loop()
{
  select(0);
  select(1);

  delay(1000);
}

void select(int func)
{
  functions[func]();
}

void func1()
{
  Serial << "func 1" << endl;
}

void func2()
{
  Serial << "func 2" << endl;
}

Du musst da halt tunlichst aufpassen dass der Index immer passt oder dein Programm stürzt ab. Das sollte man bei der Version noch in select() einbauen. Wie gesagt einfacher ist das hier nicht unbedingt.

Es dagegen sehr schön wenn du einen bestimmten Ablauf von Funktionen festlegen willst. z.B. bei LED-Animationen u.ä.

OK,

wieso hast du den Aufruf noch mal mit dem select() übergeben?

void loop()
{
  select(0);
  select(1);

  delay(1000);
}

void select(int func)
{
  functions[func]();
}

Kann ich die entsprechende Funktion nicht gleich mit functions[x](); aufrufen?

Ja, aber wie gesagt ist es nicht verkehrt da den Index zu überprüfen. Man kann auch testen dass man keinen Null-Pointer aufruft:

void select(int func)
{
  if((func < sizeof(functions) / sizeof(functions[0])) && (functions[func] != NULL))
  {
     functions[func]();
  }
}

OK,

ich probiers mal aus. Mir hat halt die ellenlange (20 Einträge) Switch-Anweisung nicht gefallen. Wobei ich mit der Methode auch nicht so viel gewinne, da ich die Namen der 20 Funktionen in das Array einzeln in Klartext übergeben muss. Ich hatte mit meiner Frage die Hoffnung, dass man den Namen des Funktionsaufrufes zur Laufzeit irgendwie aus einem Text und einer laufenden Nummer zusammenbasteln und übergeben kann.

Das geht nur mit Konstanten zur Compile-Zeit:

#define CALL_FUNC(X) (func##X##())

void loop()
{
  CALL_FUNC(1);
  CALL_FUNC(2);
}

Oder mit Parametern z.B.:

#define CALL_FUNC(X, Y) (func##X##(Y))

Die Funktionsnamen sind zur Laufzeit nicht mehr bekannt. Die werden beim Compilieren durch Adressen ersetzt.

hk007: ... Ich hatte mit meiner Frage die Hoffnung, dass man den Namen des Funktionsaufrufes zur Laufzeit irgendwie aus einem Text und einer laufenden Nummer zusammenbasteln und übergeben kann.

Naja ... so ungefähr kannst Du das ja auch machen. Aber ich glaube nicht, dass der Code dadurch sonderlich übersichtlicher oder lesbarer wird. Man kann sowas bestimmt noch „schlimmer“ machen. Und auch dann gilt: Schlimmer geht immer :-)

Ich versuche in so einem Fall, alles über eine geeignete Formatierung (Einrückungen, mehrere Befehle in /eine/ statt mehreren Zeilen ...) zu lösen. Das ist garantiert wesentlich besser lesbar und für Leute wie mich, die manchmal im Blödsein nicht zu schlagen sind, leichter nachvollziehbar.

Gruß

Gregor

Wenn Du Zeiger (Pointer) auf Funktionen verwendest und diese in ein Array schreibst kanst Du mit dem Index die Funktion aufrufen

funktion[ x ]

Der Sketch wird dadurch aber serhr schwer leserlich da Du keine sprechenden Funktionsnamen mehr hast. Dazu kommt wie gesagt die Gefahr des Indexoverflow und der falschen Zeigerverwendung.

Grüße Uwe

Hallo zusammen,

unabhängig davon, ob es im Code schlechter lesbar ist (was es ist !!!), könnte es zur Laufzeit in zeitkritischen Anwendungen einen Vorteil geben, wenn man ein Array mit Funktionszeigern verwendet.

Der indizierte Aufruf ist da unter Umständen schneller als eine Implementierung mit vielen switch/if-Statements.

Nimmt man 20 Funktionen an und tritt der Fall auf, dass "die 20." Funktion aufgerufen werden soll, gibt halt auch 20 Vergleiche, bis es zum Aufruf kommt. Beim Indizieren wird die Adresse ermittelt und danach erfolgt der Aufruf.

Korrigiert mich bitte, wenn diese These falsch ist.

Viele Grüße

Harry

HarryR:
Nimmt man 20 Funktionen an und tritt der Fall auf, dass “die 20.” Funktion
aufgerufen werden soll, gibt halt auch 20 Vergleiche, bis es zum Aufruf kommt.
Beim Indizieren wird die Adresse ermittelt und danach erfolgt der Aufruf.

Richtig!
Fragt sich aber, woher der Wert 20 kommt.
Evtl. brauchst du den gar nicht, sondern kannst an der Stelle wo du auf das Äquivalent zu
int myswitch = 20; kommst, stattdessen gleich sowas wie

void (*myfunctionPtr)() = myfunc20;

setzen und diese Funktion dann als

    (*myfunctionPtr)();  // rufe die passende Funktion auf

aufrufen.

michael_x: Fragt sich aber, woher der Wert 20 kommt.

Das fragt sich nur einer, der nicht verstanden hat, dass das ein Beispiel ist.

SCNR

Gregor

michael_x: Richtig! Fragt sich aber, woher der Wert 20 kommt. Evtl. brauchst du den gar nicht, sondern kannst an der Stelle wo du auf das Äquivalent zu int myswitch = 20; kommst, stattdessen gleich sowas wie

void (*myfunctionPtr)() = myfunc20;

setzen und diese Funktion dann als

    (*myfunctionPtr)();  // rufe die passende Funktion auf

aufrufen.

Der Vergleich war Funktionen mit verschiedenen Namen und eine Switch Case Anweisung und einem Array mit Zeigern auf die Funktionen.

Grüße Uwe

Jetzt mal mit praktischen Hintergrund:
Ich habe ein 4-zeiliges Display. Da möchte ich 20 verschiedene Menübilder mit unterschiedlichen Inhalten darstellen. Durch das Menü arbeite ich mich mit einem Encoder, der eine Integer Variable zwischen 0 und 19 rauf und runterzählt. Dass die Variable nur in dem Bereich bleibt, dafür ist gesorgt. Also sollte ein Indexoverflow auszuschliessen sein.
Wenn ich jetzt die beiden varianten (Switch und Zeigerarray) gegenüberstelle, dann finde ich das mit dem Array gar nicht so falsch am Platze. Auch bezüglich der Lesbarkeit sollte es selbst für mich C/C+±Nase mit einigermassen Kommentar auch noch später lesbar sein.

Hier mal im Vergleich beide Varianten für 10(+1) Bilder
SWITCH:

void LCD_main() {
  switch (LcdPage) {
    case 1:
      LCDPage_1(); break;
    case 2:
      LCDPage_2(); break;
    case 3:
      LCDPage_3(); break;
    case 4:
      LCDPage_4(); break;
    case 5:
      LCDPage_5(); break;                  
    case 6:
      LCDPage_6(); break;  
    case 7:
      LCDPage_7(); break;  
    case 8:
      LCDPage_8(); break;        
    case 9:
      LCDPage_9(); break;      
    case 10:
      LCDPage_10(); break;      
    default:
      LCDPage_0();
  }
}

ZEIGERARRAY:

typedef void(*funcPtr)();
funcPtr LCDfunction[] = {LCDPage_0, LCDPage_1, LCDPage_2, LCDPage_3, LCDPage_4, LCDPage_5, LCDPage_6, LCDPage_7, LCDPage_8, LCDPage_9, LCDPage_10};

void LCD_main() {
  LCDfunction[LcdPage]();
}

Die Funktionen für die einzelnen Menubilder sind bei beiden Varianten die selben.

Ein anderer gedanklicher Ansatz von mir war auch schon, dass ich mir 2dimensionales Array von 20 x 4 “lcd.print…” Statements ablege, und dann abhängig von meinem Encoderzähler die entsprechenden 4 Array-elemente ans LCD schicke.
Aber da bin ich dann schon wieder an meinen Grenzen, wie ich so was:lcd << "Temp 1 = " << _FLOAT(Temperatur[1],1) << " " << deg << "C"; in ein Array bekomme.

Ja, dafür sind Funktionszeiger super

Sprechende Namen zu haben ist da eben völlig egal, da die Funktionen eh nur durchnummeriert sind.

Wegen deiner anderen Idee. Arrays aus structs sind die bessere Lösung. Damit bist du flexibler. Die können Text, Variablen oder auch Zeiger auf Variablen enthalten. Und den Text kann man als char* anlegen, d.h. verschiedene structs können Text unterschiedlicher Länge enthalten

Was man auch machen kann ist die Funktionen automatisch mit Makros zu erzeugen:

#define create_func(number, text1, val, text2 ) \
void LCDPage_##number() \
{ \
  lcd << text1 << val << text2; \
}

create_func(0, F("Func_0: "), 23.5, " C");
create_func(1, F("Func_1: "), 3, " V");

typedef void(*funcPtr)();
funcPtr LCDfunctions[] = { LCDPage_0, LCDPage_1 };

void setup()
{
  for (int i = 0; i < sizeof(LCDfunctions) / sizeof(LCDfunctions[0]); i++)
    LCDfunctions[i]();

}

Hat aber den großen Nachteil, dass kaum lesbare Fehler kommen wenn was mit dem Makro nicht passt.

Wichtig: nach dem \ darf kein Leerzeichen kommen! Sonst spinnt der Prä-Prozessor.

Auch hier gilt: das ist eine Text-Ersetzung. Man kann also auch Variablen-Namen an das Makro übergeben.

Das mit den Struct-Arrays werde ich mir mal anschauen.

Ist das dann so gedacht, dass jedes Element einer LCD-Zeile (["Temp 1 = "] ; [_FLOAT(Temperatur[1],1)] ; [deg] ; [“C”]) ein einzelnes Array-Element darstellen soll?
Und ich das dann bei der Ausgabe mit den Streaming-Operator (<<) zusammenbauen muss?
Dann geht das aber nur, wenn ich für eine einzelne LCD-Zeile immer die selbe Struktur habe. Und das ist bei mir leider nicht der Fall.

Das mit den Makros nehm ich mal als Möglichkeit zur Kenntnis :wink: Seh ich aber für mich nicht als passend.

hk007:
Dann geht das aber nur, wenn ich für eine einzelne LCD-Zeile immer die selbe Struktur habe. Und das ist bei mir leider nicht der Fall.

Dann kannst du es vergessen

Das mit den Makros nehm ich mal als Möglichkeit zur Kenntnis :wink: Seh ich aber für mich nicht als passend.

Wennn du x Funktion schreibst passt das für dich. Das dient nur dazu dir die Schreib-Arbeit abzunehmen. Und der Code wird eventuell etwas übersichtlicher, da nicht so viel Platz mit Funktionsdeklarationen drauf geht und der eigentliche Inhalt der Funktionen direkt zusammen steht.

Hat eben den Vorteil, dass die Datentypen nicht fest stehen. Da kann man auch sowas machen:

create_func(0, F("Func_0: ") << temp << "C");

Wenn man das Makro einfach so definiert:

#define create_func(number, arg) \

Dann ist man völlig flexibel was die Ausgabe betrifft. Oder wenn man zwei Zeilen will, nimmt man pro Zeile einen Parameter und macht dazwischen setCursor()