„printf” vs. „cout” w C ++

Odpowiedzi:

332

Dziwi mnie, że wszyscy w tym pytaniu twierdzą, że std::coutjest o wiele lepszy niż printf, nawet jeśli pytanie dotyczyło tylko różnic. Teraz jest różnica - std::coutto C ++ i printfC (jednak można go używać w C ++, podobnie jak prawie wszystko inne z C). Będę tu szczery; oba printfi std::coutmają swoje zalety.

Prawdziwe różnice

Rozciągliwość

std::coutjest rozszerzalny. Wiem, że ludzie powiedzą, że printfjest również rozszerzalny, ale takie rozszerzenie nie jest wspomniane w standardzie C (więc musiałbyś użyć niestandardowych funkcji - ale nawet nie istnieje wspólna niestandardowa funkcja), a takie rozszerzenia to jedna litera (więc łatwo jest konfliktować z już istniejącym formatem).

W przeciwieństwie do printf, std::coutzależy całkowicie od przeciążenia operatora, więc nie ma problemu z niestandardowymi formatami - wystarczy, że zdefiniujesz podprogram std::ostreamjako pierwszy argument, a typ jako drugi. W związku z tym nie ma problemów z przestrzenią nazw - dopóki masz klasę (która nie jest ograniczona do jednej postaci), możesz mieć std::ostreamnadmiar pracy .

Wątpię jednak, aby wiele osób chciało przedłużać ostream(szczerze mówiąc, rzadko widziałem takie rozszerzenia, nawet jeśli są łatwe do wykonania). Jest jednak tutaj, jeśli go potrzebujesz.

Składnia

Jak można to łatwo zauważyć, jak printfi std::coutużywać innej składni. printfużywa standardowej składni funkcji przy użyciu łańcucha wzorca i list argumentów o zmiennej długości. W rzeczywistości printfjest to powód, dla którego C je ma - printfformaty są zbyt złożone, aby można je było bez nich zastosować. Jednak std::coutużywa innego API - ten operator <<interfejs API, który wraca sam.

Ogólnie oznacza to, że wersja C będzie krótsza, ale w większości przypadków nie będzie to miało znaczenia. Różnica jest zauważalna przy drukowaniu wielu argumentów. Jeśli musisz napisać coś takiego Error 2: File not found., zakładając numer błędu, a jego opis jest symbolem zastępczym, kod wyglądałby tak. Oba przykłady działają identycznie (no cóż, std::endlwłaściwie opróżniają bufor).

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

Chociaż nie wydaje się to zbyt szalone (jest tylko dwa razy dłuższe), rzeczy stają się bardziej szalone, gdy faktycznie formatujesz argumenty, zamiast po prostu je drukować. Na przykład drukowanie czegoś takiego 0x0424jest po prostu szalone. Jest to spowodowane std::coutstanem mieszania i wartościami rzeczywistymi. Nigdy nie widziałem języka, w którym coś takiego std::setfillbyłoby typem (oczywiście innym niż C ++). printfwyraźnie oddziela argumenty i rzeczywisty typ. Naprawdę wolałbym zachować jego printfwersję (nawet jeśli wygląda to trochę tajemniczo) w porównaniu do iostreamwersji (ponieważ zawiera zbyt dużo hałasu).

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

Tłumaczenie

To jest prawdziwa zaleta printfkłamstw. printfCiąg format jest dobrze ... ciąg. Dzięki temu tłumaczenie jest naprawdę łatwe w porównaniu z operator <<nadużyciami iostream. Zakładając, że gettext()funkcja tłumaczy i chcesz pokazać Error 2: File not found., kod do uzyskania tłumaczenia pokazanego wcześniej ciągu formatu wyglądałby następująco:

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

Załóżmy teraz, że tłumaczymy na fikcyjny, gdzie numer błędu znajduje się po opisie. Tak wyglądałby przetłumaczony ciąg %2$s oru %1$d.\n. Jak to zrobić w C ++? Nie mam pojęcia. Wydaje mi się, że można zrobić fałszywe iostreamkonstrukcje printf, które można przekazać gettextlub coś w celu tłumaczenia. Oczywiście $nie jest standardem C, ale jest tak powszechny, że moim zdaniem można go bezpiecznie używać.

Nie trzeba zapamiętywać / wyszukiwać składni typu liczb całkowitych

C ma wiele typów całkowitych, podobnie jak C ++. std::coutobsługuje wszystkie typy dla Ciebie, podczas gdy printfwymaga określonej składni w zależności od typu liczby całkowitej (istnieją typy niecałkowate, ale jedynym typem niecałkowitym, którego będziesz używać w praktyce, printfjest const char *(ciąg C, można uzyskać to_cmetodą std::string). Na przykład, aby wydrukować size_t, musisz użyć %zd, podczas gdy int64_tbędzie wymagać użycia %"PRId64". Tabele są dostępne na stronie http://en.cppreference.com/w/cpp/io/c/fprintf i http://en.cppreference.com/w/cpp/types/integer .

Nie możesz wydrukować bajtu NUL, \0

Ponieważ printfużywa ciągów C w przeciwieństwie do ciągów C ++, nie można wydrukować bajtu NUL bez określonych sztuczek. W niektórych przypadkach jest to możliwe korzystać %cz '\0'jako argument, mimo to wyraźnie hack.

Różnice, na których nikomu nie zależy

Wydajność

Aktualizacja: Okazuje się, że iostreamjest tak wolny, że zwykle jest wolniejszy niż dysk twardy (jeśli przekierujesz program do pliku). Wyłączenie synchronizacji z stdiomoże pomóc, jeśli musisz wyprowadzać dużo danych. Jeśli wydajność stanowi poważny problem (w przeciwieństwie do pisania kilku linii do STDOUT), po prostu użyj printf.

Wszyscy myślą, że zależy im na wydajności, ale nikt nie stara się jej zmierzyć. Moja odpowiedź jest taka, że ​​We / Wy jest wąskie gardło, bez względu na to, czy używasz, printfczy iostream. Myślę, że printf może to być szybsze od szybkiego spojrzenia na asembler (skompilowany z clang za pomocą -O3opcji kompilatora). Zakładając mój przykład błędu, printfprzykład wykonuje znacznie mniej wywołań niż coutprzykład. To jest int mainz 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:

Możesz łatwo zauważyć, że dwa ciągi i 2(liczba) są wypychane jako printfargumenty. O to chodzi; nie ma nic więcej. Dla porównania iostreamkompiluje się to w asemblerze. Nie, nie ma inklinacji; każde pojedyncze operator <<połączenie oznacza kolejne połączenie z innym zestawem argumentów.

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:

Jednak szczerze mówiąc, to nic nie znaczy, ponieważ we / wy jest wąskim gardłem. Chciałem tylko pokazać, że iostreamnie jest to szybsze, ponieważ jest „bezpieczne dla typu”. Większość implementacji C implementuje printfformaty wykorzystujące obliczone goto, więc printfjest tak szybkie, jak to tylko możliwe, nawet bez wiedzy kompilatora printf(nie to, że nie są - niektóre kompilatory mogą zoptymalizować printfw niektórych przypadkach - ciąg zakończony ciągiem \njest zwykle optymalizowany puts) .

Dziedzictwo

Nie wiem, dlaczego chcesz dziedziczyć ostream, ale mnie to nie obchodzi. To też jest możliwe FILE.

class MyFile : public FILE {}

Wpisz bezpieczeństwo

To prawda, że ​​listy argumentów o zmiennej długości nie mają bezpieczeństwa, ale to nie ma znaczenia, ponieważ popularne kompilatory C mogą wykryć problemy z printfłańcuchem formatu, jeśli włączysz ostrzeżenia. W rzeczywistości Clang może to zrobić bez włączania ostrzeżeń.

$ 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);
     ^
Konrad Borowski
źródło
18
Mówisz, że We / Wy jest wąskim gardłem. Oczywiście nigdy nie przetestowałeś tego założenia. Cytuję siebie: „Z drugiej strony wersja iostreams, z prędkością 75,3 MB / s, nie może buforować danych wystarczająco szybko, aby nadążyć za dyskiem twardym. To źle, a nawet nie robi żadnej prawdziwej pracy. „Nie sądzę, że mam zbyt wysokie oczekiwania, kiedy mówię, że moja biblioteka we / wy powinna być w stanie nasycić mój kontroler dysku”.
Ben Voigt
4
@BenVoigt: Przyznaję, staram się unikać C ++, jeśli to możliwe. Próbowałem go często używać, ale było to bardziej irytujące i mniej konserwowalne niż w innym języku programowania, którego używałem. To kolejny powód, dla którego unikam C ++ - to nie jest nawet szybkie (to nawet nie iostream - cała biblioteka C ++ jest wolna w większości implementacji, być może z wyjątkiem std::sort, która jest zaskakująco szybka w porównaniu do qsort(2 razy), w koszt rozmiaru pliku wykonywalnego).
Konrad Borowski
3
Nikt tutaj nie wspomniał o problemach w środowisku równoległym podczas korzystania z cout.
Nicholas Hamilton
9
Twój argument dotyczący wydajności nie ma żadnego sensu. Więcej montaż w programie nie oznacza, że program będzie wolniejszy, bo jesteś nie stanowi całego kodu, który sprawia, że printf funkcji, która jest dużo kodu. Moim zdaniem można zoptymalizować cout za pomocą operatora << znacznie lepiej niż printf, ponieważ kompilator lepiej rozumie zmienne i formatowanie.
Ignas2526,
18
Lubię wiele rzeczy w tej odpowiedzi, ale być może moją ulubioną częścią jest „Wszyscy myślą, że zależy im na wydajności, ale nikt nie stara się jej zmierzyć”.
Kyle Strand,
203

Z C ++ FAQ :

[15.1] Dlaczego powinienem używać <iostream> zamiast tradycyjnego <cstdio>?

Zwiększyć bezpieczeństwo typu, zmniejszyć liczbę błędów, umożliwić rozszerzalność i zapewnić dziedziczenie.

printf()prawdopodobnie nie jest zepsuty i scanf()może być znośny pomimo podatności na błędy, jednak oba są ograniczone w odniesieniu do tego, co potrafi C / I we / wy. C ++ I / O (używając <<i >>) jest względem C (używając printf()i scanf()):

  • Bardziej bezpieczny dla typu: Z <iostream>, typ obiektu będącego we / wy znany jest statycznie przez kompilator. W przeciwieństwie do tego <cstdio>używa pól „%” do dynamicznego obliczania typów.
  • Mniej podatne na błędy: dzięki <iostream>nie ma zbędnych tokenów „%”, które musiałyby być spójne z rzeczywistymi obiektami będącymi we / wy. Usunięcie redundancji usuwa klasę błędów.
  • Rozszerzalny: <iostream>mechanizm C ++ pozwala nowym typom zdefiniowanym przez użytkownika na operacje wejścia / wyjścia bez zerwania istniejącego kodu. Wyobraź sobie chaos, jeśli wszyscy jednocześnie dodawali nowe niekompatybilne pola „%” do printf()i scanf()?!
  • Dziedziczony: <iostream>mechanizm C ++ jest zbudowany z prawdziwych klas, takich jak std::ostreami std::istream. W odróżnieniu od <cstdio>„s FILE*, są to prawdziwe klasy i stąd dziedziczone. Oznacza to, że możesz mieć inne zdefiniowane przez użytkownika rzeczy, które wyglądają i działają jak strumienie, a jednak robią wszystko, co chcesz, dziwne i wspaniałe. Możesz automatycznie użyć zillionów wierszy kodu we / wy napisanych przez użytkowników, których nawet nie znasz, i nie muszą oni wiedzieć o twojej klasie „rozszerzonego strumienia”.

Z drugiej strony printfjest znacznie szybszy, co może uzasadniać korzystanie z niego zamiast coutw bardzo szczególnych i ograniczonych przypadkach. Zawsze najpierw profiluj. (Zobacz na przykład http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)

Marcelo Cantos
źródło
2
Z drugiej strony jest biblioteka FastFormat ( fastformat.org ), oferująca jednocześnie bezpieczeństwo typu, ekspresję i wydajność. (Nie żeby tego jeszcze nie próbowałem ...)
xtofl
3
@Marcelo prawdopodobnie dlatego, że jest to dobre podsumowanie, ze wszystkimi cytowanymi. Formatowanie ... tak, to całkiem złe. Powinienem to sam naprawić, ale wygląda na to, że zajęli się tym inni (w tym ty), co oczywiście jest bardziej konstruktywne niż zwykłe marudzenie.
Mikeage
2
Od niedawna printf()ma być również rozszerzalny. Zobacz „haczyki printf” na stronie udrepper.livejournal.com/20948.html
Maxim Egorushkin,
4
@MaximYegorushkin: Standard printfnie ma takiej zdolności. Mechanizmy bibliotek nieprzenośnych nie są na tym samym poziomie co w pełni znormalizowana rozszerzalność iostreamów.
Ben Voigt
4
„Z drugiej strony printf jest znacznie szybszy” printf jest również czystszy i łatwiejszy w użyciu, dlatego unikam cout, gdy jest to możliwe.
FluorescentGreen5
43

Ludzie często twierdzą, że printfjest to znacznie szybsze. To w dużej mierze mit. Właśnie go przetestowałem z następującymi wynikami:

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

Wniosek: jeśli chcesz tylko nowe wiersze, użyj printf; w przeciwnym razie coutjest prawie tak samo szybkie lub nawet szybsze. Więcej informacji można znaleźć na moim blogu .

Dla jasności nie próbuję powiedzieć, że iostreamzawsze są lepsze niż printf; Próbuję tylko powiedzieć, że powinieneś podjąć świadomą decyzję w oparciu o rzeczywiste dane, a nie dzikie przypuszczenia oparte na jakimś powszechnym, mylącym założeniu.

Aktualizacja: Oto pełny kod, którego użyłem do testowania. Kompilowany g++bez żadnych dodatkowych opcji (oprócz -lrtczasu).

#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);
    }
}
Tomasz
źródło
5
W twoich wynikach printf bije łatwo (większość przypadków). Zastanawiam się, dlaczego polecasz stosowanie cout, jeśli chodzi o perf. Chociaż zgadzam się, że perf nie różni się zbytnio w realistycznych przypadkach ..
mishal153
3
@ mishal153: Próbuję tylko powiedzieć, że wydajność nie jest zbyt inna, więc powszechnie słyszana rada „nigdy nie używaj cout, ponieważ jest bardzo powolna” jest po prostu głupia. Zauważ, że cout ma oczywistą zaletę bezpieczeństwa typu, a często także czytelności. (Formatowanie zmiennoprzecinkowe za pomocą iostreams jest okropne ...)
Thomas
35
Istotna różnica między printf()i std::ostreampolega na tym, że poprzednie generuje wszystkie argumenty w jednym wywołaniu, podczas gdy std::ostreamdla każdego wywołuje osobne wywołanie <<. Test generuje tylko jeden argument i nowy wiersz, dlatego nie widać różnicy.
Maxim Egorushkin
12
Kompilator powinien mieć możliwość wstawiania tych wywołań. Ponadto printfmoże wykonywać wiele wywołań pod pokrywami do funkcji pomocniczych dla różnych specyfikatorów formatowania ... to, lub jest to monolityczna funkcja monolityczna. I znowu, ze względu na nachylenie, w ogóle nie powinno mieć znaczenia prędkość.
Thomas
4
Zmierzyłeś swój terminal. Użyj sprintflub fprintfi stringstreamlub fstream.
Ben Voigt
41

I cytuję :

W kategoriach wysokiego poziomu główne różnice to bezpieczeństwo typu (cstdio go nie ma), wydajność (większość implementacji iostreams jest wolniejsza niż cstdio) i rozszerzalność (iostreams pozwala na niestandardowe cele wyjściowe i bezproblemową produkcję typów zdefiniowanych przez użytkownika).

Kyle Rosendo
źródło
Zwłaszcza na Unixie, gdzie w POSIX-ie nigdy nie wiesz, jaki rozmiar ma naprawdę jeden z typedefs, więc potrzebujesz wielu rzutowań lub 99% programów po prostu ryzykujesz z% d. Zajęło to nawet długo, zanim% z przyszedł z C99. Ale dla time_t / off_t trwa wyszukiwanie instrukcji dotyczących prawidłowego formatu.
Lothar
30

Jedna to funkcja, która drukuje na standardowe wyjście. Drugi to obiekt, który udostępnia kilka funkcji składowych i przeciążenia operator<<tego wydruku na standardowe wyjście. Istnieje wiele innych różnic, które mógłbym wymienić, ale nie jestem pewien, o co ci chodzi.

Marcelo Cantos
źródło
12

Dla mnie rzeczywiste różnice, które skłoniłyby mnie do użycia „cout” zamiast „printf” to:

1) << Operator może być przeciążony dla moich klas.

2) Strumień wyjściowy dla cout można łatwo zmienić na plik: (: kopiuj wklej :)

#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) Uważam, że jest bardziej czytelny, szczególnie gdy mamy wiele parametrów.

Jednym z problemówcout są opcje formatowania. Formatowanie danych (precyzja, uzasadnienie itp.) printfJest łatwiejsze.

mishal153
źródło
1
to miłe. Skąd mam wiedzieć, że nikt nie modyfikuje globalnej mody w ten sposób w jakimś zagranicznym wątku bibliotecznym?
vp_arth
1
Możesz również łatwo zmienić printfplik, zastępując go fprintf...
CoffeeTableEspresso
5

Dwie kwestie, które nie zostały tu wymienione inaczej, które uważam za istotne:

1) coutprzewozi dużo bagażu, jeśli jeszcze nie korzystasz z STL. Dodaje ponad dwukrotnie więcej kodu do pliku obiektowego niż printf. Dotyczy to równieżstring i jest to główny powód, dla którego używam własnej biblioteki ciągów.

2) coutużywa przeciążonych <<operatorów, co uważam za niefortunne. Może to powodować zamieszanie, jeśli również używasz<< operatora zgodnie z jego przeznaczeniem (przesuń w lewo). Osobiście nie lubię przeciążać operatorów do celów związanych z ich przeznaczeniem.

Konkluzja: Użyję cout(i string), jeśli już używam STL. W przeciwnym razie staram się tego unikać.

Bill Weinman
źródło
4

W przypadku prymitywów prawdopodobnie nie ma to żadnego znaczenia, którego używasz. Mówię, że przydaje się, gdy chcesz wyprowadzać złożone obiekty.

Na przykład, jeśli masz zajęcia,

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

Teraz powyższe może nie wydawać się aż tak świetne, ale załóżmy, że musisz to wyprowadzić w wielu miejscach w kodzie. Mało tego, powiedzmy, że dodajesz pole „int d”. Dzięki cout musisz go zmienić tylko w jednym miejscu. Jednak z printf musiałbyś go zmienić w możliwie wielu miejscach i nie tylko, musisz sobie przypomnieć, które z nich wydrukować.

Powiedziawszy to, dzięki cout możesz skrócić wiele czasu poświęcanego na utrzymanie swojego kodu i nie tylko, że jeśli ponownie użyjesz obiektu „Coś” w nowej aplikacji, tak naprawdę nie musisz się martwić o wynik.

Daniel
źródło
Ponadto, jeśli chodzi o wydajność, powiedziałbym, że nie powinieneś generować niczego, jeśli twoja aplikacja jest przeznaczona do wydajności. Każde wyjście na standard jest raczej drogie i wolne. Mówię, że powinieneś tego unikać i generować tylko wtedy, gdy jest to absolutnie konieczne.
Daniel
pamiętaj, że twoja klasa może mieć członków prywatnych, do których nie masz tak łatwego dostępu z zewnątrz. Dzięki operatorowi wyjścia masz dokładnie jedną lokalizację, która musi być przyjazna dla twojej klasy, a teraz możesz wydrukować ją w dowolnym miejscu, nawet w kodzie, o którym nie wiedziałeś.
hochl
2

Oczywiście możesz napisać „coś” nieco lepiej, aby zachować konserwację:

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

I nieco rozszerzony test cout vs. printf, dodano test „double”, jeśli ktoś chce zrobić więcej testów (Visual Studio 2008, wydanie wersji pliku wykonywalnego):

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

Wynik to:

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
LuP
źródło
Wow, dlaczego jest endlo wiele mniej wydajny niż '\n'?
Nicholas Hamilton
1
Wydaje mi się, że dzieje się tak, ponieważ endlopróżnia bufor i \nnie robi tego, chociaż nie jestem pewien, czy jest to ostatecznie powód.
Caleb Xu,
To nie jest odpowiedź na pytanie, to raczej odpowiedź na pytanie Daniela i Thomasa .
Fabio mówi Przywróć Monikę
2

Chciałbym zaznaczyć, że jeśli chcesz grać wątkami w C ++, jeśli używasz cout, możesz uzyskać ciekawe wyniki.

Rozważ ten kod:

#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

Teraz dane wyjściowe są tasowane. Może także dawać różne wyniki, spróbuj wykonać kilka razy:

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

Możesz użyć, printfaby zrobić to dobrze, lub możesz użyć mutex.

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

Baw się dobrze!

Apollo
źródło
2
wtf threadnie powodują, że dane wyjściowe są szalone. Właśnie odtworzyłem i znalazłem jedno xyzi drugie ABC. Nie było manglingu b / w ABCas ABABAB.
Abhinav Gauniyal
1
Nie wiem, jak coutdziała z wątkami, ale wiem na pewno, że wyświetlany kod nie jest tym, którego użyłeś do uzyskania tych danych wyjściowych. Kod przekazuje ciąg "ABC"dla wątku 1 i "xyz"wątku 2, ale dane wyjściowe pokazują AAAi BBB. Napraw to, bo w tej chwili jest to mylące.
Fabio mówi Przywróć Monikę
1
cout<< "Hello";
printf("%s", "Hello"); 

Oba są używane do drukowania wartości. Mają zupełnie inną składnię. C ++ ma oba, C ma tylko printf.

scatman
źródło
19
... co? pomieszałeś coś?
xtofl
1
Naprawiono problem. -1, ponieważ wymagało naprawy, a odpowiedź pozostawia wiele do życzenia.
Yacoby,
3
Nazwy funkcji zostały odwrócone: cout użyto ze składnią printf, a printf użyto ze składnią cout. Nie powinno być nawet akceptowane!
Mahmoud Al-Qudsi
2
a główną wadą cout jest to, że używa operatora <<, co jest pełne i brzydkie i prawdopodobnie nadużycie operatora. :)
jalf
8
Chociaż nie jest to z pewnością najlepsza odpowiedź, nie rozumiem, w jaki sposób Scatman jest karany za swoją odpowiedź tylko dlatego, że została wybrana jako najlepsza odpowiedź. xbit ma zdecydowanie gorszą odpowiedź IMO, ale ma -1 głos. Nie twierdzę, że xbit powinien już zostać odrzucony, ale nie sądzę, aby sprawiedliwe było głosowanie scatman za błąd OP, niż to musi być ...
Jesse
1

Chciałbym powiedzieć, że brak rozszerzalności nie printfjest do końca prawdą: w
C jest to prawda. Ale w C nie ma prawdziwych klas.
W C ++ możliwe jest przeciążenie operatora rzutowania, więc przeciążenie char*operatora i użycie w printften sposób:

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

może być możliwe, jeśli Foo przeładuje dobrego operatora. Lub jeśli zrobiłeś dobrą metodę. Krótko mówiąc, printfjest tak rozszerzalny jakcout dla mnie.

Argument techniczny, który widzę dla strumieni C ++ (ogólnie ... nie tylko cout.) To:

  • Typesafety. (A tak przy okazji, jeśli chcę wydrukować taki, '\n'którego używamputchar('\n') ... nie użyję bomby nuklearnej do zabicia owada.).

  • Łatwiej się uczyć. (żadnych „skomplikowanych” parametrów do nauczenia, wystarczy użyć <<i>> operatorów)

  • Pracuj natywnie z std::string(bo printfjest std::string::c_str(), ale dla scanf?)

Bo printfwidzę:

  • Łatwiejsze lub co najmniej krótsze (pod względem zapisanych znaków) złożone formatowanie. O wiele bardziej czytelne dla mnie (chyba kwestia gustu).

  • Lepsza kontrola nad tym, co zrobiła funkcja (Zwróć ile znaków zostało napisanych, a jest %nformatyzator: „Nic nie wydrukowano. Argument musi być wskaźnikiem do podpisanej liczby wewnętrznej, w której przechowywana jest liczba zapisanych do tej pory znaków.” (Z printf - C ++ Reference )

  • Lepsze możliwości debugowania. Z tego samego powodu, co ostatni argument.

Moje osobiste preferencje dotyczą funkcji printf(i scanf), głównie dlatego, że uwielbiam krótkie wiersze i ponieważ nie sądzę, że trudno jest uniknąć problemów z drukowaniem tekstu. Jedyne, co wdrażam za pomocą funkcji w stylu C, to to, że std::stringnie jest obsługiwane. Musimy przejść char*przed, zanim go oddasz printf(z tym, std::string::c_str()jeśli chcemy czytać, ale jak pisać?)

bmorel
źródło
3
Kompilator nie ma informacji o typie funkcji varargs, więc nie skonwertuje rzeczywistego parametru (z wyjątkiem domyślnych promocji argumentów , takich jak standardowe promocje całki). Zobacz 5.2.2p7. Konwersja zdefiniowana przez użytkownika na char*nie będzie używana.
Ben Voigt,
Nawet gdyby to zadziałało, nie byłby to przykład rozszerzenia sprintf, tylko sprytny hack, aby dać sprintfowi to, czego się spodziewa, i ignoruje pewne poważne problemy, takie jak miejsce char*życia i jak długo, a także niebezpieczeństwa zdefiniowane przez użytkownika niejawne obsady.
Marcelo Cantos,
1

Więcej różnic: „printf” zwraca wartość całkowitą (równą liczbie drukowanych znaków), a „cout” nic nie zwraca

I.

cout << "y = " << 7; nie jest atomowy.

printf("%s = %d", "y", 7); jest atomowy.

cout wykonuje kontrolę typu, printf nie.

Nie ma odpowiednika iostream "% d"

skan
źródło
3
coutnic nie zwraca, ponieważ jest to obiekt, a nie funkcja. operator<<zwraca coś (zwykle jest to lewy operand, ale w przypadku błędu występuje wartość fałszywa). I w jakim sensie jest to printfwezwanie „atomowy”?
Keith Thompson,
9
To jest jak bomba atomowa. printf("%s\n",7);
artless noise
@artlessnoise czekać, dlaczego błąd segmentacji? %sjest ?
Abhinav Gauniyal
1
Taki jest sens stwierdzenia „bomba atomowa”. A printf % s argument musi mieć ważny wskaźnik do NUL łańcuch. Zakres pamięci „7” (wskaźnik) zwykle nie jest prawidłowy; błąd segmentacji może mieć szczęście. W niektórych systemach „7” może wydrukować dużo śmieci na konsolę i trzeba by na nie spojrzeć przez jeden dzień, zanim program się zatrzyma. Innymi słowy, jest to zła rzecz printf. Narzędzia analizy statycznej mogą wychwycić wiele z tych problemów.
bezgłośny hałas
Chociaż technicznie printfnie sprawdza on typów, nigdy nie korzystałem z kompilatora, który nie ostrzegałby mnie przed błędami podczas pisania printf...
CoffeeTableEspresso
1

TL; DR: Zawsze ufaj wygenerowanemu kodowi maszynowemu , wydajności , czytelności i czasowi kodowania, zanim zaufasz losowym komentarzom online, w tym również temu.

Nie jestem ekspertem. Właśnie słyszałem dwóch współpracowników rozmawiających o tym, jak powinniśmy unikać używania C ++ w systemach wbudowanych z powodu problemów z wydajnością. Co ciekawe, zrobiłem test porównawczy oparty na prawdziwym zadaniu projektowym.

W tym zadaniu musieliśmy zapisać konfigurację do pamięci RAM. Coś jak:

kawa = gorący
cukier = brak
mleka = pierś
mac = AA: BB: CC: DD: EE: FF

Oto moje programy testowe (tak, wiem, że OP pytał o printf (), a nie fprintf (). Spróbuj uchwycić istotę, a przy okazji, link OP wskazuje na fprintf ().)

Program C:

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

Program C ++:

//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();

Zrobiłem co w mojej mocy, aby je wypolerować, zanim zapętliłem je oba 100 000 razy. Oto wyniki:

Program C:

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

Program C ++:

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

Rozmiar pliku obiektu:

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

Wniosek: na mojej bardzo specyficznej platformie , z bardzo specyficznym procesorem , z bardzo specyficzną wersją jądra Linuksa , aby uruchomić program, który jest skompilowany z bardzo konkretną wersją GCC , w celu wykonania bardzo konkretnego zadania , powiedziałbym podejście C ++ jest bardziej odpowiednie, ponieważ działa znacznie szybciej i zapewnia znacznie lepszą czytelność. Z drugiej strony C oferuje niewielką powierzchnię, moim zdaniem nie znaczy prawie nic, ponieważ rozmiar programu nie jest naszym problemem.

Pamiętaj, YMMV.

Wesley
źródło
Nie zgadzam się, że C ++ jest bardziej czytelny w tym przykładzie, ponieważ twój przykład pakuje wiele linii w jedno wywołanie printf. Jest to naturalnie mniej czytelne niż sposób, w jaki napisałeś kod C ++, i rzadko jest wykonywane w C, ponieważ jest trudne do odczytania i trudne do utrzymania. Rzetelne porównanie rozdzieliłoby C na osobne printfs, jeden dla linii zasięgu.
maharvey67
1
@ maharvey67 To prawda, co powiedziałeś. Jednak przykład, który podałem w C, dotyczył wydajności. Wywołanie spakowane w fprintf było już o dwie sekundy wolniejsze niż równoważność C ++. Gdybym sprawił, że kod C będzie czytelny, może być jeszcze wolniejszy. Oświadczenie: To było rok temu i pamiętam, że starałem się jak najlepiej wypolerować zarówno C, jak i C ++. Nie miałem dowodu, że oddzielne połączenia z fprintf będą szybsze niż jedno połączenie, ale powód, dla którego to zrobiłem w ten sposób, prawdopodobnie wskazuje, że tak nie było.
Wesley,
0

Nie jestem programistą, ale byłem inżynierem czynników ludzkich. Uważam, że język programowania powinien być łatwy do nauczenia się, rozumienia i używania, a to wymaga prostej i spójnej struktury językowej. Chociaż wszystkie języki są symboliczne, a zatem u ich podstaw są arbitralne, istnieją konwencje, a ich przestrzeganie ułatwia naukę i używanie języka.

Istnieje ogromna liczba funkcji w C ++ i innych językach zapisanych jako funkcja (parametr), składnia, która pierwotnie była używana do relacji funkcjonalnych w matematyce w epoce przed komputerem. printf()postępuje zgodnie z tą składnią i jeśli autorzy C ++ chcieli stworzyć inną logicznie odmienną metodę odczytu i zapisu plików, mogliby po prostu stworzyć inną funkcję przy użyciu podobnej składni.

W Pythonie możemy oczywiście drukować przy użyciu również dość standardowej object.methodskładni, tj. Variablename.print, ponieważ zmienne są obiektami, ale w C ++ nimi nie są.

Nie przepadam za składnią cout, ponieważ operator << nie przestrzega żadnych reguł. Jest to metoda lub funkcja, tzn. Bierze parametr i coś z nim robi. Jest jednak napisany tak, jakby był operatorem porównania matematycznego. Jest to złe podejście z ludzkiego punktu widzenia.

Daniel Woodard
źródło
-1

printfjest funkcją, podczas gdy coutjest zmienną.

Jan
źródło
6
Dokonałem wycofania, ponieważ chociaż sama odpowiedź może być błędna, nadal jest to prawdziwa odpowiedź. Jeśli uważasz (poprawnie), że odpowiedź jest nieprawidłowa, masz dwie opcje: 1) dodaj komentarz lub 2) dodaj nową odpowiedź (lub wykonaj obie te czynności). Nie zmieniaj czyjejś odpowiedzi na taką, która mówi coś zupełnie innego niż zamierzony przez autora.
Mark
1
printfjest funkcją, ale printf()jest wywołaniem funkcji =)
vp_arth
cout to obiekt, a nie zmienna.
Lin