526 votes

printf et cout en C++.

Quelle est la différence entre printf() y cout en C++ ?

528voto

xfix Points 2890

Je suis surpris que tout le monde dans cette question affirme que std::cout est bien meilleur que printf même si la question ne demandait que des différences. Maintenant, il y a une différence - std::cout est C++, et printf est en C (toutefois, vous pouvez l'utiliser en C++, tout comme casi tout autre élément de C). Maintenant, je vais être honnête ici ; les deux printf y std::cout ont leurs avantages.

Différences réelles

Extensibilité

std::cout est extensible. Je sais que les gens vont dire que printf est également extensible, mais une telle extension n'est pas mentionnée dans la norme C (il faudrait donc utiliser des fonctionnalités non standard - mais il n'existe même pas de fonctionnalité non standard commune), et ces extensions sont d'une seule lettre (il est donc facile d'entrer en conflit avec un format déjà existant).

Contrairement à printf , std::cout dépend entièrement de la surcharge des opérateurs, de sorte qu'il n'y a pas de problème avec les formats personnalisés - il suffit de définir une sous-routine prenant le format std::ostream comme premier argument et votre type comme second. En tant que tel, il n'y a pas de problème d'espace de noms - tant que vous avez une classe (qui n'est pas limitée à un caractère), vous pouvez avoir des fonctions std::ostream surcharge pour elle.

Cependant, je doute que beaucoup de personnes veuillent prolonger ostream (pour être honnête, j'ai rarement vu de telles extensions, même si elles sont faciles à réaliser). Cependant, elle est là si vous en avez besoin.

Syntaxe

Comme on peut facilement le constater, les deux printf y std::cout utilisent une syntaxe différente. printf utilise une syntaxe de fonction standard utilisant des chaînes de motifs et des listes d'arguments de longueur variable. En fait, printf Il y a une raison pour laquelle C les a - printf sont trop complexes pour être utilisables sans eux. Cependant, std::cout utilise une API différente - l operator << API qui se renvoie elle-même.

En général, cela signifie que la version C sera plus courte, mais dans la plupart des cas, cela n'aura aucune importance. La différence est perceptible lorsque vous imprimez de nombreux arguments. Si vous devez écrire quelque chose comme Error 2: File not found. En supposant que le numéro de l'erreur et sa description sont des caractères de remplissage, le code ressemblerait à ceci. Les deux exemples fonctionnent de manière identique (enfin, en quelque sorte, std::endl vide effectivement le tampon).

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

Bien que cela ne semble pas trop fou (c'est juste deux fois plus long), les choses deviennent plus folles lorsque vous formatez réellement les arguments, au lieu de simplement les imprimer. Par exemple, l'impression de quelque chose comme 0x0424 est juste fou. Ceci est causé par std::cout mélangeant les valeurs d'état et les valeurs réelles. Je n'ai jamais vu un langage où quelque chose comme std::setfill serait un type (autre que C++, bien sûr). printf sépare clairement les arguments et le type réel. Je préférerais vraiment maintenir le printf (même s'il a l'air un peu cryptique) par rapport aux iostream version (car elle contient trop de bruit).

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

Traduction

C'est là que le véritable avantage de printf mensonges. Le site printf La chaîne de format est bien... une chaîne. Cela le rend vraiment facile à traduire, comparé à operator << l'abus de iostream . En supposant que le gettext() traduit la fonction, et vous voulez montrer Error 2: File not found. le code pour obtenir la traduction de la chaîne de format présentée précédemment ressemblerait à ceci :

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

Maintenant, supposons que nous traduisions en Fictionish, où le numéro d'erreur se trouve après la description. La chaîne traduite ressemblerait à %2$s oru %1$d.\n . Maintenant, comment le faire en C++ ? Eh bien, je n'en ai aucune idée. Je suppose que vous pouvez faire des faux iostream qui construit printf que vous pouvez passer à gettext ou autre, à des fins de traduction. Bien sûr, $ n'est pas la norme C, mais elle est si courante qu'elle peut être utilisée sans risque à mon avis.

Ne pas avoir à se souvenir de la syntaxe spécifique des types d'entiers.

Le C a beaucoup de types d'entiers, et le C++ aussi. std::cout gère tous les types pour vous, tandis que printf requiert une syntaxe spécifique dépendant d'un type d'entier (il existe des types non entiers, mais le seul type non entier que vous utiliserez en pratique avec printf es const char * (chaîne C, peut être obtenue en utilisant to_c méthode de std::string )). Par exemple, pour imprimer size_t vous devez utiliser %zu alors que int64_t il faudra utiliser %"PRId64" . Les tableaux sont disponibles à l'adresse suivante http://en.cppreference.com/w/cpp/io/c/fprintf y http://en.cppreference.com/w/cpp/types/integer .

Vous ne pouvez pas imprimer l'octet NUL, \0

Parce que printf utilise des chaînes de caractères C par opposition aux chaînes de caractères C++, il ne peut pas imprimer l'octet NUL sans astuces spécifiques. Dans certains cas, il est possible d'utiliser %c con '\0' comme un argument, bien que ce soit clairement un hack.

Des différences dont personne ne se soucie

Performance

Mise à jour : Il s'avère que iostream est si lent qu'il est généralement plus lent que votre disque dur (si vous redirigez votre programme vers un fichier). Désactiver la synchronisation avec stdio peut aider, si vous avez besoin de sortir beaucoup de données. Si les performances sont un réel souci (par rapport à l'écriture de plusieurs lignes dans STDOUT), utilisez simplement printf .

Tout le monde pense se soucier des performances, mais personne ne se soucie de les mesurer. Ma réponse est que les E/S sont de toute façon un goulot d'étranglement, peu importe si vous utilisez printf o iostream . Je pense que printf pourrait être plus rapide d'après un rapide coup d'œil dans l'assemblage (compilé avec clang en utilisant la fonction -O3 option du compilateur). En supposant que mon exemple d'erreur, printf fait beaucoup moins d'appels que l'exemple cout exemple. C'est int main con 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:

Vous pouvez facilement remarquer que deux chaînes de caractères, et 2 (nombre) sont poussés comme printf arguments. C'est à peu près tout ; il n'y a rien d'autre. À titre de comparaison, voici iostream compilé en assemblage. Non, il n'y a pas d'inlining ; chaque simple operator << signifie un autre appel avec un autre ensemble d'arguments.

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:

Cependant, pour être honnête, cela ne signifie rien, car les E/S sont de toute façon le goulot d'étranglement. Je voulais juste montrer que iostream n'est pas plus rapide parce qu'elle est "sûre en type". La plupart des implémentations C mettent en œuvre printf en utilisant le goto calculé, donc le printf est aussi rapide qu'il peut l'être, même sans que le compilateur soit conscient de printf (non pas qu'ils ne le soient pas - certains compilateurs peuvent optimiser printf dans certains cas - chaîne constante se terminant par \n est généralement optimisé pour puts ).

Héritage

Je ne sais pas pourquoi vous voudriez hériter ostream mais je m'en fiche. C'est possible avec FILE aussi.

class MyFile : public FILE {}

Sécurité de type

Il est vrai que les listes d'arguments de longueur variable n'ont pas de sécurité, mais cela n'a pas d'importance, car les compilateurs C les plus courants peuvent détecter les problèmes avec les listes d'arguments de longueur variable. printf chaîne de format si vous activez les avertissements. En fait, Clang peut le faire sans activer les avertissements.

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

30 votes

Vous dites que les E/S sont le goulot d'étranglement de toute façon. De toute évidence, vous n'avez jamais a testé cette hypothèse. Je me cite : "D'autre part, la version iostreams, à 75,3 Mo/s, ne peut pas mettre en mémoire tampon les données assez rapidement pour suivre un disque dur. C'est mauvais, et il ne fait même pas encore de travail réel. Je ne pense pas avoir des attentes trop élevées lorsque je dis que ma bibliothèque d'E/S devrait être capable de saturer mon contrôleur de disque."

7 votes

@BenVoigt : J'admets que j'essaie d'éviter le C++ quand c'est possible. J'ai beaucoup essayé de l'utiliser, mais il était plus ennuyeux et moins facile à maintenir que les autres langages de programmation que j'ai utilisés. C'est encore une autre raison pour moi d'éviter le C++ - ce n'est même pas rapide (ce n'est même pas iostream - toute la bibliothèque C++ est lente dans la plupart des implémentations, peut-être avec l'exception de std::sort qui est en quelque sorte étonnamment rapide par rapport à qsort (2 fois), au prix de la taille de l'exécutable).

3 votes

Personne ici n'a mentionné de problèmes en environnement parallèle lors de l'utilisation de cout.

228voto

Mikeage Points 3796

De la FAQ C++ :

[15.1] Pourquoi devrais-je utiliser <iostream> au lieu de la traditionnelle <cstdio> ?

Augmenter la sécurité des types, réduire les erreurs, permettre l'extensibilité et fournir l'héritabilité.

printf() n'est pas cassé, et scanf() est peut-être vivable, bien qu'elle soit sujette aux erreurs, mais toutes deux sont limitées par rapport à ce que les E/S C++ peuvent faire. Les E/S C++ (utilisant << y >> ) est, par rapport à C (en utilisant printf() y scanf() ) :

  • Plus sûr au niveau du type : Avec <iostream> , connu statiquement par le compilateur. Dans revanche, <cstdio> utilise les champs "%" pour pour déterminer les types de manière dynamique.
  • Moins de risques d'erreurs : Avec <iostream> , "%" redondants qui doivent être cohérents [ ] La suppression de la redondance élimine une classe d'erreurs.
  • Extensible : Le C++ <iostream> m [ ] code existant. Imaginez le chaos si tout le monde ajoutait simultanément de nouvelles incompa printf() y scanf() ? !
  • Héritable : Le C++ <iostream> m telles que std::ostream y std::istream . Contrairement à <cstdio> ' FILE* , donc héritables. Cela signifie que vous pouvez avoir d'autres choses définies par l'utilisateur qui qui ressemblent et agissent comme des flux, mais qui font toutes les choses étranges et merveilleuses choses que vous voulez. Vous pouvez automatiquement automatiquement les zillions de lignes de de code d'E/S écrites par des utilisateurs que vous ne connaissez pas. que vous ne connaissez même pas, et ils n'ont pas besoin de de connaître votre classe "extended stream". classe.

D'un autre côté, printf est significativement plus rapide, ce qui peut justifier de l'utiliser de préférence à cout en très des cas spécifiques et limités. Il faut toujours commencer par le profil. (Voir, par exemple, http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)

2 votes

D'un autre côté, il y a la bibliothèque FastFormat ( fastformat.org ), offrant à la fois la sécurité des types, l'expressivité et la performance. (Non pas que je l'ai encore essayé...)

1 votes

Comment un morceau de copier-coller aussi mal présenté, sans aucune considération pour l'échappement (d'où une série d'omissions inexplicables dans le texte) peut-il se retrouver dans une situation où il n'est pas possible d'obtenir des résultats satisfaisants ? neuf des votes positifs ?

0 votes

@Marcelo Cantos : Je soupçonne une réaction négative à la réponse acceptée.

57voto

Thomas Points 63635

Les gens prétendent souvent que printf est beaucoup plus rapide. C'est en grande partie un mythe. Je viens de le tester, avec les résultats suivants :

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

Conclusion : si vous voulez seulement des nouvelles lignes, utilisez printf ; sinon, cout est presque aussi rapide, voire même plus rapide. Vous trouverez plus de détails sur mon blog .

Pour être clair, je n'essaie pas de dire que iostream sont toujours meilleurs que printf J'essaie simplement de dire que vous devriez prendre une décision éclairée, basée sur des données réelles, et non sur une supposition hasardeuse fondée sur une hypothèse commune et trompeuse.

Mise à jour : Voici le code complet que j'ai utilisé pour les tests. Compilé avec g++ sans aucune option supplémentaire (à l'exception de -lrt pour le calendrier).

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

0 votes

"plus de détails sur votre blog" ? Pas du tout. Votre blog manque des mêmes détails que cet article : il ne nous dit pas ce que vous avez testé ni comment vous l'avez testé.

0 votes

@jalf : Tu crois ? J'ai fait un lien au lieu de poster le code complet ici, car il est un peu long et répétitif. J'y ai bien écrit ce que j'ai testé et comment... le texte sur mon blog devrait être suffisant pour reproduire le code. Mais je l'ai édité dans mon post... est-ce suffisant ?

5 votes

Dans vos résultats, printf bat facilement cout (dans la majorité des cas). Je me demande pourquoi vous recommandez d'utiliser cout lorsqu'il s'agit de perf. Bien que je convienne que la performance n'est pas trop différente dans des cas réalistes

44voto

Kyle Rozendo Points 15606

Et je citation :

En termes de haut niveau, les principales différences sont la sécurité des types (cstdio ne la possède pas), les performances (la plupart des implémentations de iostreams sont sont plus lentes que celles de cstdio) et l'extensibilité (iostreams permet des cibles de des cibles de sortie personnalisées et la sortie transparente de types définis par l'utilisateur).

0 votes

En particulier sur unix où, avec POSIX, on ne sait jamais quelle est la taille réelle d'un des typedefs, ce qui nécessite beaucoup de casts ou, comme 99% des programmes, on risque simplement avec %d. Il a même fallu attendre longtemps avant que %z n'apparaisse dans C99. Mais pour time_t/off_t la quête de l'instruction de format correcte continue.

31voto

Marcelo Cantos Points 91211

L'une d'entre elles est une fonction qui imprime sur la sortie standard. L'autre est un objet qui fournit plusieurs fonctions membres et surcharges de la fonction operator<< qui s'impriment sur la sortie standard. Il existe de nombreuses autres différences que je pourrais énumérer, mais je ne suis pas sûr de ce que vous recherchez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X