«printf» против «cout» в C ++ - PullRequest
315 голосов
/ 20 мая 2010

В чем разница между printf() и cout в C ++?

Ответы [ 16 ]

266 голосов
/ 27 ноября 2013

Я удивлен тем, что все в этом вопросе утверждают, что std::cout намного лучше, чем printf, даже если вопрос только что спросил о различиях. Теперь есть разница - std::cout - это C ++, а printf - это C (однако вы можете использовать его в C ++, точно так же, как почти что-нибудь еще из C). Теперь я буду честен здесь; printf и std::cout имеют свои преимущества.

Реальные различия

Расширяемость

std::cout является расширяемым. Я знаю, что люди скажут, что printf тоже расширяемо, но такое расширение не упоминается в стандарте C (поэтому вам придется использовать нестандартные функции - но даже не существует общих нестандартных функций), и такие расширения одна буква (так что легко конфликтовать с уже существующим форматом).

В отличие от printf, std::cout полностью зависит от перегрузки операторов, поэтому нет никаких проблем с пользовательскими форматами - все, что вам нужно сделать, это определить подпрограмму, принимающую std::ostream в качестве первого аргумента и ваш тип в качестве второго. Таким образом, нет проблем с пространством имен - если у вас есть класс (который не ограничен одним символом), вы можете иметь рабочую перегрузку std::ostream для него.

Однако я сомневаюсь, что многие люди захотят расширить ostream (если честно, я редко видел такие расширения, даже если их легко сделать). Тем не менее, это здесь, если вам это нужно.

Синтаксис

Как можно легко заметить, и printf, и std::cout используют разный синтаксис. printf использует стандартный синтаксис функции с использованием шаблонной строки и списков аргументов переменной длины. На самом деле, printf является причиной, по которой они есть в C - форматы printf слишком сложны, чтобы их можно было использовать без них. Однако std::cout использует другой API - API operator <<, который возвращает себя.

Обычно это означает, что версия C будет короче, но в большинстве случаев это не имеет значения. Разница заметна, когда вы печатаете много аргументов. Если вам нужно написать что-то вроде Error 2: File not found., предполагая номер ошибки и ее описание является заполнителем, код будет выглядеть следующим образом. Оба примера работают одинаково (ну, вроде, std::endl фактически очищает буфер).

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

Хотя это не выглядит слишком сумасшедшим (просто в два раза дольше), все становится более сумасшедшим, когда вы на самом деле форматируете аргументы, а не просто печатаете их. Например, печать чего-то вроде 0x0424 просто сумасшедшая. Это вызвано std::cout смешиванием состояния и фактических значений. Я никогда не видел языка, в котором что-то вроде std::setfill было бы типом (кроме C ++, конечно). printf четко разделяет аргументы и фактический тип. Я действительно предпочел бы сохранить версию printf (даже если она выглядит несколько загадочно) по сравнению с версией iostream (так как она содержит слишком много шума).

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

Перевод

В этом и заключается реальное преимущество printf. Строка формата printf - это хорошо ... строка. Это позволяет легко переводить по сравнению с operator << злоупотреблением iostream. Предполагая, что функция gettext() преобразуется, и вы хотите показать Error 2: File not found., код для получения перевода ранее показанной строки формата будет выглядеть следующим образом:

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

Теперь давайте предположим, что мы переводим на Fictionish, где номер ошибки после описания. Переведенная строка будет выглядеть как %2$s oru %1$d.\n. Теперь, как это сделать в C ++? Ну, я понятия не имею. Я предполагаю, что вы можете сделать фальшивку iostream, которая создает printf, которую вы можете передать gettext, или что-то еще, для целей перевода. Конечно, $ не является стандартом C, но, на мой взгляд, он настолько распространен, что его безопасно использовать.

Не нужно запоминать / искать определенный синтаксис целочисленных типов

C имеет много целочисленных типов, также как и C ++. std::cout обрабатывает все типы для вас, в то время как printf требует определенного синтаксиса в зависимости от целочисленного типа (есть нецелые типы, но единственный нецелочисленный тип, который вы будете использовать на практике с printf, это const char * ( Строка C, может быть получена с использованием to_c метода std::string)). Например, для печати size_t необходимо использовать %zd, а для int64_t потребуется %"PRId64". Таблицы доступны в http://en.cppreference.com/w/cpp/io/c/fprintf и http://en.cppreference.com/w/cpp/types/integer.

Вы не можете напечатать байт NUL, \0

Поскольку printf использует строки C, а не строки C ++, он не может печатать NUL-байт без специальных уловок. В некоторых случаях можно использовать %c с '\0' в качестве аргумента, хотя это явно подделка.

Различия никого не волнуют

Performance

Обновление: получается, что iostream настолько медленный, что обычно он медленнее, чем ваш жесткий диск (если вы перенаправляете свою программу в файл). Отключение синхронизации с stdio может помочь, если вам нужно вывести много данных. Если производительность действительно важна (в отличие от записи нескольких строк в STDOUT), просто используйте printf.

Каждый думает, что ему небезразлична производительность, но никто не мешает ее измерить. Мой ответ таков, что ввод-вывод в любом случае является узким местом, независимо от того, используете ли вы printf или iostream. Я думаю, что printf может быть быстрее из быстрого просмотра сборки (скомпилированной с помощью clang с использованием опции компилятора -O3). Предполагая мой пример ошибки, пример printf делает намного меньше вызовов, чем пример cout. Это int main с 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:

Вы можете легко заметить, что две строки и 2 (число) выдвигаются как printf аргументы. Это об этом; больше ничего нет Для сравнения: iostream скомпилировано в сборку. Нет, там нет встраивания; каждый отдельный operator << вызов означает еще один вызов с другим набором аргументов.

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:

Однако, если честно, это ничего не значит, поскольку ввод-вывод в любом случае является узким местом. Я просто хотел показать, что iostream не быстрее, потому что это «безопасный тип». Большинство реализаций C реализуют форматы printf с использованием вычисленного goto, поэтому printf настолько быстр, насколько это возможно, даже без того, чтобы компилятор не знал о printf (не то, что это не так - некоторые компиляторы могут оптимизировать printf в некоторых случаях - постоянная строка, заканчивающаяся \n, обычно оптимизируется до puts).

Наследование * +1136 * Я не знаю, почему вы хотели бы наследовать ostream, но мне все равно. Это возможно и с FILE. class MyFile : public FILE {} Тип безопасности

Правда, списки аргументов переменной длины не имеют никакой безопасности, но это не имеет значения, так как популярные компиляторы C могут обнаружить проблемы со строкой формата printf, если вы включите предупреждения. Фактически, Clang может сделать это без включения предупреждений.

$ 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);
     ^
193 голосов
/ 20 мая 2010

Из C ++ FAQ :

[15.1] Почему я должен использовать <iostream> вместо традиционного <cstdio>?

Повышение безопасности типов, уменьшение количества ошибок, расширение возможностей и обеспечение наследуемости.

printf() возможно не сломан, а scanf() возможно пригоден для жизни, несмотря на то, что подвержен ошибкам, однако оба ограничены в отношении того, что может делать ввод / вывод C ++. Ввод-вывод C ++ (с использованием << и >>) относительно C (с использованием printf() и scanf()):

  • Более безопасный тип: С <iostream> тип объекта ввода / вывода: статически известен компилятором. В напротив, <cstdio> использует поля "%" для определить типы динамически.
  • Меньше ошибок: с <iostream> нет избыточных "%" токены, которые должны быть согласованы с фактическими объектами, являющимися I / O'd. Удаление избыточности удаляет класс ошибок.
  • Расширяемый: механизм C ++ <iostream> позволяет новые пользовательские типы, которые будут I / O'd без нарушения существующий код. Вообразите хаос, если все одновременно добавляли новые несовместимые поля "%" для printf() и scanf()?!
  • Наследуемо: механизм C ++ <iostream> построен из реальных классов такие как std::ostream и std::istream. В отличие от <cstdio> FILE*, это настоящие классы и следовательно наследуется. Это означает, что вы можете есть другие определенные пользователем вещи, которые выглядеть и вести себя как потоки, но это делай все, что странно и чудесно вещи, которые вы хотите. Вы автоматически получить использовать миллионы линий Код ввода / вывода, написанный пользователями, которых вы не делаете даже знаю, и им не нужно знать о вашем "расширенном потоке" класс.

С другой стороны, printf значительно быстрее, что может оправдать использование его вместо cout в очень конкретных и ограниченных случаях. Всегда профиль первым. (См., Например, http://programming -designs.com / 2009/02 / c-speed-test-part-2-printf-vs-cout /)

38 голосов
/ 20 мая 2010

Люди часто утверждают, что printf намного быстрее. Это во многом миф. Я только что проверил, со следующими результатами:

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

Вывод: если вам нужны только новые строки, используйте printf; в противном случае cout почти так же быстро или даже быстрее. Более подробную информацию можно найти в моем блоге .

Для ясности, я не пытаюсь сказать, что iostream всегда лучше, чем printf; Я просто пытаюсь сказать, что вы должны принять обоснованное решение, основанное на реальных данных, а не дикие предположения, основанные на некоторых распространенных, вводящих в заблуждение предположениях.

Обновление: вот полный код, который я использовал для тестирования. Скомпилировано с g++ без каких-либо дополнительных параметров (кроме -lrt для синхронизации).

#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);
    }
}
37 голосов
/ 20 мая 2010

А я цитата :

В терминах высокого уровня основными различиями являются безопасность типов (cstdio не имеет), производительность (большинство реализаций iostreams медленнее, чем cstdio) и расширяемость (iostreams позволяет пользовательские цели вывода и плавный вывод пользовательских типов).

29 голосов
/ 20 мая 2010

Одна - это функция, которая печатает на стандартный вывод. Другой - это объект, который предоставляет несколько функций-членов и перегрузок operator<<, которые печатаются в стандартный вывод. Я могу перечислить еще много различий, но я не уверен, что вы ищете.

11 голосов
/ 20 мая 2010

Для меня реальные отличия, которые заставили бы меня пойти на «cout», а не «printf»:

1) << </strong> оператор может быть перегружен для моих классов.

2) Выходной поток для cout может быть легко изменен на файл: (: копировать вставить:)

#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) Мне кажется, что cout более читабелен, особенно когда у нас много параметров.

Одна проблема с cout - параметры форматирования. Форматирование данных (точность, выравнивание и т. Д.) В printf проще.

5 голосов
/ 06 декабря 2014

Два момента, не упомянутых здесь иным, которые я считаю значимыми:

1) cout перевозит много багажа, если вы еще не используете STL. Он добавляет вдвое больше кода в ваш объектный файл, чем printf. Это также верно для string, и это главная причина, по которой я склонен использовать свою собственную библиотеку строк.

2) cout использует перегруженные << операторы, что я считаю неудачным. Это может привести к путанице, если вы также используете оператор << по прямому назначению (сдвиг влево). Лично я не люблю перегружать операторов в целях, которые касаются их предполагаемого использования.

Итог: я буду использовать coutstring), если я уже использую STL. В противном случае, я склонен избегать этого.

4 голосов
/ 20 мая 2010

С примитивами, вероятно, не имеет значения, какой из них вы используете. Я говорю, где это полезно, когда вы хотите выводить сложные объекты.

Например, если у вас есть класс,

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

Теперь вышеприведенное может показаться не таким уж большим, но давайте предположим, что вы должны вывести это в нескольких местах в вашем коде. Кроме того, допустим, вы добавили поле «int d». С Cout вам нужно только изменить его в одном месте. Однако с printf вам придется менять его, возможно, во многих местах, и не только это, вы должны напоминать себе, какие из них выводить.

С учетом сказанного, с помощью cout вы можете сократить много времени, затрачиваемого на обслуживание кода, и не только то, что если вы повторно используете объект "Что-то" в новом приложении, вам не нужно беспокоиться о выходе.

2 голосов
/ 17 ноября 2015

Я хотел бы отметить, что если вы хотите поиграть с потоками в C ++, если вы используете cout, вы можете получить некоторые интересные результаты.

Рассмотрим этот код:

#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

Теперь на выходе все перемешано. Это также может дать разные результаты, попробуйте выполнить несколько раз:

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

Вы можете использовать printf, чтобы получить его правильно, или вы можете использовать mutex.

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

Веселись!

2 голосов
/ 25 ноября 2011

Конечно, вы можете написать «что-то» немного лучше, чтобы сохранить обслуживание:

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

И немного расширенный тест cout против printf, добавлен тест 'double', если кто-то хочет проводить больше тестов (Visual Studio 2008, выпуск исполняемого файла):

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

Результат:

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
...