Должен ли я использовать printf в моем коде на С++?

Обычно я использую cout и cerr для записи текста на консоль. Однако иногда мне проще использовать старый добрый оператор printf. Я использую его, когда мне нужно отформатировать вывод.

Один пример того, где я буду использовать это:

// Lets assume that I'm printing coordinates... 
printf("(%d,%d)\n", x, y);

// To do the same thing as above using cout....
cout << "(" << x << "," << y << ")" << endl;

Я знаю, что могу форматировать вывод с помощью cout, но я уже знаю, как использовать printf. Есть ли причина, по которой я не должен использовать инструкцию printf?

Ответ 1

Мои ученики, которые сначала изучают cin и cout, затем изучают printf позже, в основном предпочитают printf (или чаще fprintf). Я сам нашел модель printf достаточно читаемой, чтобы портировать ее на другие языки программирования. Так что Olivier Danvy, который даже сделал его безопасным для текста.

Если у вас есть компилятор, который способен проверять тип вызовов на printf, я не вижу причин не использовать fprintf и друзей на С++.

Отказ от ответственности: Я ужасный программист на С++.

Ответ 2

Если вы когда-либо надеетесь на свою программу, держитесь подальше от iostreams. Проблема в том, что невозможно правильно локализовать ваши строки, если предложение состоит из нескольких фрагментов, как это делается с iostream.

Помимо проблемы с фрагментами сообщений, у вас также есть проблема с заказом. Рассмотрите отчет, в котором печатается имя студента и средний балл оценки:

std::cout << name << " has a GPA of " << gpa << std::endl;

Когда вы переводите это на другой язык, грамматика другого языка может понадобиться вам показать GPA перед именем. AFAIK, iostreams не имеет способа изменить порядок интерполированных значений.

Если вы хотите лучшее из обоих миров (введите безопасность и возможность i18n), используйте Boost.Format.

Ответ 3

Используйте boost:: format. Вы получаете безопасность типов, std::string поддержку, интерфейс printf, возможность использования cout и множество других полезных вещей. Вы не вернетесь.

Ответ 4

Адаптивность

Любая попытка printf не-POD приводит к поведению undefined:

struct Foo { virtual ~Foo() {}
             operator float() const { return 0.f; }
};

printf ("%f", Foo());

std::string foo;
printf ("%s", foo);

Вышеупомянутые printf-вызовы дают поведение undefined. Ваш компилятор может действительно вас предупредить, но эти предупреждения не требуются стандартами и не могут использоваться для строк формата, известных только во время выполнения.

IO-Streams:

std::cout << Foo();
std::string foo;
std::cout << foo;

Судите сами.

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

struct Person {
    string first_name;
    string second_name;
};
std::ostream& operator<< (std::ostream &os, Person const& p) {
    return os << p.first_name << ", " << p.second_name;
}

С

printf ("%s, %s", p.first_name, p.second_name);
printf ("%s, %s", p.first_name, p.second_name);
fprintf (some_file, "%s, %s", p.first_name, p.second_name);

С++:

cout << p;
cout << p;
some_file << p;

Судите сами.

I18N

Мы повторно используем наше определение Person:

cout << boost::format("Hello %1%") % p;
cout << boost::format("Na %1%, sei gegrüßt!") % p;

printf ("Hello %1$s, %2$s", p.first_name.c_str(), p.second_name.c_str()); 
printf ("Na %1$s, %2$s, sei gegrüßt!", 
        p.first_name.c_str(), p.second_name.c_str()); 

Судите сами.

Производительность

  • Вы измеряли фактическое значение производительности printf? Являются ли ваши приложения с узким местом настолько ленивыми, что результаты вычислений являются узким местом? Вы уверены, что вам нужен С++ вообще?
  • Страшное наказание за исполнение должно удовлетворить тех из вас, кто хочет использовать сочетание printf и cout. Это особенность, а не ошибка!

Если вы последовательно используете iostreams, вы можете

std::ios::sync_with_stdio(false);

и получите равную рабочую среду с хорошим компилятором:

#include <cstdio>
#include <iostream>
#include <ctime>
#include <fstream>

void ios_test (int n) {
    for (int i=0; i<n; ++i) {
        std::cout << "foobarfrob" << i;
    }
}

void c_test (int n) {
    for (int i=0; i<n; ++i) {
        printf ("foobarfrob%d", i);
    }
}


int main () {
    const clock_t a_start = clock();
    ios_test (10024*1024);
    const double a = (clock() - a_start) / double(CLOCKS_PER_SEC);

    const clock_t p_start = clock();
    c_test (10024*1024);
    const double p = (clock() - p_start) / double(CLOCKS_PER_SEC);

    std::ios::sync_with_stdio(false);
    const clock_t b_start = clock();
    ios_test (10024*1024);
    const double b = (clock() - b_start) / double(CLOCKS_PER_SEC);


    std::ofstream res ("RESULTS");
    res << "C ..............: " << p << " sec\n"
        << "C++, sync with C: " << a << " sec\n"
        << "C++, non-sync ..: " << b << " sec\n";
}

Результаты (g++ -O3 synced-unsynced-printf.cc, ./a.out > /dev/null, cat RESULTS):

C ..............: 1.1 sec
C++, sync with C: 1.76 sec
C++, non-sync ..: 1.01 sec

Судите сами...

Нет. Вы не запретите мне мой printf.

Вы можете столкнуться с типичным, I18N дружественным printf в С++ 11, благодаря вариативным шаблонам. И вы сможете использовать их очень, очень хорошо, используя пользовательские литералы, то есть можно будет написать полностью статичное воплощение.

У меня есть доказательство концепции. Тогда поддержка С++ 11 была не такой зрелой, как сейчас, но вы получили представление.

Временная адаптивность

// foo.h
...
struct Frob {
    unsigned int x;
};
...

// alpha.cpp
... printf ("%u", frob.x); ...

// bravo.cpp
... printf ("%u", frob.x); ...

// charlie.cpp
... printf ("%u", frob.x); ...

// delta.cpp
... printf ("%u", frob.x); ...

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

// foo.h
...
    unsigned long long x;
...

Это интересное упражнение, которое поддерживает это и делает это без ошибок. Особенно, когда другие, не связанные проекты используют foo.h.

Другие.

  • Потенциал ошибок. Там много места для совершения ошибок с printf, особенно когда вы вводите строки ввода входных данных в микс (подумайте о своей команде I18N). Вы должны позаботиться о том, чтобы надлежащим образом избежать каждой такой строки форматирования, вы должны обязательно передать правильные аргументы и т.д. И т.д.

  • IO-Streams делает мой двоичный файл более крупным. Если это более важная проблема, чем ремонтопригодность, качество кода, возможность повторного использования, то (после проверки проблемы!) используйте printf.

Ответ 5

Я использую printf, потому что ненавижу уродливый синтаксис <<cout<<.

Ответ 6

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

Ответ 7

Потоки являются каноническим способом. Попробуйте сделать этот код с printf:

template <typename T>
void output(const T& pX)
{
    std::cout << pX << std::endl;
}

Удачи.

Что я имею в виду, вы можете заставить операторов разрешить вывод типов в ostream, и без проблем использовать его так же, как и любой другой тип. printf не соответствует общности С++ или, более конкретно, шаблонам.

Там больше, чем удобство. Там также последовательность. Во всех моих проектах у меня есть cout (и cerr и clog) tee'd для вывода в файл. Если вы используете printf, вы пропустите все это. Кроме того, сама согласованность - хорошая вещь; смешивание cout и printf, в то время как совершенно верно, является уродливым.

Если у вас есть объект, и вы хотите сделать его выводным, самый чистый способ сделать это - перегрузка operator<< для этого класса. Как вы собираетесь использовать printf тогда? Вы закончите с кодом, смешанным с cout и printf.

Если вы действительно хотите форматировать, используйте Boost.Format, поддерживая интерфейс потока. Согласованность и форматирование.

Ответ 8

Использовать printf. Не используйте потоки С++. printf дает вам гораздо лучший контроль (например, точность поплавка и т.д.). Код также обычно короче и читабельнее.

Руководство по стилю Google С++.

Не используйте потоки, кроме случаев, когда требуемый интерфейсом ведения журнала. использование printf-like.

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

Ответ 9

В целом я согласен (ненавижу < < синтаксис, особенно если вам требуется сложное форматирование)

Но я должен указать на аспекты безопасности.

printf("%x",2.0f)
printf("%x %x",2)
printf("%x",2,2)

Вероятно, он не будет замечен компилятором, но может привести к сбою вашего приложения.

Ответ 10

Используйте все, что соответствует вашим потребностям и предпочтениям. Если вам удобно работать с printf, то обязательно используйте его. Если вы счастливы с iostreams, придерживайтесь их. Смешайте и совместите, как лучше всего соответствует вашим требованиям. Это программное обеспечение, в конце концов, там лучшие пути и худшие способы, но редко существует только один способ.

Поделитесь и наслаждайтесь.

Ответ 11

Я часто "откидываюсь назад" к использованию printf(), но чаще snprintf() для упрощенного форматированного вывода. При программировании на С++ я использую эту оболочку, которую я написал некоторое время назад, так называемый (чтобы использовать ваш пример, как указано выше): cout << format("(%d,%d)\n", x, y);

Здесь заголовок (stdiomm.h):

#pragma once

#include <cstdarg>
#include <string>

template <typename T>
std::basic_string<T> format(T const *format, ...);

template <typename T>
std::basic_string<T> vformat(T const *format, va_list args);

И источник (stdiomm.cpp):

#include "stdiomm.h"
#include <boost/scoped_array.hpp>
#include <cstdio>

template <>
std::wstring vformat(wchar_t const *format, va_list arguments)
{
#if defined(_WIN32)
    int required(_vscwprintf(format, arguments));
    assert(required >= 0);
    boost::scoped_array<wchar_t> buffer(new wchar_t[required + 1]);
    int written(vswprintf(buffer.get(), required + 1, format, arguments));
    assert(written == required);
    return std::wstring(buffer.get(), written);
#else
#   error "No implementation yet"
#endif
}

template <>
std::string vformat(char const *format, va_list arguments)
{
#if defined(_WIN32)
    int required(_vscprintf(format, arguments));
    assert(required >= 0);
    boost::scoped_array<char> buffer(new char[required + 1]);
    int written(vsnprintf(buffer.get(), required + 1, format, arguments));
    assert(written == required);
    return std::string(buffer.get(), written);
#else
    char *buffer;
    int printed = vasprintf(&buffer, format, arguments);
    assert(printed != -1);
    std::string retval(buffer, printed);
    free(buffer);
    return retval;      
#endif
}

template <typename T>
std::basic_string<T> format(T const *format, ...)
{
    va_list ap;
    va_start(ap, format);
    std::basic_string<T> retval(vformat(format, ap));
    va_end(ap);
    return retval;
}

template std::wstring format(wchar_t const *format, ...);
template std::string format(char const *format, ...);

Update

После прочтения некоторых других ответов мне, возможно, придется самому переключиться на boost::format()!

Ответ 12

Мне не нравится printf. Отсутствие безопасности типа делает его опасным для использования, а также необходимость запоминать спецификаторы формата - это боль. Шаблонные операторы, которые умело делают правильные вещи, намного лучше. Поэтому я всегда использую потоки С++ в С++.

Конечно, многие люди предпочитают printf по другим причинам, перечисленные в других местах.

Ответ 13

Я почти всегда использую printf для временных отладочных инструкций. Для более постоянного кода я предпочитаю потоки "c", поскольку они являются С++ Way. Хотя boost:: format выглядит многообещающим и может заменить использование моего потока (особенно для сложного отформатированного вывода), вероятно, ничто не заменит printf для меня в течение длительного времени.

Ответ 14

Потоки С++ переоценены, ведь они на самом деле просто классы с перегруженным оператором <<.
Я много раз читал, что потоки - это С++-путь, поскольку printf - это способ C, но они оба являются библиотечными функциями, доступными на С++, поэтому вы должны использовать то, что подходит лучше всего. В основном я предпочитаю printf, но я также использовал потоки, которые обеспечивают более чистый код и не позволяют сопоставлять% заполнителей с аргументами.

Ответ 15

Это зависит от ситуации. Ничто не идеально. Я использую оба. Потоки хороши для пользовательских типов, так как вы можете перегрузить оператор → в ostream. Но когда дело доходит до интервала и т.д., Лучше использовать printf(). stringstream и т.п. лучше, чем стиль C strcat(). Поэтому используйте тот, который подходит для ситуации.

Ответ 16

Даже если вопрос довольно старый, я хочу добавить два цента.

Печать созданных пользователем объектов с помощью printf()

Это довольно просто, если вы думаете об этом - вы можете подстроить свой тип и отправить строку в printf:

std::string to_string(const MyClass &x)
{
     return to_string(x.first)+" "+to_string(x.second);
}

//...

printf("%s is awesome", to_string(my_object).c_str()); //more or less

Позора там не было (есть С++ 11 to_string()) стандартизованный интерфейс С++ для строения объектов...

printf() pitfall

Один флаг -% n

Единственный, который является выходным параметром - он ожидает указателя на int. Он записывает количество успешно записанных символов в местоположение, указанное этим указателем. Умелое использование этого может привести к переполнению, что является уязвимостью безопасности (см. Printf() string string attack).

Ответ 17

Я прочитал предупреждения о том, что cout и cerr небезопасны для многопоточности. Если это правда, это хорошая причина, чтобы не использовать их. Примечание. Я использую GNU g++ с openMP.

Ответ 18

Вы можете получить лучшее из обоих миров с помощью fmt library, которая сочетает в себе безопасность и расширяемость iostreams с удобством и производительностью (s)printf, Пример:

std::string = fmt::format("The answer is {}", 42);

Библиотека поддерживает синтаксис строки формата Python и printf.

Отказ от ответственности. Я являюсь автором fmt library.

Ответ 19

Потоки

предпочтительны в cpp, поскольку они соответствуют объектно-ориентированной парадигме cpp, кроме безопасного типа.

printf, с другой стороны, является более функциональным подходом.

только причина не использования printf в коде cpp, о котором я могу думать, не является объектно-ориентированной.

его больше личного выбора.