Variable Anzahl von Parametern - Beispielcode klappt nicht



  • Hallo ihr Lieben,
    ich wollte ein kleines Programm schreiben, in dem jedes Prozentzeichen in meinem geschriebenen Text durch eine Parameter ersetzt wird, ich ich anschließend anfüge. Kein weiterer Sinn dahinter, reine Übung.
    Was genau ist an meinem Quelltext falsch? Denn mein Text wird am Ende unverändert wiedergegeben...

    #include <iostream>
    #include <string>
    #include <conio.h>
    #include <cstdarg>

    void zeichen(std::string str, ...)
    {
    va_list args;
    va_start(args, str);

    for(int i=0; i<str.length(); ++i)
    {
    	if(str[i] == '%' && str[i+1] < str.length())
    	{
    		std::cout << va_arg(args, char);
    	}
    	else std::cout << str[i];
    }
    

    va_end(args);
    }

    int main()
    {
    zeichen("Das % ist % mein % Beispieltext", '-', '+', 'y');
    getch();
    }

    Vielen Dank für alle Hinweis!


  • Mod

    @Hawaiihemd sagte in Variable Anzahl von Parametern - Beispielcode klappt nicht:

    str[i+1] < str.length()

    Häh? was soll das deiner Meinung nach machen? Was da jedenfalls steht ist, dass der Wert eines Zeichens in deiner Zeichenkette kleiner sein soll als die Länge der Zeichenkette. Was ziemlicher Unsinn ist.



  • ACh so, ja, das habe ich aus einem anderen Programm übernommen, in dem es tatsächlich eine Bedeutung hatte. Sorry, das war eher ein Logikfehler. Erklärt aber nicht die eigentiche Ursache, oder? Denn selbst wenn ich das weglasse, klappt es nicht...


  • Mod

    @Hawaiihemd sagte in Variable Anzahl von Parametern - Beispielcode klappt nicht:

    ACh so, ja, das habe ich aus einem anderen Programm übernommen, in dem es tatsächlich eine Bedeutung hatte. Sorry, das war eher ein Logikfehler. Erklärt aber nicht die eigentiche Ursache, oder? Denn selbst wenn ich das weglasse, klappt es nicht...

    Sicher? Das ist nämlich eine sehr passende Erklärung denn die Bedingung wird so gut wie nie wahr sein. Bei mir klappt's.

    Und wenn es dann klappt, solltest du wissen, dass man das in C++ ganz anders macht, wenn man Funktionen mit variablen Argumenten braucht. du hast dir hier eine C-Lösung abgeguckt, die aus historischen Gründen noch unterstützt wird. Wie das in C++ geht, geht wahrscheinlich über deine derzeitigen Grundlagen hinaus. Braucht man aber auch so gut wie nie, cout macht ja vor, wie man auch ohne variable Argumente ganz gut auskommen kann.



  • Jetzt kommt bei mir folgende Meldung:

    'char' ist promoted to 'int' when passed through. '...'
    ...
    so you should pass 'int' not 'char' to 'va_arg'



  • @Hawaiihemd sagte in Variable Anzahl von Parametern - Beispielcode klappt nicht:

    Jetzt kommt bei mir folgende Meldung:

    'char' ist promoted to 'int' when passed through. '...'
    ...
    so you should pass 'int' not 'char' to 'va_arg'

    Der Compiler hat recht.

    Bei Variablen Argumenten werden Ganzzahltypen auf mindestens int promoted.
    Bei Fließkomma ist es mindestens double. (Darum ist bei printf %f sowohl für float als auch für double)



  • Okay. Aber wenn ich das als int ausgebe, dann gibt er mir die ASCII-Zahlen dazu aus. Aber eben nicht die Zeichen... Das will ich ja nicht... Und lässt sich das irgendwie doch so ändern, dass es klappt? Ne, anders gefragt: WIE lässt sich das ändern? Ich habe gelernt: Geht nicht, gibt's fast nie in der Programmierung 🙂



  • @Hawaiihemd Dann gib ein char aus.

    Du kannst den Wert in einer Variable vom Typ char speichern und diese ausgeben.
    oder
    den Wert bei der Ausgabe in ein char casten.



  • Seltsam. Ich habe jetzt - ich habe gelesen dass es so läuft - eine '0' dazu addiert, weil ein int ja damit zum entsprechenden char wird.

    for(int i=0; i<str.length(); ++i)
    {
    if(str[i] == '%' && str[i+1] < str.length())
    {
    std::cout << (va_arg(args, int) + '0');
    }
    else std::cout << str[i];
    }
    va_end(args);
    }

    Aber das klappt trotzdem nicht... Hm...



  • Ne, alles gut.
    ich habe meinen Fehler gefunden.
    Jetzt sieht meine Funktion so aus:

    void zeichen(std::string str, ...)
    {
    va_list args;
    va_start(args, str);

    for(int i=0; i<str.length(); ++i)
    {

    if(str[i] == '%')
    {
        int a = va_arg(args, int);
        char b = (char) a;
    	std::cout << b;
    }
    else std::cout << str[i];
    

    }
    va_end(args);
    }

    Und damit passt es. Ich habe die Abfrage zu früh gemacht und bin da natürlich über den tatsächlichen Bereich hinausgegangen und hatte völlig wilde Ergebnisse...

    Vielen Dank für Eure Hilfen!



  • @Hawaiihemd sagte in Variable Anzahl von Parametern - Beispielcode klappt nicht:

    Ich habe jetzt - ich habe gelesen dass es so läuft - eine '0' dazu addiert, weil ein int ja damit zum entsprechenden char wird.

    Da hast du etwas missverstanden.
    Das Zeichenliteral '0' ist auch nur ein int (bei ASCII mit dem Wert 48)

    Wenn du Werte im Bereich 0-9 hast, kannst du dazu den Wert von '0' addieren um daraus den Wert für das Zeichen der Ziffer zu bekommen.
    (Bei C++ müssen die Zeichen der Ziffern aufeinanderfolgend sein)

    Und bei Ganzzahlberechnungen wird mindestens in intgerechnet.

    cout erkennt den Typ vom Wert. Bei char-Typen wird halt angenommen, dass man das Zeichen sehen möchte.


  • Mod

    Bevor das gerade zu tief versinkt in die Spezialitäten der Argument promotion in C: Hier die obligatorische C++-Musterlösung: Es ist auf den ersten Blick ein bisschen komplizierter, aber dafür hat man halt keine Probleme mit irgendwelchen chars die plötzlich zu ints werden. Und man bekommt Unterstützung für alle Typen kostenlos, wo es im C-Code schon schwer genug war nur char zu unterstützen. Der Code weiß nix von std::complex und doch kann er ihn ausgeben.

    Wenn man noch nie ein variadic Template gesehen hat (oder, wie ich beim OP befürchte, gar noch nie ein Template gesehen hat), versteht man natürlich nur Bahnhof. Aber das ist ein um so mehr ein Grund, sich mit den "richtigen" C++-Sprachmitteln zu beschäftigen, anstatt mit va_args, damit man so etwas versteht und selber schreiben kann.

    #include <iostream>
    
    void typesafe_printf(const char* format) // Rekursionsende des Templates
    {
        while (*format) {
            if (*format == '%') {
                throw std::runtime_error("Mehr Platzhalter als Parameter");
            }
            std::cout << *format++;
        }
    };
    
    template<typename T, typename... Rest>
    void typesafe_printf(const char* format, T param, Rest... params) 
    {
        while (*format) {
            if (*format == '%') {
                std::cout << param;
                format++;
                typesafe_printf(format, params...);
                return;
            }
            std::cout << *format++;
        }
    };
    
    
    #include <string>
    #include <complex>
    
    int main()
    {
    	typesafe_printf("Ein char: %, viele chars: %, ein string: %, ein int: %, ein double: %, ein complex: %",
    	'a', "abc", std::string("blah"), 123, 123.123, std::complex<double>(20.2,2.5)
    	);
    }
    

    Ganz grobe Erklärung:

    template<typename T, typename... Rest>
    void typesafe_printf(const char* format, T param, Rest... params) 
    

    Diese Funktion, typesafe_printf, hat als erstes Argument den formatstring, dann ein beliebiges anderes Argument (param), und dann eine beliebige Anzahl (ausgedrückt durch das ...) anderer Argumente von beliebigem Typ (params). Mit dem Format machen wir dann das, was Hawaiihemd auch schon bei sich machte: Ein Zeichen nach dem anderen ausgeben, außer es ist ein %, in dem Fall geben wir das erste Argument aus (param). Danach kommt der große Unterschied zum va_args-Code: va_args leben zur Laufzeit und können daher in Laufzeitkonstrukten wie Schleifen abgearbeitet werden. Templates leben zur Compilezeit, wir können also nicht in einer Schleife durch params gehen. Stattdessen benutzen wir Rekursion und rufen wieder typesafe_printf auf, mit dem Rest des Formatstrings, ohne den vormals ersten Parameter, und mit all den restlichen Parametern (params). Dadurch wird der erste Parameter in params nun zum neuen ersten Parameter von typesafe_printf (also zu param) und das Spiel beginnt von vorne. Das wiederholen wir, bis params leer ist und wir in der spezialisierten Funktion für das Rekursionsende landen.



  • Noch ein Hinweis: Falls das nicht nur eine persönliche Übung werden soll und die Möglicheit besteht, C++20 zu verwenden, möchte ich hier nochmal die Werbetrommel für std::format rühren. Damit sind alle Gründe für printf und selbstgestrickte Lösungen endgültig obsolet. Für ältere C++-Versionen gibt es das auch als header-only-fähige Standalone-Bibliothek: libfmt.

    Edit: LOL... völlig überlesen:

    @Hawaiihemd sagte in Variable Anzahl von Parametern - Beispielcode klappt nicht:

    Kein weiterer Sinn dahinter, reine Übung.

    Nix für ungut. std::formatkann nie zu viel Liebe bekommen 🙂



  • Mensch, ihr seid echt hammer 🙂
    Nicht nur, dass ihr auf meine Fragen extrem kompetent antwortet, ihr nehmt Euch auch noch Zeit für Alternativcodes und erklärt sie sogar. SO lerne ich definitiv eine Menge! Vielen lieben Dank Euch!!!


Anmelden zum Antworten