printf mit std :: string?

157

Ich verstehe, dass dies stringein Mitglied des stdNamespace ist. Warum tritt also Folgendes auf?

#include <iostream>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString);
    cin.get();

    return 0;
}

Geben Sie hier die Bildbeschreibung ein

Jedes Mal, wenn das Programm ausgeführt wird, wird myStringeine scheinbar zufällige Zeichenfolge mit 3 Zeichen gedruckt, wie in der obigen Ausgabe.

TheDarkIn1978
quelle
8
Nur um Sie wissen zu lassen, kritisieren viele Leute dieses Buch. Was ich verstehen kann, weil objektorientierte Programmierung nicht viel zu bieten hat, aber ich denke nicht, dass es so schlimm ist, wie die Leute behaupten.
Jesse Good
ouf! Nun, es ist gut, dies zu berücksichtigen, während ich mich durch das Buch bewege. Ich bin mir sicher, dass es nicht das einzige C ++ - Buch sein wird, das ich im Laufe des nächsten Jahres oder so lesen werde, also hoffe ich, dass es nicht zu viel Schaden anrichtet :)
TheDarkIn1978
Die Verwendung der höchsten Compiler-Warnung würde Ihre Frage beantworten - beim Kompilieren mit gcc. Wie MSVC damit umgeht - ich weiß es nicht.
Peter VARGA

Antworten:

236

Es wird kompiliert, weil printfes nicht typsicher ist, da es variable Argumente im C-Sinne 1 verwendet . printfhat keine Option für std::string, nur eine Zeichenfolge im C-Stil. Wenn Sie etwas anderes anstelle dessen verwenden, was erwartet wird, erhalten Sie definitiv nicht die gewünschten Ergebnisse. Es ist eigentlich undefiniertes Verhalten, also könnte überhaupt alles passieren.

Der einfachste Weg, dies zu beheben, da Sie C ++ verwenden, besteht darin, es normal zu drucken std::cout, da dies std::stringdurch Überladen von Operatoren unterstützt wird:

std::cout << "Follow this command: " << myString;

Wenn Sie aus irgendeinem Grund die Zeichenfolge im C-Stil extrahieren müssen, können Sie die c_str()Methode verwenden std::string, um eine Zeichenfolge zu erhalten const char *, die nullterminiert ist. Anhand Ihres Beispiels:

#include <iostream>
#include <string>
#include <stdio.h>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString.c_str()); //note the use of c_str
    cin.get();

    return 0;
}

Wenn Sie eine Funktion wünschen, die ähnlich ist printf, aber sicher eingibt, schauen Sie sich verschiedene Vorlagen an (C ++ 11, unterstützt von allen wichtigen Compilern ab MSVC12). Ein Beispiel dafür finden Sie hier . Es gibt nichts, von dem ich weiß, dass es so in der Standardbibliothek implementiert ist, aber es könnte speziell in Boost vorhanden sein boost::format.


[1]: Dies bedeutet, dass Sie eine beliebige Anzahl von Argumenten übergeben können. Die Funktion ist jedoch darauf angewiesen, dass Sie die Anzahl und den Typ dieser Argumente angeben. Im Fall von printfbedeutet dies eine Zeichenfolge mit codierten Typinformationen wie %dBedeutung int. Wenn Sie über den Typ oder die Nummer lügen, hat die Funktion keine Standardmethode, obwohl einige Compiler die Möglichkeit haben, zu überprüfen und Warnungen zu geben, wenn Sie lügen.

chris
quelle
@MooingDuck, guter Punkt. Es ist in Jerrys Antwort, aber als akzeptierte Antwort sehen die Leute dies und sie könnten gehen, bevor sie die anderen sehen. Ich habe diese Option hinzugefügt, um die erste und die empfohlene Lösung zu sein.
Chris
43

Bitte nicht benutzen printf("%s", your_string.c_str());

Verwenden Sie cout << your_string;stattdessen. Kurz, einfach und typsicher. Wenn Sie C ++ schreiben, möchten Sie dies im Allgemeinen printfvollständig vermeiden - es ist ein Überbleibsel von C, das in C ++ selten benötigt oder nützlich ist.

Es gibt zahlreiche Gründe , warum Sie coutstattdessen verwenden sollten printf. Hier eine Auswahl einiger der offensichtlichsten:

  1. Wie die Frage zeigt, printfist nicht typsicher. Wenn der von Ihnen übergebene Typ von dem im Konvertierungsspezifizierer angegebenen Typ abweicht,printf wird versucht, alles, was auf dem Stapel gefunden wird, so zu verwenden, als wäre es der angegebene Typ, was zu einem undefinierten Verhalten führt. Einige Compiler können unter bestimmten Umständen davor warnen, aber einige Compiler können / werden überhaupt nicht und keiner kann unter allen Umständen.
  2. printfist nicht erweiterbar. Sie können nur primitive Typen übergeben. Der Satz von Conversion-Spezifizierern, den er versteht, ist in seiner Implementierung fest codiert, und Sie können keine weiteren / anderen hinzufügen. Die meisten gut geschriebenen C ++ - Programme sollten diese Typen hauptsächlich verwenden, um Typen zu implementieren, die auf das zu lösende Problem ausgerichtet sind.
  3. Dies erschwert eine anständige Formatierung erheblich. Als offensichtliches Beispiel möchten Sie beim Drucken von Zahlen zum Lesen normalerweise alle paar Ziffern Tausende Trennzeichen einfügen. Die genaue Anzahl der Ziffern und die als Trennzeichen verwendeten Zeichen variieren, werden aber coutauch abgedeckt. Beispielsweise:

    std::locale loc("");
    std::cout.imbue(loc);
    
    std::cout << 123456.78;

    Das namenlose Gebietsschema (das "") wählt ein Gebietsschema basierend auf der Konfiguration des Benutzers aus. Daher wird dies auf meinem Computer (konfiguriert für US-Englisch) als gedruckt 123,456.78. Für jemanden, der seinen Computer für (sagen wir) Deutschland konfiguriert hat, würde er so etwas wie ausdrucken 123.456,78. Für jemanden, der es für Indien konfiguriert hat, würde es als 1,23,456.78(und natürlich gibt es viele andere) ausgedruckt. Mit printfbekomme ich genau ein Ergebnis : 123456.78. Es ist konsequent, aber es ist konsequent für alle überall falsch . Im Wesentlichen besteht die einzige Möglichkeit, dies zu umgehen, darin, die Formatierung separat durchzuführen und das Ergebnis dann als Zeichenfolge an zu übergeben printf, da der Job printfselbst einfach nicht korrekt ausgeführt wird.

  4. Obwohl sie recht kompakt sind, printfkönnen Formatzeichenfolgen ziemlich unlesbar sein. Selbst unter C-Programmierern, die printfpraktisch jeden Tag arbeiten, müssten mindestens 99% nachschlagen, um sicherzugehen, was das #In %#xbedeutet und wie sich das von dem unterscheidet, was das #In %#fbedeutet (und ja, sie bedeuten ganz andere Dinge ).
Jerry Sarg
quelle
11
@ TheDarkIn1978: Du hast es wahrscheinlich vergessen #include <string>. VC ++ hat einige Kuriositäten in seinen Headern, mit denen Sie einen String definieren, aber nicht an ihn senden können cout, ohne den <string>Header einzuschließen.
Jerry Coffin
28
@ Jerry: Ich möchte nur darauf hinweisen, dass die Verwendung von printf VIEL schneller ist als die Verwendung von cout beim Umgang mit großen Datenmengen. Also bitte nicht sagen, dass es nutzlos ist: D
Programmierer
7
@Programmer: siehe stackoverflow.com/questions/12044357/… . Zusammenfassung: Meistens coutist das langsamer, weil Sie dort verwendet haben, std::endlwo Sie es nicht sollten.
Jerry Coffin
29
Typische Arroganz von C ++ - Experten. Wenn printf existiert, warum nicht?
Kuroi Neko
6
OK, Entschuldigung für den bissigen Kommentar. Trotzdem ist printf ziemlich praktisch zum Debuggen und die Streams sind zwar weitaus leistungsfähiger, haben jedoch den Nachteil, dass der Code keine Vorstellung von der tatsächlichen Ausgabe gibt. Für formatierte Ausgaben ist printf immer noch eine praktikable Alternative, und es ist eine Schande, dass beide Systeme nicht besser zusammenarbeiten können. Nur meine Meinung natürlich.
Kuroi Neko
28

Verwenden myString.c_str()Sie diese Option, wenn eine c-ähnliche Zeichenfolge ( const char*) mit printf verwendet werden soll

Vielen Dank

Alessandro Pezzato
quelle
6

Verwenden Sie das Beispiel std :: printf und c_str ():

std::printf("Follow this command: %s", myString.c_str());
Adel Ben Hamadi
quelle
1

Der Hauptgrund ist wahrscheinlich, dass eine C ++ - Zeichenfolge eine Struktur ist, die einen Wert für die aktuelle Länge enthält, nicht nur die Adresse einer Zeichenfolge, die mit einem 0-Byte abgeschlossen ist. Printf und seine Verwandten erwarten, eine solche Sequenz zu finden, keine Struktur, und werden daher durch C ++ - Zeichenfolgen verwirrt.

Wenn ich für mich selbst spreche, glaube ich, dass printf einen Platz hat, der nicht einfach mit syntaktischen C ++ - Funktionen gefüllt werden kann, genauso wie Tabellenstrukturen in HTML einen Platz haben, der nicht einfach mit divs gefüllt werden kann. Wie Dykstra später über das goto schrieb, hatte er nicht die Absicht, eine Religion zu gründen, und argumentierte wirklich nur dagegen, sie als Kludge zu verwenden, um schlecht gestalteten Code auszugleichen.

Es wäre sehr schön, wenn das GNU-Projekt die printf-Familie zu ihren g ++ - Erweiterungen hinzufügen würde.

MMacD
quelle
1

Printf ist eigentlich ziemlich gut zu verwenden, wenn es auf die Größe ankommt. Das heißt, wenn Sie ein Programm ausführen, bei dem der Speicher ein Problem darstellt, ist printf tatsächlich eine sehr gute und unterbewertete Lösung. Cout verschiebt im Wesentlichen Bits, um Platz für die Zeichenfolge zu schaffen, während printf nur einige Parameter aufnimmt und diese auf dem Bildschirm druckt. Wenn Sie ein einfaches Hallo-Welt-Programm kompilieren würden, wäre printf in der Lage, es in weniger als 60.000 Bit zu kompilieren, im Gegensatz zu cout. Das Kompilieren würde über 1 Million Bit erfordern.

Für Ihre Situation empfehlen wir die Verwendung von cout, einfach weil es viel bequemer zu verwenden ist. Obwohl ich argumentieren würde, dass printf etwas Gutes zu wissen ist.

Howard Howard
quelle
1

printfakzeptiert eine variable Anzahl von Argumenten. Diese können nur POD-Typen (Plain Old Data) haben. Code, der alles andere als POD printfnur zum Kompilieren übergibt, da der Compiler davon ausgeht, dass Sie das richtige Format haben. %sbedeutet, dass das jeweilige Argument ein Zeiger auf a sein soll char. In deinem Fall ist es ein std::stringnicht const char*. printfweiß es nicht, weil der Argumenttyp verloren geht und aus dem Formatparameter wiederhergestellt werden soll. Wenn Sie dieses std::stringArgument in const char*den resultierenden Zeiger umwandeln, verweist es auf einen irrelevanten Speicherbereich anstelle der gewünschten C-Zeichenfolge. Aus diesem Grund druckt Ihr Code Kauderwelsch aus.

Dies printfist zwar eine ausgezeichnete Wahl zum Ausdrucken von formatiertem Text (insbesondere, wenn Sie eine Auffüllung beabsichtigen), kann jedoch gefährlich sein, wenn Sie keine Compiler-Warnungen aktiviert haben. Aktivieren Sie immer Warnungen, da solche Fehler leicht vermieden werden können. Es gibt keinen Grund, den ungeschickten std::coutMechanismus zu verwenden, wenn die printfFamilie dieselbe Aufgabe viel schneller und schöner erledigen kann. Stellen Sie einfach sicher, dass Sie alle Warnungen ( -Wall -Wextra) aktiviert haben und Sie werden gut sein. Wenn Sie Ihre eigene benutzerdefinierte printfImplementierung verwenden, sollten Sie diese mit dem __attribute__Mechanismus deklarieren, mit dem der Compiler die Formatzeichenfolge anhand der angegebenen Parameter überprüfen kann .

Hyäne
quelle