Wieso verwendet man void-Funktionen als Parameter für eine andere Funktion?



  • Hallo Leute, ich bin Anfänger in C. Ich schaue mir gerade Demo-Beispielcode von 'nem Roboter-Hobbyprojekt an.

    Dort taucht sinngemäß sowas auf:

    funcAussen(funcInnen);
    

    Hierbei ist die "innere Funktion" wie folgt:

    void funcInnen(void) 
    {
    //irgendein Code
    }
    

    In die innere Funktion kommt also offenbar nichts rein und nichts raus, sieht tut bloß irgendwas mit einer globalen Variablen. (Funktion ist in der main.c-File definiert.) Der Ausgabewert ist aber immer void, vollkommen egal, was die Funktion macht. Für die Funktion funcAussen macht es also so oder so keinen Unterschied und man hätte gleich funcAussen(void) schreiben - und dafür den Inhalt von funcInnen(void) direkt in funcAussen schreiben können oder nicht?

    So meine ich:

    funcAussen()
    {
       funcInnen();
    
       // sonstiger, ursprünglicher Code von funcAussen
    }
    

    Wäre es vom Programmablauf her nicht dasselbe? Fände es so jedenfalls übersichtlicher.



  • Du bist auf dem Holzweg 😉

    Erstmal:
    eine Funktion von Typ void sagt nur, dass sie keinen Wert zurückgibt.

    void foo()
    {
       ...
    }
    
    void bar()
    {
        int x = foo();
    }
    

    wird die einen Fehler geben, denn foo liefert keinen Wert zurück. Korrekt wäre

    void foo()
    {
       ...
    }
    
    void bar()
    {
        foo();
    }
    

    ------

    void foo()
    {
      funcAussen(funcInnen);
    }
    
    void funcInnen(void) 
    {
      ...
    }
    

    Das hat nichts mit void zu tun, es ist nur Zufall, dass dein Bsp void nutzt.

    Nun, hier geht es um Funktionszeiger. Du kannst einen Zeiger auf eine Funktion in eine Variable speichern, um später diese Funktion über die Variable aufzurufen.

    Die Deklaration eines Funktionszeiger lautet wie folgt:

    return-typ (*varname)([params]);
    

    return-typ ist der Rückgabewert der Funktion. varname ist der Name der Variable, und params kann weggelassen werden, falls die Funktion keine Parameter bekommt. Falls doch, dann musst du dort die Typen der Parameter. Bsp:

    int add(int x, int y)
    {
        return x+y;
    }
    
    int sub(int x, int y)
    {
        return x-y;
    }
    
    int calculate(int x, int y, char operator)
    {
        int (*func)(int, int);
    
        if(operator == '+')
            func = add;
        else
            func = sub;
    
        return func(x, y);
    }
    
    calculate(10, 11, '+');  // liefert 21 zurück
    

    In deinem Bsp funcAussen sieht dann so aus:

    void funcAussen(void (*func)(void))
    {
        ....
        func();
        ....
    }
    

    Mein Beispiel zeigt, wie man Funktionszeiger verwendet. Es ist nicht sonderlich hilfreich an dieser Steller, wenn du aber ein gutes Beispiel suchst, dann gibt es man: qsort(3), mit der du Arrays beliebiges Types sortieren kannst. Über die Funktionszeiger lieferst du die Vergleichsfunktion.



  • Ich gebe dir recht:
    - globale Variablen sind Schrott, auch Hardwareansteuerung ist dafür keine Ausrede
    - ebenso sind void-Funktionen und auch noch ohne Parameter ziemlich sinnfrei, da der Datenaustausch mit anderen Programmkontexten - wie du richtig erkannt hast - nur global (und somit laienhaft und fehlerbehaftet) passieren kann

    Prinzipiell liegt hier daher ausschließlich eine Funktionalitätenkapselung vor, die äußere Funktion kannst du praktisch als definierte Schnittstelle ansehen, die feststehend ist - also praktisch als eine Art Fassade - die aber unterschiedliche Operationen ausführen kann.

    Sowas (meist Callback genannt) wird meist in Bibliotheken verwendet - die Bibliothek definiert nur die Fassade durch die Vorgabe des äußeren Funktionsnamens - und der Anwender der Bibliothek (meist nicht derselbe wie der Bibliothekshersteller) kann dann seinen eigenen Userkontext quasi innerhalb des Bibliothekskontextes ausführen lassen, der Bibliothekshersteller gibt nur die Schnittstelle, hier also die Funktionssignatur der inneren Funktion vor, für den Rest ist der Aufrufer verantwortlich.
    Das macht natürlich nur richtig Sinn, wenn auch Parameter verwendet werden.

    void maschine( void operation() ); /* Schnittstelle der Bibliothek */
    
    /* eigener Userkontext */
    void ein() { }
    void aus() { }
    void linkslauf() { }
    void rechtslauf() { }
    
    int main()
    {
       maschine( ein );
       maschine( linkslauf );
       maschine( rechtslauf );
       maschine( aus );
       return 0;
    }
    

    Der Anwender kann also beliebig zur Laufzeit die Bibliothek unter Beachtung der Schnittstellendefinitionen nutzen und in seinem Programm dann selbst den Ablauf bestimmen - die Bibliothek stellt nur ein Interface zum Zugriff auf externe Ressourcen bereit.



  • Hier ein Beispiel mit der man: qsort(3) Funktion:

    Du willst ein Array von Objekten des Typs struct Person lexikographisch zuerst nach Nachmame, dann nach Vorname sortieren.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct Person {
      char lastname[100];
      char firstname[100];
    };
    
    void print_person(struct Person *person)
    {
        printf("%s, %s", person->lastname, person->firstname);
    }
    
    void print_list_of_people(struct Person *person, size_t len)
    {
    	size_t i;
    	for(i = 0; i < len; ++i)
    	{
    		print_person(&(person[i]));   // du kannst auch schreiben
    		                              // print_person(person + i);
    									  // wenn du das nicht verstehst,
    									  // lies über Zeigerarithmetik.
    
    		printf(" -> ");
    	}
    
    	printf(" END OF LIST\n");
    }
    
    int cmp_person(const void *obj1, const void *obj2); // definition kommt nach int main() vor
    
    int main(void)
    {
    	struct Person list[] = {
    		{ "Lennon",    "John" },
    		{ "Star",      "Ringo" },
    		{ "McCartney", "Paul" },
    		{ "Harrison",  "George" },
    		{ "Mercury",   "Freddie" },
    		{ "Deacon",    "John" },
    		{ "May",       "Brian" },
    		{ "Taylor",    "Roger" },
    	};
    
    	print_list_of_people(list, sizeof list / sizeof *list);
    
            // hier verwendet man einen Funktionszeiger
    	qsort(list, sizeof list / sizeof *list, sizeof *list, cmp_person);
    
    	printf("\nSorted list:\n\n");
    
    	print_list_of_people(list, sizeof list / sizeof *list);
    
    	return 0;
    }
    
    int cmp_person(const void *obj1, const void *obj2) {
    	const struct Person *p1 = obj1, *p2 = obj2;
    
    	// lexikogrphischer Vergleich nach Nachname
    	int cmp = strcmp(p1->lastname, p2->lastname);
    
    	if(cmp != 0)
    		return cmp;
    
    	return strcmp(p1->firstname, p2->firstname);
    }
    

    Ausgabe:

    Lennon, John -> Star, Ringo -> McCartney, Paul -> Harrison, George -> Mercury, Freddie -> Deacon, John -> May, Brian -> Taylor, Roger -> END OF LIST

    Sorted list:

    Deacon, John -> Harrison, George -> Lennon, John -> May, Brian -> McCartney, Paul -> Mercury, Freddie -> Star, Ringo -> Taylor, Roger -> END OF LIST



  • Vielleicht solltest Du die Beach Boys noch mit aufnehmen, sonst wird Zeile 68 nie ausgeführt 😉



  • SG1 schrieb:

    Vielleicht solltest Du die Beach Boys noch mit aufnehmen, sonst wird Zeile 68 nie ausgeführt 😉

    daran habe ich auch gedacht, aber ich war zu faul andere Namen zu suchen 🤡



  • Zeile 50 geht auch kürzer

    qsort(list, sizeof list / sizeof *list, sizeof *list, strcmp);
    

    das schult das Verständnis zwischen char[] und char*.



  • Ok, aber das geht auch ohne Funktionszeiger und sehr viel kürzer:

    set<pair<string, string>> P = { /* ... */ };
    for (auto i : P)
      cout << i.second << " " << i.first << endl;
    


  • Oops - ist ja das C-Unterforum hier. Hab' gedacht, es wäre C++ ...:D



  • Danke supertux und Wutz, ihr habt einen kleinen Verständnis-Boost bei mir ausgelöst. 🙂

    Mit Funktionszeigern kam ich bisher noch nicht in Kontakt. Hast sie mir aber mit deinen Beispielen gut schmackhaft machen können, supertux, gefällt mir!
    Du hast Recht,

    void funcAussen(void (*func)(void))
    {
        ....
        func();
        ....
    }
    

    habe ich nun auch in einer der mitgelieferten Bibliotheken in dieser Form gefunden.

    Ohne Wutz' Beurteilung und Hintergrundinfos hätte ich trotzdem nicht kapiert, was bei meinem Beispiel die Idee dahinter gewesen sein könnte. Ja, das mit den "Callbacks" passt und wird in meinem Demo-Code anscheinend auch häufiger angewendet. Danke Wutz!
    Mal sehen, vielleicht werde ich die mitgelieferten Bibliotheken wohl teilweise ändern und den Demo-Code wohl doch nicht so direkt als Ausgangsbasis übernehmen, wie ich es ursprünglich vor hatte...



  • zufallswert schrieb:

    Ok, aber das geht auch ohne Funktionszeiger und sehr viel kürzer:

    set<pair<string, string>> P = { /* ... */ };
    for (auto i : P)
      cout << i.second << " " << i.first << endl;
    

    es geht gar nicht darum, wie man sortiert sondern wie man Funktionspointern verwendet 😕


Anmelden zum Antworten