Ausgabe von Strings



  • Hi!

    Ich versuche, in einem bestehendem Programm (nicht von mir) Strings auszugeben - ganz normal auf der Konsole.

    Leider fliegt es mir um die Ohren - und ich habe absolut keine Ahnung, was hier schief geht.

    Ich habe folgende Methode, in der ich das printf untergebracht habe:

    const ResourceType *TechTree::getResourceType(const string &name) const{
    	for(int i=0; i<resourceTypes.size(); ++i){
    		if(resourceTypes[i].getName()==name){
    			printf("%s", resourceTypes[i].getName()); // <---- HIER
    			return &resourceTypes[i];
    		}
    	}
    	throw runtime_error("Resource Type not found: "+name);
    }
    

    Das Ganze stirbt ganz furchtbar - auf der Konsole steht ein Haufen Müll und danach stirbt die Anwendung. Schaue ich mir die Methode an, so sieht sie so aus:

    string getName() const	{return name;}
    

    Ich hatte den Verdacht, dass string vllt. irgendwie kein normaler String ist, aber wenn ich mir die Definition anschaue, dann finde ich da

    typedef basic_string<char, char_traits<char>, allocator<char> >
    	string;
    

    im Namespace std in der Datei xstring - ich würde also davon ausgehen, dass das ein normaler String ist.

    Jetzt bin ich ratlos.

    Was mache ich falsch?

    Liebe Grüße,
    fkerber

    P.S.
    Crossposting: http://www.delphipraxis.net/153237-ausgabe-von-strings.html



  • Du benutzt printf. Das ist ein Relikt aus C und hat keinerlei Bewusstsein über Objekte wie std::string eins ist.

    Benutze den ostream cout.

    cout << resourceTypes[i].getName();
    


  • Hi!

    Ah, perfekt. Das klappt.

    Jetzt habe ich nur ein anderes Problem 😞
    Die Ausgabe auf der Konsole war lediglich eine Verzweiflungstat, da dass, was ich eigentlich will, nicht funktioniert hat.

    Mein eigentlicher Plan war, dass ich eine Methode habe, die Argumente wie printf entgegen nimmt - also einen Formatstring und beliebige varArgs.
    Diese wollte ich dann mit vsprintf in einen String packen und dann weiterverarbeiten. Das hat (wohl aus den gleichen Gründen) nicht geklappt.

    Jetzt stellt sich mir die Frage, wie kann ich das lösen? Also so, dass diese Funktion auch mit Objekten wie std::string klar kommt.

    Liebe Grüße,
    fkerber


  • Mod

    fkerber schrieb:

    Hi!

    Ah, perfekt. Das klappt.

    Jetzt habe ich nur ein anderes Problem 😞
    Die Ausgabe auf der Konsole war lediglich eine Verzweiflungstat, da dass, was ich eigentlich will, nicht funktioniert hat.

    Mein eigentlicher Plan war, dass ich eine Methode habe, die Argumente wie printf entgegen nimmt - also einen Formatstring und beliebige varArgs.
    Diese wollte ich dann mit vsprintf in einen String packen und dann weiterverarbeiten. Das hat (wohl aus den gleichen Gründen) nicht geklappt.

    Jetzt stellt sich mir die Frage, wie kann ich das lösen? Also so, dass diese Funktion auch mit Objekten wie std::string klar kommt.

    Liebe Grüße,
    fkerber

    Entweder mit varArgs wie du es vor hattest (an sich spricht nichts gegen die Mischung von varArgs mit std::strings, man muss es nur programmieren. Und bei printf ist dies nicht geschehen) oder du lässt dich von den Streams inspirieren und schreibst eine Klasse mit einem internen Zustand, welche dann wie die Streams benutzbar wäre (die ja auch mit beliebig vielen Argumenten gefüttert werden können, bevor die eigentliche Ausgabe erfolgt).



  • SeppJ schrieb:

    an sich spricht nichts gegen die Mischung von varArgs mit std::strings, man muss es nur programmieren

    Doch, undefiniertes Verhalten bei VarArgs in Verbindung mit Nicht-POD-Objekten spricht deutlich dagegen.

    SeppJ schrieb:

    oder du lässt dich von den Streams inspirieren und schreibst eine Klasse mit einem internen Zustand, welche dann wie die Streams benutzbar wäre (die ja auch mit beliebig vielen Argumenten gefüttert werden können, bevor die eigentliche Ausgabe erfolgt).

    Finde ich viel die bessere Idee. Wenn einem der Formatstring gefällt: Das geht auch typsicher, zum Beispiel mit Boost.Format.



  • Hi!

    Danke für die schnelle Antwort - aber ich glaube, ich verstehe sie nicht.
    Vllt. habe ich auch schlecht erklärt, was ich brauche - ich versuche es noch einmal.

    Meine Idealvorstellung ist eine Funktion, in die ich beliebige Werte (und sowas wie einen Formatstring) reinstecken kann und raus kommt ein schöner String.

    Das hat in der jetzigen Form auch ganz gut funktioniert - bis auf die Strings eben.

    Also momentan sieht das so aus:

    void test(const char *fmt, ...) {
    
    	char buf[1024];
    	memset(buf, 0, sizeof(char) * 1024);
    	va_list VarArgs;
    	va_start(VarArgs, fmt);
    	int len = vsprintf(buf, fmt, VarArgs);
    	va_end(VarArgs);
    	// weiter mit buf arbeiten.
    }
    

    Der Plan war an sich, dass ich da jetzt eben auch sowas reinstecken könnte:

    test("%s - value %d", resourceTypes[i].getName(), 4711);
    

    Das ging genauso schief, wie die Konsolenausgabe - wohl aus dem selben Grund.
    Daher wäre meine Frage, wie ich das jetzt umbauen kann, dass es hier auch klappt - mit allem außer String scheint es zu klappen.

    Liebe Grüße,
    fkerber


  • Mod

    Nexus schrieb:

    SeppJ schrieb:

    an sich spricht nichts gegen die Mischung von varArgs mit std::strings, man muss es nur programmieren

    Doch, undefiniertes Verhalten bei VarArgs in Verbindung mit Nicht-POD-Objekten spricht deutlich dagegen.

    Das ist undefiniert? Wusste ich noch gar nicht, aber macht Sinn, wenn man drüber nachdenkt. Dann ist dies allerdings ein Grund die Methode zu meiden.


  • Mod

    fkerber schrieb:

    Das ging genauso schief, wie die Konsolenausgabe - wohl aus dem selben Grund.
    Daher wäre meine Frage, wie ich das jetzt umbauen kann, dass es hier auch klappt - mit allem außer String scheint es zu klappen.

    Du könntest dich natürlich auf C-Strings beschränken (std::string hat eine Methode c_str() zur Umwandlung), damit sollte das klappen. Aber sei dir bewusst, dass du dies in Zukunft nur schwer auf andere Objekttypen erweitern kannst.



  • Versuchs doch mal so

    [cpp]test("%s - value %d", resourceTypes[i].getName().c_str(), 4711); [/cpp]

    Die c_str()-Funktion der Stringklasse liefert den Inhalt des Strings als const char * zurück. Das sollte mit %s kompatibel sein.



  • Das Ganze geht schief, weil printf für %s einen char* erwartet - und einen string erhält. Da die Ellipsen nicht typsicher sind, kann der Fehler nicht vom Compiler erkannt werden. Das ganze ginge mit resourceTypes[i].getName().c_str() , aber aus genannten Gründen versucht man in C++ Ellipsen eher zu vermeiden, sondern löst das in der Regel mit Operatorenüberladung.

    So geschehen bei den Streams:

    stringstream s;
    s << resourceTypes[i].getName() << " - value " << 4711;
    // weiterarbeiten mit s.str()
    

    und bei Boost.Format:

    format f("%s - value %d");
    f % resourceTypes[i].getName() % 4711;
    // weiterarbeiten mit f.str()
    

    Vielleicht solltest du dich auch eher daran orientieren, anstatt mit einer Ellipse zu arbeiten.



  • Hi,

    hmm, ja - das sieht soweit gut aus, aber 😉

    Ich verstehe noch nicht, wie ich es nutzen kann. Ich brauche doch an sich die variable Argumentliste, damit ich beliebig viele Argumente reinstecken kann. Um nun an die einzelnen Elemente dran zu kommen, müsste ich sie ja mit va_arg auslesen, wofür ich den Typ wissen müsste - ich müsste also sowas wie printf nachbauen - das meintet ihr ja sicher nicht 😉

    Wo liegt mein Denkfehler?

    Liebe Grüße,
    fkerber


  • Mod

    fkerber schrieb:

    Wo liegt mein Denkfehler?

    Du denkst C.

    In C++:
    1. Kann man Operatoren überladen
    2. Objekte können einen inneren Zustand haben (ok, können C Funktionen streng genommen auch, aber es ist ungleich umständlicher, da sie global sind)

    Daher kannst du erstmal ein Objekt mit Daten füttern und danach die eigentliche Funktionalität auf den gesammelten Daten ausführen.

    Beispiel der ostream cout: Erstmal bekommt er jede Menge Daten zugeschaufelt. Diese schreibt er in einen internen Puffer. Erst wenn er den Manipulator flush übergeben bekommt (oder der Puffer voll ist), werden die Daten tatsächlich nach stdout geschrieben. Selbst Formatierung ist auf diese Weise möglich, weil sich der Stream die erhaltenen Manipulatoren merkt und folgende Eingaben dann entsprechend anders behandelt.



  • Hallo,

    ja, das ist mir vollkommen klar.

    Aber das hieße in der Konsequenz doch dann folgendes:

    Ich brauche ein Objekt, mit dem ich was veranstalten kann - soweit so gut.
    Aber dann braucht ich doch auf diesem Objekt auch Methoden, nennen wir sie mal add, die ich für alle möglichen Argumenttypen überladen muss, dann intern diese Eingaben speichern und dann das irgendwie mit meinem Formatstring matchen?

    Das erscheint mir sehr viel Aufwand für so ein einfaches Vorhaben, auch in C++ formatierte Ausgaben zu haben, wie es sie in C ja frei Haus gibt.

    Liebe Grüße,
    fkerber


  • Mod

    fkerber schrieb:

    Hallo,

    ja, das ist mir vollkommen klar.

    Aber das hieße in der Konsequenz doch dann folgendes:

    Ich brauche ein Objekt, mit dem ich was veranstalten kann - soweit so gut.
    Aber dann braucht ich doch auf diesem Objekt auch Methoden, nennen wir sie mal add, die ich für alle möglichen Argumenttypen überladen muss, dann intern diese Eingaben speichern und dann das irgendwie mit meinem Formatstring matchen?

    Jupp.

    Das erscheint mir sehr viel Aufwand für so ein einfaches Vorhaben, auch in C++ formatierte Ausgaben zu haben, wie es sie in C ja frei Haus gibt.

    😕 Die istreams sind ja auch formatiert und leicht überladbar. Außerdem wurde dir ja schon boost::format genannt, falls du etwas machen möchtest, was über istreams hinausgeht, aber nicht alles selber programmieren willst.

    Denk dran, dass du auch bei der printf Methode für sämtliche möglichen Datentypen Code zur Verfügung stellen musst. Deshalb klappt das ja auch mit std::string nicht, da printf keinen Code für std::string enthält (und aus dem anderen genannten Grund, dass std::string kein POD ist, aber genauso könnte man fragen, warum printf nicht eine saubere Wertetabelle ausgibt, wenn man beispielsweise ein int Array übergibt. Eben weil kein Code dafür da ist.)



  • Hi,

    ok, alles klar.
    Dann schaue ich mich mal in der Richtung um.

    Liebe Grüße,
    fkerber



  • Möchte hier nur kurz auf Wikipedia hinweisen:

    In C++ gelten Ellipsen jedoch bei einigen Leuten als veraltet — da C++ Möglichkeiten der Funktionsüberladung und Defaultargumente besitzt — und als schlechter Stil, da sie keine Typsicherheit bieten. Sie sind jedoch die einzige Möglichkeit, eine nahezu unbegrenzte Anzahl an Argumenten zu übergeben.



  • Sie sind jedoch die einzige Möglichkeit, eine nahezu unbegrenzte Anzahl an Argumenten zu übergeben.

    Ich würde das nicht so eng sehen und nur als Übergabe akzeptieren, was von zwei Klammern begrenzt und durch Kommas getrennt ist. Es reicht, sich das printf() -Äquivalent in C++ anzuschauen:

    std::cout << "Hallo " << 47 << '.' << "Welt!" << std::endl;
    

    Container, Ranges, Iteratoren stellen auch eine Möglichkeit dar (sind aber auf einen statischen Typen beschränkt). In C++0x wird es zudem std::initializer_list und Variadic-Templates geben. Mit letzteren kann die Ellipse aus C typsicher implementiert werden.


Anmelden zum Antworten