Я удивлен тем, что все в этом вопросе утверждают, что 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);
^