530 Stimmen

printf" vs. "cout" in C++

Was ist der Unterschied zwischen printf() y cout in C++?

531voto

Konrad Borowski Punkte 10645

Ich bin überrascht, dass jeder in dieser Frage behauptet, dass std::cout ist viel besser als printf auch wenn in der Frage nur nach Unterschieden gefragt wurde. Nun, es gibt einen Unterschied - std::cout ist C++, und printf ist C (Sie können es aber auch in C++ verwenden, genau wie fast alles andere von C). Ich will hier ehrlich sein: Beide printf y std::cout haben ihre Vorteile.

Echte Unterschiede

Erweiterbarkeit

std::cout ist erweiterbar. Ich weiß, dass die Leute sagen werden, dass printf ist ebenfalls erweiterbar, aber eine solche Erweiterung wird im C-Standard nicht erwähnt (man müsste also Nicht-Standard-Funktionen verwenden - aber es gibt nicht einmal gemeinsame Nicht-Standard-Funktionen), und solche Erweiterungen bestehen aus einem Buchstaben (so dass es leicht zu Konflikten mit einem bereits vorhandenen Format kommt).

Anders als printf , std::cout hängt vollständig von der Überladung von Operatoren ab, so dass es keine Probleme mit benutzerdefinierten Formaten gibt - Sie müssen lediglich ein Unterprogramm definieren, das std::ostream als erstes Argument und Ihren Typ als zweites. Daher gibt es keine Probleme mit dem Namensraum - solange Sie eine Klasse haben (die nicht auf ein Zeichen beschränkt ist), können Sie eine funktionierende std::ostream Überlastung für sie.

Ich bezweifle jedoch, dass viele Menschen die ostream (um ehrlich zu sein, habe ich solche Erweiterungen selten gesehen, auch wenn sie leicht zu machen sind). Wie auch immer, es ist hier, wenn Sie es brauchen.

Syntax

Wie unschwer zu erkennen ist, sind beide printf y std::cout eine andere Syntax verwenden. printf verwendet eine Standardfunktionssyntax mit Musterzeichenfolgen und Argumentlisten variabler Länge. Eigentlich, printf ist ein Grund, warum C sie hat - printf Formate sind zu komplex, um ohne sie verwendet werden zu können. Allerdings, std::cout verwendet eine andere API - die operator << API, die sich selbst zurückgibt.

Im Allgemeinen bedeutet das, dass die C-Version kürzer ist, aber in den meisten Fällen spielt das keine Rolle. Der Unterschied wird deutlich, wenn Sie viele Argumente ausgeben. Wenn Sie etwas schreiben müssen wie Error 2: File not found. Wenn man davon ausgeht, dass die Fehlernummer und die Beschreibung Platzhalter sind, würde der Code wie folgt aussehen. Beide Beispiele identisch arbeiten (naja, irgendwie schon, std::endl den Puffer tatsächlich leert).

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

Während dies nicht allzu verrückt erscheint (es ist nur zwei Mal länger), wird es noch verrückter, wenn man Argumente tatsächlich formatiert, anstatt sie nur auszudrucken. Zum Beispiel wird das Drucken von etwas wie 0x0424 ist einfach verrückt. Dies wird verursacht durch std::cout Vermischung von Zustand und tatsächlichen Werten. Ich habe noch nie eine Sprache gesehen, in der etwas wie std::setfill wäre ein Typ (natürlich ein anderer als C++). printf trennt klar zwischen Argumenten und dem eigentlichen Typ. Ich würde es wirklich vorziehen, die printf Version davon (auch wenn sie etwas kryptisch aussieht) im Vergleich zu iostream Version 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 eigentliche Vorteil von printf liegt. Die printf format string ist nun einmal... eine Zeichenkette. Das macht es wirklich einfach zu übersetzen, verglichen mit operator << Missbrauch von iostream . Unter der Annahme, dass die gettext() Funktion übersetzt, und Sie wollen zeigen Error 2: File not found. würde der Code für die Übersetzung der zuvor gezeigten Formatzeichenfolge wie folgt aussehen:

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

Nehmen wir nun an, wir übersetzen ins Fiktive, wo die Fehlernummer nach der Beschreibung steht. Die übersetzte Zeichenfolge würde wie folgt aussehen %2$s oru %1$d.\n . Wie macht man das nun in C++? Nun, ich habe keine Ahnung. Ich schätze, man kann gefälschte iostream die Konstruktionen printf die Sie an gettext oder so ähnlich, für die Zwecke der Übersetzung. Natürlich, $ ist kein C-Standard, aber er ist so weit verbreitet, dass er meiner Meinung nach sicher verwendet werden kann.

Sie müssen sich nicht an die spezifische Syntax von Ganzzahlen erinnern bzw. diese nachschlagen.

C hat viele Integer-Typen, ebenso wie C++. std::cout bearbeitet alle Typen für Sie, während printf erfordert eine spezielle Syntax, die von einem Integer-Typ abhängt (es gibt auch nicht-ganzzahlige Typen, aber der einzige nicht-ganzzahlige Typ, den Sie in der Praxis mit printf es const char * (C-String), kann man erhalten durch to_c Methode der std::string )). Zum Beispiel, um zu drucken size_t müssen Sie Folgendes verwenden %zu , während int64_t erfordert die Verwendung von %"PRId64" . Die Tabellen sind verfügbar unter http://en.cppreference.com/w/cpp/io/c/fprintf y http://en.cppreference.com/w/cpp/types/integer .

Sie können das NUL-Byte nicht ausdrucken, \0

Denn printf C-Strings im Gegensatz zu C++-Strings verwendet, kann es ohne spezielle Tricks kein NUL-Byte ausgeben. In bestimmten Fällen ist es möglich, mit %c avec '\0' als Argument, obwohl das eindeutig ein Scherz ist.

Unterschiede, für die sich niemand interessiert

Leistung

Aktualisierung: Es hat sich herausgestellt, dass iostream ist so langsam, dass es normalerweise langsamer ist als Ihre Festplatte (wenn Sie Ihr Programm in eine Datei umleiten). Deaktivieren der Synchronisierung mit stdio kann helfen, 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 .

Alle glauben, dass ihnen die Leistung wichtig ist, aber niemand macht sich die Mühe, sie zu messen. Meine Antwort ist, dass E/A sowieso der Flaschenhals ist, egal ob man printf o iostream . Ich denke, dass printf könnte schneller sein, wie ein kurzer Blick in die Assemblierung zeigt (kompiliert mit Clang unter Verwendung der -O3 Compiler-Option). Angenommen, mein Fehlerbeispiel, printf Beispiel macht viel weniger Aufrufe als das cout Beispiel. Dies ist int main avec 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 Zeichenketten, und 2 (Zahl) werden geschoben als printf Argumente. Das war's; es gibt nichts anderes. Zum Vergleich: Dies ist iostream zu einer Baugruppe kompiliert. Nein, es gibt kein Inlining; jede 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 Flaschenhals ist. Ich wollte nur zeigen, dass iostream ist nicht schneller, weil es "typsicher" ist. Die meisten C-Implementierungen implementieren printf Formate unter Verwendung des berechneten goto, so dass die printf ist so schnell wie möglich, auch wenn der Compiler nicht weiß, dass printf (nicht, dass sie es nicht wären - einige Compiler können die printf in bestimmten Fällen - konstante Zeichenfolge mit der Endung \n ist in der Regel optimiert auf puts ).

Vererbung

Ich weiß nicht, warum Sie erben wollen. ostream aber das ist mir egal. Es ist möglich mit FILE auch.

class MyFile : public FILE {}

Typ Sicherheit

Es stimmt, dass Argumentlisten mit variabler Länge keine Sicherheit bieten, aber das spielt keine Rolle, da gängige C-Compiler Probleme mit printf Formatstring, wenn Sie Warnungen aktivieren. Tatsächlich kann Clang dies auch ohne die Aktivierung von Warnungen tun.

$ 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);
     ^

228voto

Mikeage Punkte 6304

Von der C++ FAQ :

[15.1] Warum sollte ich die <iostream> anstelle der traditionellen <cstdio> ?

Erhöhen Sie die Typsicherheit, reduzieren Sie Fehler, ermöglichen Sie Erweiterbarkeit und bieten Sie Vererbbarkeit.

printf() ist wohl nicht gebrochen, und scanf() ist trotz seiner Fehleranfälligkeit vielleicht lebbar, aber beide sind im Hinblick auf die Möglichkeiten von C++ I/O begrenzt. C++ E/A (mit << y >> ) ist, bezogen auf C (unter Verwendung von printf() y scanf() ):

  • Mehr Typsicherheit: Mit <iostream> ist der Typ des E/A-Objekts statisch durch den Compiler bekannt. In Gegensatz dazu, <cstdio> verwendet "%"-Felder, um die Typen dynamisch zu ermitteln.
  • Weniger fehleranfällig: Mit <iostream> gibt es keine redundanten "%"-Zeichen, die mit den tatsächlichen mit den tatsächlichen Objekten übereinstimmen müssen, die I/O'd sind. Die Beseitigung der Redundanz beseitigt eine Klasse von Fehlern.
  • Erweiterbar: Die C++ <iostream> Mechanismus ermöglicht neue benutzerdefinierte E/A-Typen, ohne dass der bestehende bestehenden Code. Stellen Sie sich das Chaos vor, wenn alle gleichzeitig neue neue inkompatible "%"-Felder zu printf() y scanf() ?!
  • Vererbbar: Die C++ <iostream> Mechanismus ist aus echten Klassen aufgebaut wie z.B. std::ostream et std::istream . Anders als <cstdio> 's FILE* sind dies echte Klassen und daher vererbbar. Das bedeutet, Sie können andere benutzerdefinierte Dinge haben, die aussehen und sich wie Streams verhalten, die jedoch tun, was auch immer für seltsame und wunderbare Dinge tun, die Sie wollen. Sie können automatisch die Zillionen von Zeilen mit E/A-Code, die von Benutzern geschrieben wurden, die Sie nicht die Sie nicht einmal kennen, und sie müssen auch nicht über Ihre "erweiterte Stream"-Klasse wissen Klasse.

Andererseits, printf ist deutlich schneller, was es rechtfertigen kann, es stattdessen zu verwenden cout en très spezifische und begrenzte Fälle. Erstellen Sie immer zuerst ein Profil. (Siehe z. B., http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)

57voto

Thomas Punkte 160390

Es wird oft behauptet, dass printf ist viel schneller. Das ist weitgehend ein Mythos. Ich habe es gerade getestet, mit den folgenden Ergebnissen:

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 wollen, verwenden Sie printf ; sonst, cout ist fast genauso schnell, wenn nicht sogar schneller. Weitere Einzelheiten finden Sie unter mein Blog .

Um das klarzustellen, ich will damit nicht sagen, dass iostream s sind immer besser als printf Ich will damit nur sagen, dass Sie eine fundierte Entscheidung auf der Grundlage realer Daten treffen sollten und nicht eine wilde Vermutung auf der Grundlage einer allgemeinen, irreführenden Annahme.

Update: Hier ist der vollständige Code, den ich zum Testen verwendet habe. Kompiliert mit g++ ohne zusätzliche Optionen (abgesehen von -lrt für den Zeitplan).

#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);
    }
}

44voto

Kyle Rosendo Punkte 24351

Und ich Zitat :

Die wichtigsten Unterschiede sind die Typsicherheit (cstdio hat sie nicht), Leistung (die meisten iostreams-Implementierungen sind langsamer als die von cstdio) und Erweiterbarkeit (iostreams erlaubt benutzerdefinierte Ausgabeziele und nahtlose Ausgabe von benutzerdefinierten Typen).

31voto

Marcelo Cantos Punkte 173498

Die eine ist eine Funktion, die nach stdout ausgibt. Das andere ist ein Objekt, das mehrere Mitgliedsfunktionen und Überladungen von operator<< die in stdout ausgegeben werden. Es gibt noch viele weitere Unterschiede, die ich aufzählen könnte, aber ich bin mir nicht sicher, worauf Sie aus sind.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X