'printf' vs. 'cout' in C ++


Antworten:


333

Ich bin überrascht, dass jeder in dieser Frage behauptet, das std::coutsei viel besser als printf, auch wenn die Frage nur nach Unterschieden gefragt hat. Jetzt gibt es einen Unterschied - std::coutist C ++ und printfist C (Sie können es jedoch in C ++ verwenden, genau wie fast alles andere von C). Jetzt werde ich hier ehrlich sein; beide printfund std::couthaben ihre Vorteile.

Wirkliche Unterschiede

Erweiterbarkeit

std::coutist erweiterbar. Ich weiß, dass die Leute sagen werden, dass dies auch printferweiterbar ist, aber eine solche Erweiterung wird im C-Standard nicht erwähnt (Sie müssten also nicht standardmäßige Funktionen verwenden - aber es gibt nicht einmal übliche nicht standardmäßige Funktionen), und solche Erweiterungen bestehen aus einem Buchstaben (so ist es leicht, mit einem bereits vorhandenen Format in Konflikt zu geraten).

Im Gegensatz zu printf, std::coutvollständig auf Operatorüberladung abhängt, so ist es kein Problem mit benutzerdefinierten Formaten - alles , was Sie tun , ist ein Unterprogramm Mitnahmen definieren std::ostreamals erstes Argument und Ihre Art als zweites. Daher gibt es keine Namespace-Probleme. Solange Sie eine Klasse haben (die nicht auf ein Zeichen beschränkt ist), können Sie arbeitenstd::ostream Überladung kommen.

Ich bezweifle jedoch, dass viele Leute erweitern möchten ostream(um ehrlich zu sein, habe ich solche Erweiterungen selten gesehen, auch wenn sie einfach zu erstellen sind). Es ist jedoch hier, wenn Sie es brauchen.

Syntax

Wie leicht zu erkennen war, verwenden beide printfund std::coutunterschiedliche Syntax. printfVerwendet die Standardfunktionssyntax unter Verwendung von Musterzeichenfolgen und Argumentlisten mit variabler Länge. Tatsächlich printfist dies ein Grund, warum C sie hat - printfFormate sind zu komplex, um ohne sie verwendet werden zu können. Verwendet std::coutjedoch eine andere API - dieoperator << API, die sich selbst zurückgibt.

Im Allgemeinen bedeutet dies, dass die C-Version kürzer ist, aber in den meisten Fällen spielt dies keine Rolle. Der Unterschied macht sich bemerkbar, wenn Sie viele Argumente drucken. Wenn Sie unter der Error 2: File not found.Annahme einer Fehlernummer etwas schreiben müssen und die Beschreibung einen Platzhalter enthält, sieht der Code folgendermaßen aus. Beide Beispiele funktionieren identisch (nun, irgendwie wird std::endlder Puffer tatsächlich geleert).

printf("Error %d: %s.\n", id, errors[id]);
std::cout << "Error " << id << ": " << errors[id] << "." << std::endl;

Dies scheint zwar nicht zu verrückt zu sein (es ist nur zweimal länger), aber die Dinge werden verrückter, wenn Sie Argumente tatsächlich formatieren, anstatt sie nur zu drucken. Zum Beispiel ist das Drucken von so etwas 0x0424einfach verrückt. Dies wird durch den std::coutMischzustand und die tatsächlichen Werte verursacht. Ich habe nie eine Sprache gesehen, in der so etwas wie std::setfillein Typ wäre (außer natürlich C ++). printftrennt Argumente und tatsächlichen Typ klar voneinander. Ich würde es wirklich vorziehen, die printfVersion davon beizubehalten (auch wenn sie irgendwie kryptisch aussieht) im Vergleich zur iostreamVersion davon (da sie zu viel Rauschen enthält).

printf("0x%04x\n", 0x424);
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(4) << 0x424 << std::endl;

Übersetzung

Hier liegt der wahre Vorteil von printf. Die printfFormatzeichenfolge ist gut ... eine Zeichenfolge. Das macht es wirklich einfach zu übersetzen, verglichen mit operator <<Missbrauch von iostream. Angenommen, die gettext()Funktion übersetzt Error 2: File not found.den Code, um die Übersetzung der zuvor angezeigten Formatzeichenfolge zu erhalten , und Sie möchten ihn anzeigen , sieht folgendermaßen aus:

printf(gettext("Error %d: %s.\n"), id, errors[id]);

Nehmen wir nun an, wir übersetzen in Fictionish, wobei die Fehlernummer nach der Beschreibung steht. Die übersetzte Zeichenfolge würde so aussehen %2$s oru %1$d.\n. Wie geht das in C ++? Nun, ich habe keine Ahnung. Ich denke, Sie können Fälschungen machen, an iostreamdie Sie Konstrukte weitergeben printfkönnen gettext, oder etwas anderes, um sie zu übersetzen. Natürlich $ist es kein C-Standard, aber es ist so häufig, dass es meiner Meinung nach sicher ist.

Keine bestimmte Ganzzahl-Syntax merken / nachschlagen müssen

C hat viele ganzzahlige Typen, ebenso wie C ++. std::coutbehandelt alle Typen für Sie, printferfordert jedoch abhängig von einem Integer-Typ eine bestimmte Syntax (es gibt Nicht-Integer-Typen, aber der einzige Nicht-Integer-Typ, mit dem Sie in der Praxis arbeiten, printfist const char *(C-Zeichenfolge, kann mit der to_cMethode von erhalten werden std::string)). Zum Drucken size_tmüssen Sie beispielsweise verwenden %zd, während int64_tdie Verwendung erforderlich ist %"PRId64". Die Tabellen sind unter http://en.cppreference.com/w/cpp/io/c/fprintf und http://en.cppreference.com/w/cpp/types/integer verfügbar .

Sie können das NUL-Byte nicht drucken. \0

Da printfim Gegensatz zu C ++ - Zeichenfolgen C-Zeichenfolgen verwendet werden, kann kein NUL-Byte ohne bestimmte Tricks gedruckt werden. In bestimmten Fällen ist die Verwendung %cmit möglich'\0' als Argument zu verwenden, obwohl dies eindeutig ein Hack ist.

Unterschiede, die niemanden interessieren

Performance

Update: Es stellt sich heraus, dass iostreames so langsam ist, dass es normalerweise langsamer als Ihre Festplatte ist (wenn Sie Ihr Programm in eine Datei umleiten). Das Deaktivieren der Synchronisierung mit stdiokann hilfreich sein, wenn Sie viele Daten ausgeben müssen. Wenn die Leistung ein echtes Problem darstellt (im Gegensatz zum Schreiben mehrerer Zeilen in STDOUT), verwenden Sie einfach printf.

Jeder denkt, dass ihm die Leistung am Herzen liegt, aber niemand stört sich daran, sie zu messen. Meine Antwort ist, dass E / A sowieso ein Engpass ist, egal ob Sie printfoder verwenden iostream. Ich denke, das printf könnte von einem kurzen Blick in die Assembly schneller sein (kompiliert mit clang unter Verwendung der -O3Compiler-Option). Unter der Annahme meines Fehlerbeispiels führt das printfBeispiel weit weniger Anrufe aus als das coutBeispiel. Dies ist int mainmit printf:

main:                                   @ @main
@ BB#0:
        push    {lr}
        ldr     r0, .LCPI0_0
        ldr     r2, .LCPI0_1
        mov     r1, #2
        bl      printf
        mov     r0, #0
        pop     {lr}
        mov     pc, lr
        .align  2
@ BB#1:

Sie können leicht feststellen, dass zwei Zeichenfolgen und 2(Zahl) als printfArgumente verwendet werden. Das ist alles; es gibt nichts anderes. Zum Vergleich wird dies iostreamzur Baugruppe kompiliert. Nein, es gibt kein Inlining. Jeder einzelne operator <<Aufruf bedeutet einen weiteren Aufruf mit einem anderen Satz von Argumenten.

main:                                   @ @main
@ BB#0:
        push    {r4, r5, lr}
        ldr     r4, .LCPI0_0
        ldr     r1, .LCPI0_1
        mov     r2, #6
        mov     r3, #0
        mov     r0, r4
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        mov     r0, r4
        mov     r1, #2
        bl      _ZNSolsEi
        ldr     r1, .LCPI0_2
        mov     r2, #2
        mov     r3, #0
        mov     r4, r0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r1, .LCPI0_3
        mov     r0, r4
        mov     r2, #14
        mov     r3, #0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r1, .LCPI0_4
        mov     r0, r4
        mov     r2, #1
        mov     r3, #0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r0, [r4]
        sub     r0, r0, #24
        ldr     r0, [r0]
        add     r0, r0, r4
        ldr     r5, [r0, #240]
        cmp     r5, #0
        beq     .LBB0_5
@ BB#1:                                 @ %_ZSt13__check_facetISt5ctypeIcEERKT_PS3_.exit
        ldrb    r0, [r5, #28]
        cmp     r0, #0
        beq     .LBB0_3
@ BB#2:
        ldrb    r0, [r5, #39]
        b       .LBB0_4
.LBB0_3:
        mov     r0, r5
        bl      _ZNKSt5ctypeIcE13_M_widen_initEv
        ldr     r0, [r5]
        mov     r1, #10
        ldr     r2, [r0, #24]
        mov     r0, r5
        mov     lr, pc
        mov     pc, r2
.LBB0_4:                                @ %_ZNKSt5ctypeIcE5widenEc.exit
        lsl     r0, r0, #24
        asr     r1, r0, #24
        mov     r0, r4
        bl      _ZNSo3putEc
        bl      _ZNSo5flushEv
        mov     r0, #0
        pop     {r4, r5, lr}
        mov     pc, lr
.LBB0_5:
        bl      _ZSt16__throw_bad_castv
        .align  2
@ BB#6:

Um ehrlich zu sein, bedeutet dies jedoch nichts, da E / A ohnehin der Engpass ist. Ich wollte nur zeigen, dass iostreamdas nicht schneller ist, weil es "typsicher" ist. Die meisten C-Implementierungen implementieren printfFormate mit berechnetem goto, so dass dies printfso schnell wie möglich ist, auch ohne dass der Compiler sich dessen bewusst ist printf(nicht, dass dies nicht der printfFall ist - einige Compiler können dies in bestimmten Fällen optimieren). Konstante Zeichenfolgen, die mit enden, werden \nnormalerweise auf optimiertputs ). .

Erbe

Ich weiß nicht, warum Sie erben möchten ostream, aber es ist mir egal. Das ist auch mit möglich FILE.

class MyFile : public FILE {}

Typensicherheit

Richtig, Argumentlisten mit variabler Länge haben keine Sicherheit, aber das spielt keine Rolle, da gängige C-Compiler Probleme mit printfFormatzeichenfolgen erkennen können, wenn Sie Warnungen aktivieren. Tatsächlich kann Clang dies tun, ohne Warnungen zu aktivieren.

$ cat safety.c

#include <stdio.h>

int main(void) {
    printf("String: %s\n", 42);
    return 0;
}

$ clang safety.c

safety.c:4:28: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
    printf("String: %s\n", 42);
                    ~~     ^~
                    %d
1 warning generated.
$ gcc -Wall safety.c
safety.c: In function main’:
safety.c:4:5: warning: format ‘%s expects argument of type char *’, but argument 2 has type int [-Wformat=]
     printf("String: %s\n", 42);
     ^

18
Sie sagen, I / O ist sowieso der Engpass. Offensichtlich haben Sie diese Annahme nie getestet. Ich zitiere mich selbst: "Andererseits kann die iostreams-Version mit 75,3 MB / s Daten nicht schnell genug puffern, um mit einer Festplatte Schritt zu halten. Das ist schlecht und erledigt noch nicht einmal echte Arbeit. Ich mache keine Ich glaube nicht, dass ich zu hohe Erwartungen habe, wenn ich sage, dass meine E / A-Bibliothek meinen Festplattencontroller überlasten kann. "
Ben Voigt

4
@ BenVoigt: Ich gebe zu, ich versuche C ++ wenn möglich zu vermeiden. Ich habe viel versucht, es zu verwenden, aber es war ärgerlicher und weniger wartbar als andere Programmiersprachen, die ich verwendet habe. Dies ist ein weiterer Grund für mich, C ++ zu vermeiden - dies ist nicht einmal schnell (es ist nicht einmal iostream) - die gesamte C ++ - Bibliothek ist in den meisten Implementierungen langsam, vielleicht mit Ausnahme von std::sort, was im Vergleich zu qsort(2-mal) irgendwie überraschend schnell ist Kosten der ausführbaren Größe).
Konrad Borowski

3
Niemand hier hat Probleme in der parallelen Umgebung bei der Verwendung von cout erwähnt.
Nicholas Hamilton

9
Ihr Leistungsargument macht überhaupt keinen Sinn. Mehr Assemblierung in Ihrem Programm bedeutet nicht, dass das Programm langsamer ist, da Sie nicht den gesamten Code berücksichtigen, der die Funktion printf ausmacht, was viel Code ist. Meiner Meinung nach ist es möglich, cout mit dem Operator << viel besser als mit printf zu optimieren, da der Compiler Variablen und Formatierungen besser verstehen kann.
Ignas2526

18
Ich mag viele Dinge an dieser Antwort, aber vielleicht ist mein Lieblingsteil "Jeder denkt, dass ihm die Leistung am Herzen liegt, aber niemand stört sich daran, sie zu messen."
Kyle Strand

203

Aus den C ++ - FAQ :

[15.1] Warum sollte ich <iostream> anstelle des traditionellen verwenden <cstdio>?

Erhöhen Sie die Typensicherheit, reduzieren Sie Fehler, ermöglichen Sie Erweiterbarkeit und sorgen Sie für Vererbbarkeit.

printf()ist wohl nicht kaputt und scanf()vielleicht lebensfähig, obwohl es fehleranfällig ist, aber beide sind in Bezug auf die Möglichkeiten von C ++ I / O begrenzt. C ++ I / O (mit <<und >>) ist relativ zu C (mit printf()und scanf()):

  • <iostream>Typensicherer : Mit ist der Typ des Objekts, für das E / A-Vorgänge ausgeführt werden, dem Compiler statisch bekannt. Im Gegensatz dazu <cstdio>werden "%" -Felder verwendet, um die Typen dynamisch zu ermitteln.
  • Weniger fehleranfällig: Mit <iostream>gibt es keine redundanten "%" -Token, die mit den tatsächlichen Objekten übereinstimmen müssen, für die E / A-Vorgänge ausgeführt werden. Durch das Entfernen der Redundanz wird eine Fehlerklasse entfernt.
  • Erweiterbar: Der C ++ - <iostream>Mechanismus ermöglicht die E / A-Bearbeitung neuer benutzerdefinierter Typen, ohne dass vorhandener Code beschädigt wird. Stellen Sie sich das Chaos vor, wenn alle gleichzeitig neue inkompatible "%" -Felder zu printf()und scanf()?! Hinzufügen.
  • Vererbbar: Der C ++ - <iostream>Mechanismus basiert auf realen Klassen wie std::ostreamund std::istream. Im Gegensatz zu <cstdio>'s FILE*sind dies echte Klassen und daher vererbbar. Dies bedeutet, dass Sie andere benutzerdefinierte Dinge haben können, die wie Streams aussehen und sich verhalten, aber dennoch alle seltsamen und wunderbaren Dinge tun, die Sie wollen. Sie können automatisch die zig Millionen Zeilen E / A-Code verwenden, die von Benutzern geschrieben wurden, die Sie nicht einmal kennen, und die nichts über Ihre "Extended Stream" -Klasse wissen müssen.

Auf der anderen Seite printfist es erheblich schneller, was es rechtfertigen kann, es coutin sehr spezifischen und begrenzten Fällen vorzuziehen. Profil immer zuerst. (Siehe zum Beispiel http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)


2
Auf der anderen Seite gibt es die FastFormat-Bibliothek ( fastformat.org ), die gleichzeitig Typensicherheit , Ausdruckskraft und Leistung bietet. (Nicht, dass ich es noch versucht hätte ...)
xtofl

3
@ Marcelo wahrscheinlich, weil es eine gute Zusammenfassung ist, mit allem, was zitiert wird. Die Formatierung ... ja, das ist ziemlich schlecht. Ich hätte das selbst beheben sollen, aber es scheint, dass andere (Sie selbst eingeschlossen) sich darum gekümmert haben, was natürlich konstruktiver ist als nur zu jammern.
Mikeage

2
In letzter Zeit printf()soll auch erweiterbar sein. Siehe "printf hooks" unter udrepper.livejournal.com/20948.html
Maxim Egorushkin

4
@ MaximYegorushkin: Standard printfhat keine solche Fähigkeit. Nicht tragbare Bibliotheksmechanismen sind kaum auf dem Niveau der vollständig standardisierten Erweiterbarkeit von iostreams.
Ben Voigt

4
"Auf der anderen Seite ist printf deutlich schneller" printf ist auch sauberer und einfacher zu bedienen, weshalb ich Cout nach Möglichkeit vermeide.
FluorescentGreen5

43

Die Leute behaupten oft, das printfsei viel schneller. Dies ist größtenteils ein Mythos. Ich habe es gerade mit den folgenden Ergebnissen getestet:

cout with only endl                     1461.310252 ms
cout with only '\n'                      343.080217 ms
printf with only '\n'                     90.295948 ms
cout with string constant and endl      1892.975381 ms
cout with string constant and '\n'       416.123446 ms
printf with string constant and '\n'     472.073070 ms
cout with some stuff and endl           3496.489748 ms
cout with some stuff and '\n'           2638.272046 ms
printf with some stuff and '\n'         2520.318314 ms

Fazit: Wenn Sie nur Zeilenumbrüche wünschen, verwenden Sie printf; Ansonsten coutist es fast genauso schnell oder sogar noch schneller. Weitere Details finden Sie in meinem Blog .

Um klar zu sein, ich versuche nicht zu sagen, dass iostreams immer besser sind als printf; Ich versuche nur zu sagen, dass Sie eine fundierte Entscheidung treffen sollten, die auf realen Daten basiert, und keine wilde Vermutung, die auf einer allgemeinen, irreführenden Annahme basiert.

Update: Hier ist der vollständige Code, den ich zum Testen verwendet habe. Kompiliert mit g++ohne zusätzliche Optionen (außer -lrtfür das Timing).

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

class TimedSection {
    char const *d_name;
    timespec d_start;
    public:
        TimedSection(char const *name) :
            d_name(name)
        {
            clock_gettime(CLOCK_REALTIME, &d_start);
        }
        ~TimedSection() {
            timespec end;
            clock_gettime(CLOCK_REALTIME, &end);
            double duration = 1e3 * (end.tv_sec - d_start.tv_sec) +
                              1e-6 * (end.tv_nsec - d_start.tv_nsec);
            std::cerr << d_name << '\t' << std::fixed << duration << " ms\n"; 
        }
};

int main() {
    const int iters = 10000000;
    char const *text = "01234567890123456789";
    {
        TimedSection s("cout with only endl");
        for (int i = 0; i < iters; ++i)
            std::cout << std::endl;
    }
    {
        TimedSection s("cout with only '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << '\n';
    }
    {
        TimedSection s("printf with only '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("\n");
    }
    {
        TimedSection s("cout with string constant and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789" << std::endl;
    }
    {
        TimedSection s("cout with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789\n";
    }
    {
        TimedSection s("printf with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("01234567890123456789\n");
    }
    {
        TimedSection s("cout with some stuff and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << std::endl;
    }
    {
        TimedSection s("cout with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << '\n';
    }
    {
        TimedSection s("printf with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("%s01234567890123456789%i\n", text, i);
    }
}

5
In Ihren Partituren schlägt printf cout leicht (in den meisten Fällen). Ich frage mich, warum Sie Cout empfehlen, wenn es um Perf geht. Obwohl ich zustimme, dass Perf in realistischen Fällen nicht zu unterschiedlich ist.
mishal153

3
@ mishal153: Ich versuche nur zu sagen, dass die Leistung nicht zu unterschiedlich ist, daher ist der allgemein gehörte Ratschlag "Niemals Cout verwenden, weil es langsam ist" einfach dumm. Beachten Sie, dass cout den offensichtlichen Vorteil der Typensicherheit und häufig auch der Lesbarkeit bietet. (Gleitkomma-Formatierung mit iostreams ist schrecklich ...)
Thomas

35
Der wichtige Unterschied zwischen printf()und std::ostreambesteht darin, dass erstere alle Argumente in einem einzigen Aufrufstd::ostream ausgeben, während für jeden ein separater Aufruf erfolgt <<. Der Test gibt nur ein Argument und eine neue Zeile aus. Deshalb können Sie den Unterschied nicht erkennen.
Maxim Egorushkin

12
Der Compiler sollte diese Aufrufe einbinden können. Auch printfkönnte eine Menge Anrufe unter der Decke zu Hilfsfunktionen für verschiedene Formatierung Bezeich machen ... das, oder es ist eine monströse monolithische Funktion. Und wieder sollte es wegen Inlining überhaupt keinen Unterschied in der Geschwindigkeit machen.
Thomas

4
Sie haben Ihr Terminal zeitlich festgelegt. Verwenden Sie sprintfoder fprintfund stringstreamoder fstream.
Ben Voigt

41

Und ich zitiere :

In hohem Maße sind die Hauptunterschiede die Typensicherheit (cstdio hat sie nicht), die Leistung (die meisten iostreams-Implementierungen sind langsamer als die cstdio-Implementierungen) und die Erweiterbarkeit (iostreams ermöglicht benutzerdefinierte Ausgabeziele und die nahtlose Ausgabe benutzerdefinierter Typen).


Besonders unter Unix, wo Sie mit POSIX nie wissen, welche Größe eines der Typedefs wirklich hat, brauchen Sie viele Casts oder als 99% der Programme riskieren Sie es einfach mit% d. Es hat sogar lange gedauert, bis% z mit C99 kam. Aber für time_t / off_t wird die Suche nach der richtigen Formatanweisung fortgesetzt.
Lothar

30

Eine ist eine Funktion, die auf stdout druckt. Das andere ist ein Objekt, das operator<<stdout mehrere Elementfunktionen und Überladungen dieses Drucks bereitstellt. Es gibt noch viele weitere Unterschiede, die ich aufzählen könnte, aber ich bin mir nicht sicher, wonach Sie suchen.


12

Für mich sind die wirklichen Unterschiede, die mich dazu bringen würden, eher "cout" als "printf" zu wählen, folgende:

1) Der Operator << kann für meine Klassen überladen werden.

2) Der Ausgabestream für cout kann einfach in eine Datei geändert werden: (: Kopieren Einfügen :)

#include <iostream>
#include <fstream>
using namespace std;

int main ()
{
    cout << "This is sent to prompt" << endl;
    ofstream file;
    file.open ("test.txt");
    streambuf* sbuf = cout.rdbuf();
    cout.rdbuf(file.rdbuf());
    cout << "This is sent to file" << endl;
    cout.rdbuf(sbuf);
    cout << "This is also sent to prompt" << endl;
    return 0;
}

3) Ich finde cout besser lesbar, besonders wenn wir viele Parameter haben.

Ein Problem mit coutist die Formatierungsoptionen. Das Formatieren der Daten (Genauigkeit, Ausrichtung usw.) printfist einfacher.


1
es ist schön. Wie kann ich wissen, dass niemand in einem fremden Bibliotheksthread das globale Cout auf diese Weise ändert?
vp_arth

1
Sie können auch problemlos printfzu einer Datei wechseln , indem Sie sie durch fprintf...
CoffeeTableEspresso

5

Zwei Punkte, die hier nicht anders erwähnt werden und die ich für wichtig halte:

1) coutträgt viel Gepäck, wenn Sie die STL nicht bereits verwenden. Es fügt Ihrer Objektdatei mehr als doppelt so viel Code hinzu wie printf. Dies gilt auch für string, und dies ist der Hauptgrund, warum ich meine eigene String-Bibliothek verwende.

2) coutverwendet überladene <<Operatoren, was ich unglücklich finde. Dies kann zu Verwirrung führen, wenn Sie den <<Operator auch für den vorgesehenen Zweck verwenden (nach links verschieben). Ich persönlich möchte Betreiber nicht für Zwecke überlasten, die tangential zu ihrem Verwendungszweck sind.

Fazit: Ich werde cout(und string) verwenden, wenn ich bereits die STL verwende. Ansonsten neige ich dazu, es zu vermeiden.


4

Bei Grundelementen spielt es wahrscheinlich keine Rolle, welches Sie verwenden. Ich sage, wo es nützlich wird, wenn Sie komplexe Objekte ausgeben möchten.

Wenn Sie beispielsweise eine Klasse haben,

#include <iostream>
#include <cstdlib>

using namespace std;

class Something
{
public:
        Something(int x, int y, int z) : a(x), b(y), c(z) { }
        int a;
        int b;
        int c;

        friend ostream& operator<<(ostream&, const Something&);
};

ostream& operator<<(ostream& o, const Something& s)
{
        o << s.a << ", " << s.b << ", " << s.c;
        return o;
}

int main(void)
{
        Something s(3, 2, 1);

        // output with printf
        printf("%i, %i, %i\n", s.a, s.b, s.c);

        // output with cout
        cout << s << endl;

        return 0;
}

Nun, das Obige scheint vielleicht nicht so toll zu sein, aber nehmen wir an, Sie müssen dies an mehreren Stellen in Ihrem Code ausgeben. Angenommen, Sie fügen ein Feld "int d" hinzu. Mit cout müssen Sie es nur an einer Stelle ändern. Mit printf müssten Sie es jedoch möglicherweise an vielen Stellen ändern, und nicht nur das, Sie müssen sich auch daran erinnern, welche ausgegeben werden sollen.

Mit cout können Sie jedoch viel Zeit für die Wartung Ihres Codes sparen und nicht nur, dass Sie sich nicht wirklich um die Ausgabe kümmern müssen, wenn Sie das Objekt "Something" in einer neuen Anwendung wiederverwenden.


Um noch etwas zur Leistung hinzuzufügen, würde ich sagen, dass Sie überhaupt nichts ausgeben sollten, wenn Ihre Anwendung auf Leistung ausgelegt ist. Jede Art von Ausgabe an std ist ziemlich teuer und langsam. Ich sage, Sie sollten es vermeiden und nur dann ausgeben, wenn dies unbedingt erforderlich ist.
Daniel

Denken Sie daran, dass Ihre Klasse möglicherweise private Mitglieder hat, auf die Sie von außen nicht so einfach zugreifen können. Mit dem Ausgabeoperator haben Sie genau einen Ort, der mit Ihrer Klasse befreundet sein muss, und jetzt können Sie ihn überall ausgeben, auch in Code, den Sie nicht kennen.
hochl

2

Natürlich können Sie "etwas" etwas besser schreiben, um die Wartung aufrechtzuerhalten:

#include <iostream>
#include <cstdlib>

using namespace std;

class Something
{
    public:
        Something(int x, int y, int z) : a(x), b(y), c(z) { }
        int a;
        int b;
        int c;

        friend ostream& operator<<(ostream&, const Something&);

        void print() const { printf("%i, %i, %i\n", a, b, c); }
};

ostream& operator<<(ostream& o, const Something& s)
{
    o << s.a << ", " << s.b << ", " << s.c;
    return o;
}

int main(void)
{
    Something s(3, 2, 1);

    // Output with printf
    s.print(); // Simple as well, isn't it?

    // Output with cout
    cout << s << endl;

    return 0;
}

Und ein etwas erweiterter Test von cout vs. printf, fügte einen Test von 'double' hinzu, wenn jemand mehr Tests durchführen möchte (Visual Studio 2008, Release-Version der ausführbaren Datei):

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

class TimedSection {
    char const *d_name;
    //timespec d_start;
    clock_t d_start;

    public:
        TimedSection(char const *name) :
            d_name(name)
        {
            //clock_gettime(CLOCK_REALTIME, &d_start);
            d_start = clock();
        }
        ~TimedSection() {
            clock_t end;
            //clock_gettime(CLOCK_REALTIME, &end);
            end = clock();
            double duration = /*1e3 * (end.tv_sec - d_start.tv_sec) +
                              1e-6 * (end.tv_nsec - d_start.tv_nsec);
                              */
                              (double) (end - d_start) / CLOCKS_PER_SEC;

            std::cerr << d_name << '\t' << std::fixed << duration * 1000.0 << " ms\n";
        }
};


int main() {
    const int iters = 1000000;
    char const *text = "01234567890123456789";
    {
        TimedSection s("cout with only endl");
        for (int i = 0; i < iters; ++i)
            std::cout << std::endl;
    }
    {
        TimedSection s("cout with only '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << '\n';
    }
    {
        TimedSection s("printf with only '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("\n");
    }
    {
        TimedSection s("cout with string constant and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789" << std::endl;
    }
    {
        TimedSection s("cout with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789\n";
    }
    {
        TimedSection s("printf with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("01234567890123456789\n");
    }
    {
        TimedSection s("cout with some stuff and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << std::endl;
    }
    {
        TimedSection s("cout with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << '\n';
    }
    {
        TimedSection s("printf with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("%s01234567890123456789%i\n", text, i);
    }
    {
        TimedSection s("cout with formatted double (width & precision once)");
        std::cout << std::fixed << std::scientific << std::right << std::showpoint;
        std::cout.width(8);
        for (int i = 0; i < iters; ++i)
            std::cout << text << 8.315 << i << '\n';
    }
    {
        TimedSection s("cout with formatted double (width & precision on each call)");
        std::cout << std::fixed << std::scientific << std::right << std::showpoint;

        for (int i = 0; i < iters; ++i)
            { std::cout.width(8);
              std::cout.precision(3);
              std::cout << text << 8.315 << i << '\n';
            }
    }
    {
        TimedSection s("printf with formatted double");
        for (int i = 0; i < iters; ++i)
            printf("%8.3f%i\n", 8.315, i);
    }
}

Das Ergebnis ist:

cout with only endl    6453.000000 ms
cout with only '\n'    125.000000 ms
printf with only '\n'    156.000000 ms
cout with string constant and endl    6937.000000 ms
cout with string constant and '\n'    1391.000000 ms
printf with string constant and '\n'    3391.000000 ms
cout with some stuff and endl    9672.000000 ms
cout with some stuff and '\n'    7296.000000 ms
printf with some stuff and '\n'    12235.000000 ms
cout with formatted double (width & precision once)    7906.000000 ms
cout with formatted double (width & precision on each call)    9141.000000 ms
printf with formatted double    3312.000000 ms

Wow, warum ist endlso viel weniger effizient als '\n'?
Nicholas Hamilton

1
Ich glaube, das liegt daran, dass endlder Puffer geleert wird und \nnicht, obwohl ich nicht sicher bin, ob dies definitiv der Grund dafür ist.
Caleb Xu

Dies ist keine Antwort auf die Frage, sondern eher eine Antwort auf Daniels und Thomas .
Fabio sagt Reinstate Monica

2

Ich möchte darauf hinweisen, dass Sie couteinige interessante Ergebnisse erzielen können, wenn Sie mit Threads in C ++ spielen möchten .

Betrachten Sie diesen Code:

#include <string>
#include <iostream>
#include <thread>

using namespace std;

void task(int taskNum, string msg) {
    for (int i = 0; i < 5; ++i) {
        cout << "#" << taskNum << ": " << msg << endl;
    }
}

int main() {
    thread t1(task, 1, "AAA");
    thread t2(task, 2, "BBB");
    t1.join();
    t2.join();
    return 0;
}

// g++ ./thread.cpp -o thread.out -ansi -pedantic -pthread -std=c++0x

Jetzt wird die Ausgabe komplett gemischt. Es kann auch zu unterschiedlichen Ergebnissen führen. Versuchen Sie es mehrmals:

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

Sie können verwenden printf, um es richtig zu machen, oder Sie können verwenden mutex.

#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB

Habe Spaß!


2
wtf threads machen die Ausgabe nicht verrückt. Ich habe gerade beide reproduziert und gefunden xyzund ABCin der Ausgabe. Es wurde nicht s / w ABCals zerfetzt ABABAB.
Abhinav Gauniyal

1
Ich weiß nicht, wie es coutmit Threads funktioniert, aber ich weiß mit Sicherheit, dass der Code, den Sie anzeigen, nicht der ist, mit dem Sie diese Ausgaben erhalten haben. Ihr Code übergibt die Zeichenfolge "ABC"für Thread 1 und "xyz"für Thread 2, aber Ihre Ausgabe zeigt AAAund BBB. Bitte beheben Sie es, weil es im Moment verwirrend ist.
Fabio sagt Reinstate Monica

1
cout<< "Hello";
printf("%s", "Hello"); 

Beide werden zum Drucken von Werten verwendet. Sie haben eine völlig andere Syntax. C ++ hat beides, C hat nur printf.


19
... was? Hast du etwas verwechselt?
xtofl

1
Das Problem wurde behoben. -1, weil es korrigiert werden musste und die Antwort zu wünschen übrig lässt.
Yacoby

3
Die Funktionsnamen wurden umgekehrt: cout wurde mit der printf-Syntax und printf wurde mit der cout-Syntax verwendet. Sollte nicht einmal akzeptiert worden sein!
Mahmoud Al-Qudsi

2
und der Hauptnachteil von cout ist, dass es den Operator << verwendet, der ausführlich und hässlich ist und möglicherweise den Operator missbraucht. :)
Jalf

8
Obwohl dies mit Sicherheit nicht die beste Antwort ist, verstehe ich nicht, wie Scatman für seine Antwort bestraft wird, nur weil sie als beste Antwort ausgewählt wurde. xbit hat eine viel schlechtere Antwort IMO, hat aber -1 Stimme. Ich sage nicht, dass xbit nicht mehr heruntergestimmt werden sollte, aber ich sehe es nicht als fair an, Scatman für den Fehler des OP abzustimmen, mehr als es sein muss ...
Jesse

1

Ich möchte sagen, dass der Mangel an Erweiterbarkeit printfnicht ganz stimmt:
In C ist es wahr. Aber in C gibt es keine wirklichen Klassen.
In C ++ ist es möglich, den Cast-Operator zu überladen. Überladen Sie also einen char*Operator und verwenden Sie printfFolgendes:

Foo bar;
...;
printf("%s",bar);

kann möglich sein, wenn Foo den guten Bediener überlastet. Oder wenn Sie eine gute Methode gemacht haben. Kurz gesagt, printfist so erweiterbar wie coutfür mich.

Technische Argumente, die ich für C ++ - Streams sehen kann (im Allgemeinen ... nicht nur cout.), Sind:

  • Typensicherheit. (Und übrigens, wenn ich eine einzelne drucken möchte, die '\n'ich benutze putchar('\n')... werde ich keine Atombombe verwenden, um ein Insekt zu töten.)

  • Einfacher zu lernen. (keine "komplizierten" Parameter zu lernen, nur zu verwenden <<und >>Operatoren)

  • Arbeite nativ mit std::string(denn printfes gibt std::string::c_str(), aber für scanf?)

Denn printfich sehe:

  • Einfachere oder zumindest kürzere (in Bezug auf die geschriebenen Zeichen) komplexe Formatierung. Für mich weitaus lesbarer (Geschmackssache, denke ich).

  • Bessere Kontrolle darüber, was die Funktion gemacht hat (Geben Sie zurück, wie viele Zeichen geschrieben wurden, und es gibt den %nFormatierer: "Nichts gedruckt. Das Argument muss ein Zeiger auf ein vorzeichenbehaftetes int sein, in dem die Anzahl der bisher geschriebenen Zeichen gespeichert ist." (Von printf - C ++ Referenz )

  • Bessere Debugging-Möglichkeiten. Aus demselben Grund wie das letzte Argument.

Meine persönlichen Vorlieben beziehen sich auf printf(und scanf) Funktionen, hauptsächlich weil ich kurze Zeilen liebe und weil ich nicht denke, dass Schreibprobleme beim Drucken von Text wirklich schwer zu vermeiden sind. Das einzige, was ich mit Funktionen im C-Stil bedaure, ist, dass std::stringes nicht unterstützt wird. Wir müssen ein durchlaufen, char*bevor wir es geben printf(mit dem, std::string::c_str()wenn wir lesen wollen, aber wie schreiben?)


3
Der Compiler verfügt über keine Typinformationen für Varargs-Funktionen, sodass der tatsächliche Parameter nicht konvertiert wird (mit Ausnahme von Standardargument-Promotions , z. B. Standard-Integral-Promotions). Siehe 5.2.2p7. Eine benutzerdefinierte Konvertierung in char*wird nicht verwendet.
Ben Voigt

Selbst wenn dies funktionieren würde, wäre es kein Beispiel für die Erweiterbarkeit von Sprintf, sondern nur ein cleverer Hack, um Sprintf das zu geben, was es erwartet, und es ignoriert einige schwerwiegende Probleme, wie z. B. wo char*und wie lange das Leben und die Gefahren von benutzerdefinierten implizite Besetzungen.
Marcelo Cantos

1

Weitere Unterschiede: "printf" gibt einen ganzzahligen Wert zurück (der der Anzahl der gedruckten Zeichen entspricht) und "cout" gibt nichts zurück

Und.

cout << "y = " << 7; ist nicht atomar.

printf("%s = %d", "y", 7); ist atomar.

cout führt typechecking durch, printf nicht.

Es gibt kein iostream-Äquivalent von "% d"


3
coutgibt nichts zurück, weil es ein Objekt ist, keine Funktion. operator<<gibt etwas zurück (normalerweise der linke Operand, aber ein falscher Wert, wenn ein Fehler vorliegt). Und in welchem ​​Sinne ist der printfRuf "atomar"?
Keith Thompson

9
Es ist wie eine Atombombe. printf("%s\n",7);
Kunstloser Lärm

@artlessnoise warte warum Segmentierungsfehler? %sist?
Abhinav Gauniyal

1
Das ist der Punkt der Atombombenaussage. Ein printf % s- Argument muss einen gültigen Zeiger auf eine nullterminierte Zeichenfolge haben. Der Speicherbereich '7' (ein Zeiger) ist normalerweise nicht gültig; Ein Segmentierungsfehler könnte Glück haben. Auf einigen Systemen druckt '7' möglicherweise viel Müll auf eine Konsole, und Sie müssten ihn einen Tag lang ansehen, bevor das Programm beendet wird. Mit anderen Worten, das ist eine schlechte Sache printf. Statische Analysewerkzeuge können viele dieser Probleme lösen.
Kunstloser Lärm

Obwohl technisch printfkeine Schreibprüfung durchgeführt wird, habe ich noch nie einen Compiler verwendet, der mich nicht vor Tippfehlern gewarnt hat mit printf...
CoffeeTableEspresso

1

TL; DR: Machen Sie immer Ihre eigenen Nachforschungen hinsichtlich der Größe , Leistung , Lesbarkeit und Codierungszeit des generierten Maschinencodes , bevor Sie zufälligen Kommentaren online vertrauen, einschließlich dieser.

Ich bin kein Experte. Ich habe gerade zwei Mitarbeiter belauscht, die darüber sprachen, wie wir die Verwendung von C ++ in eingebetteten Systemen aufgrund von Leistungsproblemen vermeiden sollten. Interessanterweise habe ich einen Benchmark durchgeführt, der auf einer echten Projektaufgabe basiert.

In dieser Aufgabe mussten wir eine Konfiguration in den RAM schreiben. Etwas wie:

Kaffee = heißer
Zucker = keine
Milch = Brust
Mac = AA: BB: CC: DD: EE: FF

Hier sind meine Benchmark-Programme (Ja, ich weiß, dass OP nach printf () und nicht nach fprintf () gefragt hat. Versuchen Sie, die Essenz zu erfassen, und der Link von OP verweist übrigens trotzdem auf fprintf ().)

C-Programm:

char coffee[10], sugar[10], milk[10];
unsigned char mac[6];

/* Initialize those things here. */

FILE * f = fopen("a.txt", "wt");

fprintf(f, "coffee=%s\nsugar=%s\nmilk=%s\nmac=%02X:%02X:%02X:%02X:%02X:%02X\n", coffee, sugar, milk, mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]);

fclose(f);

C ++ - Programm:

//Everything else is identical except:

std::ofstream f("a.txt", std::ios::out);

f << "coffee=" << coffee << "\n";
f << "sugar=" << sugar << "\n";
f << "milk=" << milk << "\n";
f << "mac=" << (int)mac[0] << ":"
    << (int)mac[1] << ":"
    << (int)mac[2] << ":"
    << (int)mac[3] << ":"
    << (int)mac[4] << ":"
    << (int)mac[5] << endl;
f.close();

Ich habe mein Bestes getan, um sie zu polieren, bevor ich sie beide 100.000 Mal geloopt habe. Hier sind die Ergebnisse:

C-Programm:

real    0m 8.01s
user    0m 2.37s
sys     0m 5.58s

C ++ - Programm:

real    0m 6.07s
user    0m 3.18s
sys     0m 2.84s

Objektdateigröße:

C   - 2,092 bytes
C++ - 3,272 bytes

Fazit: Auf meiner ganz bestimmten Plattform mit einem ganz bestimmten Prozessor , auf dem eine ganz bestimmte Version des Linux-Kernels ausgeführt wird, um ein Programm auszuführen, das mit einer ganz bestimmten Version von GCC kompiliert wurde , um eine ganz bestimmte Aufgabe zu erfüllen , würde ich sagen Der C ++ - Ansatz ist besser geeignet, da er erheblich schneller ausgeführt wird und eine viel bessere Lesbarkeit bietet. Auf der anderen Seite bedeutet C einen geringen Platzbedarf, was meiner Meinung nach fast nichts bedeutet, da die Programmgröße nicht unser Anliegen ist.

Denken Sie daran, YMMV.


Ich bin nicht der Meinung, dass C ++ in diesem Beispiel besser lesbar ist, da in Ihrem Beispiel mehrere Zeilen in einem einzigen printf-Aufruf zusammengefasst sind. Das ist natürlich weniger lesbar als der C ++ - Code und wird in C selten ausgeführt, da es schwer zu lesen und zu warten ist. Ein fairer Vergleich würde das C in separate Drucke aufteilen, einen für die Reichweite.
Maharvey67

1
@ maharvey67 Es ist wahr, was du gesagt hast. Das Beispiel, das ich in C angegeben habe, berücksichtigte jedoch die Leistung. Der in einem Pack gepackte Aufruf von fprintf war bereits zwei Sekunden langsamer als die C ++ - Äquivalenz. Wenn ich den C-Code lesbar machen würde, wäre er möglicherweise noch langsamer. Haftungsausschluss: Dies war vor einem Jahr und ich erinnere mich, dass ich mein Bestes versucht habe, sowohl C- als auch C ++ - Code zu polieren. Ich hatte keinen Beweis dafür, dass getrennte Anrufe bei fprintf schneller sind als ein einzelner Anruf, aber der Grund, warum ich das so gemacht habe, deutet wahrscheinlich darauf hin, dass dies nicht der Fall war.
Wesley

0

Ich bin kein Programmierer, aber ich war ein Ingenieur für menschliche Faktoren. Ich bin der Meinung, dass eine Programmiersprache leicht zu erlernen, zu verstehen und zu verwenden sein sollte, und dies erfordert eine einfache und konsistente Sprachstruktur. Obwohl alle Sprachen symbolisch und daher im Kern willkürlich sind, gibt es Konventionen, deren Befolgung das Erlernen und Verwenden der Sprache erleichtert.

Es gibt eine Vielzahl von Funktionen in C ++ und anderen Sprachen, die als Funktion (Parameter) geschrieben wurden, eine Syntax, die ursprünglich für funktionale Beziehungen in der Mathematik in der Zeit vor dem Computer verwendet wurde. printf()folgt dieser Syntax und wenn die Autoren von C ++ eine logisch andere Methode zum Lesen und Schreiben von Dateien erstellen wollten, hätten sie einfach eine andere Funktion mit einer ähnlichen Syntax erstellen können.

In Python können wir natürlich mit der ebenfalls ziemlich standardmäßigen object.methodSyntax drucken , dh variablename.print, da Variablen Objekte sind, in C ++ jedoch nicht.

Ich mag die Cout-Syntax nicht, weil der Operator << keine Regeln befolgt. Es ist eine Methode oder Funktion, dh es nimmt einen Parameter und macht etwas damit. Es ist jedoch so geschrieben, als wäre es ein mathematischer Vergleichsoperator. Dies ist aus menschlicher Sicht ein schlechter Ansatz.


-1

printfist eine Funktion, während coutes sich um eine Variable handelt.


6
Ich habe ein Rollback durchgeführt, da die Antwort selbst zwar falsch ist, aber dennoch eine echte Antwort ist. Wenn Sie (richtig) denken, dass die Antwort falsch ist, haben Sie zwei Möglichkeiten: 1) einen Kommentar hinzufügen oder 2) eine neue Antwort hinzufügen (oder beides tun). Ändern Sie die Antwort von jemandem nicht so, dass sie etwas völlig anderes sagt als vom Autor beabsichtigt.
Mark

1
printfist eine Funktion, aber printf()ist ein Funktionsaufruf =)
vp_arth

cout ist ein Objekt, keine Variable.
Lin
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.