'printf' frente a 'cout' en C++

Resuelto hero asked hace 14 años • 0 respuestas

¿ Cuál es la diferencia entre printf()y couten C++?

hero avatar May 20 '10 16:05 hero
Aceptado

Me sorprende que todos en esta pregunta afirmen que std::coutes mucho mejor que printf, incluso si la pregunta solo pedía diferencias. Ahora, hay una diferencia: std::coutes C++ y printfes C (sin embargo, puedes usarlo en C++, como casi cualquier otra cosa de C). Ahora, seré honesto aquí; Ambos printfy std::couttienen sus ventajas.

Diferencias reales

Extensibilidad

std::coutes extensible. Sé que la gente dirá que eso printftambién es extensible, pero dicha extensión no se menciona en el estándar C (por lo que tendría que usar funciones no estándar, pero ni siquiera existe una característica no estándar común), y dichas extensiones son una letra. (por lo que es fácil entrar en conflicto con un formato ya existente).

A diferencia de printf, std::coutdepende completamente de la sobrecarga del operador, por lo que no hay problema con los formatos personalizados: todo lo que debe hacer es definir una subrutina tomando std::ostreamcomo primer argumento y su tipo como segundo. Como tal, no hay problemas de espacio de nombres: siempre que tenga una clase (que no esté limitada a un carácter), puede tener una std::ostreamsobrecarga de trabajo para ella.

Sin embargo, dudo que mucha gente quiera ampliar ostream(para ser honesto, rara vez vi extensiones de este tipo, incluso si son fáciles de hacer). Sin embargo, está aquí si lo necesita.

Sintaxis

Como se puede observar fácilmente, ambos printfusan std::coutuna sintaxis diferente. printfutiliza la sintaxis de función estándar utilizando cadenas de patrones y listas de argumentos de longitud variable. En realidad, printfes una de las razones por las que C los tiene: printflos formatos son demasiado complejos para poder utilizarse sin ellos. Sin embargo, std::coututiliza una API diferente: la operator <<API que se devuelve sola.

Generalmente, eso significa que la versión C será más corta, pero en la mayoría de los casos no importará. La diferencia se nota cuando imprimes muchos argumentos. Si tiene que escribir algo como Error 2: File not found., asumiendo el número de error, y su descripción es un marcador de posición, el código se vería así. Ambos ejemplos funcionan de manera idéntica (bueno, más o menos, std::endlen realidad vacía el búfer).

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

Si bien esto no parece demasiado loco (es solo dos veces más largo), las cosas se vuelven más locas cuando realmente formateas los argumentos, en lugar de simplemente imprimirlos. Por ejemplo, imprimir algo así 0x0424es una locura. Esto se debe a std::coutla mezcla de valores de estado y reales. Nunca vi un lenguaje en el que algo así std::setfillfuera un tipo (que no sea C++, por supuesto). printfsepara claramente los argumentos y el tipo real. Realmente preferiría mantener la printfversión (incluso si parece un poco críptica) en comparación con iostreamla versión (ya que contiene demasiado ruido).

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

Traducción

Aquí es donde printfreside la verdadera ventaja. La printfcadena de formato es bueno... una cadena. Eso hace que sea realmente fácil de traducir, en comparación con operator <<el abuso de iostream. Suponiendo que la gettext()función se traduce y desea mostrarla Error 2: File not found., el código para obtener la traducción de la cadena de formato mostrada anteriormente se vería así:

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

Ahora, supongamos que traducimos a Fictionish, donde el número de error está después de la descripción. La cadena traducida se vería así %2$s oru %1$d.\n. Ahora bien, ¿cómo hacerlo en C++? Bueno, no tengo idea. Supongo que puedes falsificar iostreamlas construcciones printfa las que puedes pasar gettext, o algo así, con fines de traducción. Por supuesto, $no es el estándar C, pero es tan común que, en mi opinión, es seguro usarlo.

No tener que recordar/buscar la sintaxis específica del tipo de entero

C tiene muchos tipos de números enteros, al igual que C++. std::coutmaneja todos los tipos por usted, mientras que printfrequiere una sintaxis específica dependiendo de un tipo entero (hay tipos no enteros, pero el único tipo no entero que usará en la práctica printfes const char *(cadena C, se puede obtener usando to_cel método de std::string)). Por ejemplo, para imprimir size_t, necesita usar %zu, mientras que int64_tserá necesario usar %"PRId64". Las tablas están disponibles en http://en.cppreference.com/w/cpp/io/c/fprintf y http://en.cppreference.com/w/cpp/types/integer .

No puedes imprimir el byte NUL,\0

Debido a que printfutiliza cadenas C en lugar de cadenas C++, no puede imprimir bytes NUL sin trucos específicos. En ciertos casos, es posible utilizar %cwith '\0'como argumento, aunque es claramente un truco.

Diferencias que a nadie le importan

Actuación

Actualización: Resulta que iostreames tan lento que generalmente es más lento que su disco duro (si redirige su programa al archivo). Deshabilitar la sincronización con stdiopuede ayudar si necesita generar muchos datos. Si el rendimiento es una preocupación real (en lugar de escribir varias líneas en STDOUT), simplemente use printf.

Todo el mundo piensa que le importa el rendimiento, pero nadie se molesta en medirlo. Mi respuesta es que la E/S es un cuello de botella de todos modos, sin importar si usas printfo iostream. Creo que printf podría ser más rápido con un vistazo rápido al ensamblaje (compilado con clang usando la -O3opción del compilador). Suponiendo mi ejemplo de error, printfel ejemplo realiza muchas menos llamadas que el coutejemplo. Esto es int maincon 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:

Puede notar fácilmente que dos cadenas y 2(número) se incluyen como printfargumentos. Eso es todo; no hay nada más. A modo de comparación, esto se iostreamcompila en ensamblador. No, no hay alineación; cada operator <<llamada significa otra llamada con otro conjunto de argumentos.

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:

Sin embargo, para ser honesto, esto no significa nada, ya que la E/S es el cuello de botella de todos modos. Sólo quería mostrar que iostreamno es más rápido porque es "seguro para escribir". La mayoría de las implementaciones de C implementan printfformatos usando goto calculado, por lo que printfes lo más rápido posible, incluso sin que el compilador lo sepa printf(no es que no lo sean; algunos compiladores pueden optimizar printfen ciertos casos; la cadena constante que termina en \ngeneralmente se optimiza para puts) .

Herencia

No sé por qué querrías heredar ostream, pero no me importa. También es posible FILE.

class MyFile : public FILE {}

Tipo de seguridad

Es cierto que las listas de argumentos de longitud variable no son seguras, pero eso no importa, ya que los compiladores de C populares pueden detectar problemas con el printfformato de cadena si habilita las advertencias. De hecho, Clang puede hacerlo sin habilitar advertencias.

$ 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);
     ^
Kamila Borowska avatar Nov 27 '2013 09:11 Kamila Borowska

De las preguntas frecuentes sobre C++ :

[15.1] ¿Por qué debería utilizar <iostream> en lugar del tradicional <cstdio>?

Aumente la seguridad de los tipos, reduzca los errores, permita la extensibilidad y proporcione heredabilidad.

printf()Podría decirse que no está roto y scanf()quizás sea habitable a pesar de ser propenso a errores; sin embargo, ambos están limitados con respecto a lo que puede hacer la E/S de C++. La E/S de C++ (usando <<y >>) es, en relación con C (usando printf()y scanf()):

  • Más seguridad de tipos: con <iostream>, el compilador conoce estáticamente el tipo de objeto que se está realizando E/S. Por el contrario, <cstdio>utiliza campos "%" para determinar los tipos dinámicamente.
  • Menos propenso a errores: con <iostream>, no hay tokens "%" redundantes que deban ser coherentes con los objetos reales a los que se realiza E/S. Al eliminar la redundancia se elimina una clase de errores.
  • Extensible: el <iostream>mecanismo de C++ permite realizar E/S de nuevos tipos definidos por el usuario sin romper el código existente. ¡Imagínese el caos si todos agregaran simultáneamente nuevos campos "%" incompatibles a printf()y scanf()?!
  • Heredable: el <iostream>mecanismo de C++ se construye a partir de clases reales como std::ostreamy std::istream. A diferencia de <cstdio>'s FILE*, estas son clases reales y, por tanto, heredables. Esto significa que puedes tener otras cosas definidas por el usuario que se ven y actúan como transmisiones, pero que hacen las cosas extrañas y maravillosas que quieras. Automáticamente podrá utilizar millones de líneas de código de E/S escritas por usuarios que ni siquiera conoce, y ellos no necesitan conocer su clase de "flujo extendido".

Por otro lado, printfes significativamente más rápido, lo que puede justificar su uso preferente couten casos muy concretos y limitados. Perfil siempre primero. (Ver, por ejemplo, http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout/ )

Marcelo Cantos avatar May 20 '2010 09:05 Marcelo Cantos

La gente suele afirmar que printfes mucho más rápido. Esto es en gran medida un mito. Lo acabo de probar, con los siguientes resultados:

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

Conclusión: si solo quieres nuevas líneas, usa printf; de lo contrario, coutes casi tan rápido, o incluso más rápido. Se pueden encontrar más detalles en mi blog .

Para ser claro, no estoy tratando de decir que iostreams siempre sea mejor que printf; Sólo estoy tratando de decir que usted debe tomar una decisión informada basada en datos reales, no una suposición descabellada basada en alguna suposición común y engañosa.

Actualización: aquí está el código completo que utilicé para las pruebas. Compilado g++sin opciones adicionales (aparte -lrtdel tiempo).

#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);
    }
}
Thomas avatar May 20 '2010 11:05 Thomas

Y cito :

En términos de alto nivel, las principales diferencias son la seguridad de tipos (cstdio no la tiene), el rendimiento (la mayoría de las implementaciones de iostreams son más lentas que las de cstdio) y la extensibilidad (iostreams permite objetivos de salida personalizados y una salida perfecta de tipos definidos por el usuario).

Kyle Rosendo avatar May 20 '2010 09:05 Kyle Rosendo