Какую библиотеку ввода/вывода C следует использовать в коде на С++?
В новом коде на С++ я предпочитаю использовать библиотеку iostream С++ вместо библиотеки Cddio.
Я заметил, что некоторые программисты, похоже, придерживаются stdio, настаивая на том, что они более портативны.
Это действительно так? Что лучше использовать?
Ответы
Ответ 1
Чтобы ответить на исходный вопрос:
Все, что можно сделать с помощью stdio, можно выполнить с помощью библиотеки iostream.
Disadvantages of iostreams: verbose
Advantages of iostreams: easy to extend for new non POD types.
На шаг вперед С++, сделанный над C, была безопасность типа.
-
iostreams был разработан, чтобы быть явно безопасным. Таким образом, назначение объекту явно проверяет тип (во время компилятора) объекта, которому тоже назначено (при необходимости генерирует ошибку времени компиляции). Таким образом, предотвращение перегрузки во время выполнения или запись значения float в объект char и т.д.
-
scanf()/printf() и семья с другой стороны полагаются на программиста, получая строку формата правильно и не было проверки типа (я считаю, что gcc имеет расширение, которое помогает). В результате он стал источником многих ошибок (поскольку программисты менее совершенны в своем анализе, чем компиляторы (не говоря о том, что компиляторы идеальны лучше, чем люди)).
Просто, чтобы уточнить комментарии Колина Йенсена.
- Библиотеки iostream были стабильными с момента выпуска последнего стандарта (я забыл текущий год, но около 10 лет назад).
Чтобы прояснить комментарии Микаэля Янсона.
- Другие языки, которые он упоминает, которые используют стиль форматирования, имеют явные гарантии для предотвращения опасных побочных эффектов библиотеки C stdio, которые могут (в C, но не упомянутых языках) вызывать сбои во время выполнения.
N.B. Я согласен с тем, что библиотека iostream немного на стороне verbose. Но я готов смириться с подробностью, чтобы обеспечить безопасность во время работы. Но мы можем смягчить многословие, используя Библиотека формата ускорения.
#include <iostream>
#include <iomanip>
#include <boost/format.hpp>
struct X
{ // this structure reverse engineered from
// example provided by 'Mikael Jansson' in order to make this a running example
char* name;
double mean;
int sample_count;
};
int main()
{
X stats[] = {{"Plop",5.6,2}};
// nonsense output, just to exemplify
// stdio version
fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
stats, stats->name, stats->mean, stats->sample_count);
// iostream
std::cerr << "at " << (void*)stats << "/" << stats->name
<< ": mean value " << std::fixed << std::setprecision(3) << stats->mean
<< " of " << std::setw(4) << std::setfill(' ') << stats->sample_count
<< " samples\n";
// iostream with boost::format
std::cerr << boost::format("at %p/%s: mean value %.3f of %4d samples\n")
% stats % stats->name % stats->mean % stats->sample_count;
}
Ответ 2
Это просто слишком много.
Подумайте о конструкции iostream для выполнения следующего (аналогично для scanf):
// nonsense output, just to examplify
fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
stats, stats->name, stats->mean, stats->sample_count);
Для этого потребуется что-то вроде:
std::cerr << "at " << static_cast<void*>(stats) << "/" << stats->name
<< ": mean value " << std::precision(3) << stats->mean
<< " of " << std::width(4) << std::fill(' ') << stats->sample_count
<< " samples " << std::endl;
Форматирование строк - это случай, когда объектно-ориентированность может и должна быть обойдена стороной в пользу форматирования DSL, встроенного в строки. Рассмотрим Lisp format
, форматирование в стиле Python в формате printf или PHP, Bash, Perl, Ruby и их интрополяцию строк.
iostream
для этого случая использования в лучшем случае ошибочно.
Ответ 3
Библиотека форматирования форматирования предоставляет объектно-ориентированную альтернативу для форматирования строк в стиле printf и является дополнением к iostreams, который не страдает от обычных вопросов детализации из-за умного использования оператора%. Я рекомендую рассмотреть его с использованием plain C printf, если вам не нравится форматирование с помощью оператора iostream <
Ответ 4
В старые добрые времена Комитет по стандартам C++ продолжал перебирать язык, а iostreams - движущаяся цель. Если вы использовали iostreams, вам была предоставлена возможность переписывать части вашего кода каждый год или около того. Из-за этого я всегда использовал stdio, который не изменился значительно с 1989 года.
Если бы я делал что-то сегодня, я бы использовал iostreams.
Ответ 5
Если, как и я, вы изучили C до изучения С++, библиотеки stdio кажутся более естественными в использовании. Есть плюсы и минусы для iostream vs. stdio, но я пропускаю printf() при использовании iostream.
Ответ 6
В принципе, я бы использовал iostreams, на практике я делаю слишком много отформатированных десятичных знаков и т.д., которые делают iostreams слишком непроницаемыми, поэтому я использую stdio. Boost:: format - это улучшение, но не достаточно мотивированное для меня. На практике stdio почти типичен, поскольку большинство современных компиляторов все равно проверяют аргументы.
Это область, где я до сих пор не полностью доволен решением.
Ответ 7
Для двоичного ввода-вывода я предпочитаю использовать stdio fread и fwrite. Для форматированного материала я обычно использую IO Stream, хотя, как сказал Микаэль, форматирование без треугольника (не по умолчанию?) Может быть PITA.
Ответ 8
Несмотря на то, что API-интерфейс iostreams С++ имеет много преимуществ, одна существенная проблема связана с i18n. Проблема в том, что порядок подстановок параметров может варьироваться в зависимости от культуры. Классический пример - это что-то вроде:
// i18n UNSAFE
std::cout << "Dear " << name.given << ' ' << name.family << std::endl;
В то время как это работает на английском языке, на китайском языке фамилия на первом месте.
Когда речь заходит о переводе кода на внешние рынки, перевод фрагментов чреват опасностью, поэтому для новых l10ns могут потребоваться изменения кода, а не только разные строки.
boost:: format, кажется, объединяет лучшее из stdio (одноформатная строка, которая может использовать параметры в другом порядке, чем они появляются) и iostreams (тип-безопасность, расширяемость).
Ответ 9
Я буду сравнивать две основные библиотеки из стандартной библиотеки С++.
В С++ не следует использовать строковые процедуры, основанные на стилевом стиле, в С++.
Существует несколько причин для их использования:
- Не типы
- Вы не можете передавать типы не-POD в списки вариационных аргументов (т.е. ни scanf + co., ни printf + co.),
или вы входите в Dark Stronghold из Undefined Behavior
- Легко ошибиться:
- Вы должны сохранить синхронизацию строки формата и "value-argument-list"
- Вы должны правильно синхронизировать
Тонкие ошибки, введенные в удаленных местах
Это не только сам printf, это не хорошо. Программное обеспечение стареет и рефакторируется и модифицируется, и ошибки могут быть введены из удаленных мест. Предположим, что у вас есть
.
// foo.h
...
float foo;
...
и где-то...
// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...
И три года спустя вы обнаружите, что foo должен иметь какой-то особый тип...
// foo.h
...
FixedPoint foo;
...
но где-то...
// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...
... тогда ваш старый файл printf/scanf будет компилироваться, за исключением того, что теперь вы получаете случайные segfault и вы не помните, почему.
Многозначность iostreams
Если вы считаете, что printf() менее подробный, то существует определенная вероятность того, что вы не используете их полную силу iostream. Пример:
printf ("My Matrix: %f %f %f %f\n"
" %f %f %f %f\n"
" %f %f %f %f\n"
" %f %f %f %f\n",
mat(0,0), mat(0,1), mat(0,2), mat(0,3),
mat(1,0), mat(1,1), mat(1,2), mat(1,3),
mat(2,0), mat(2,1), mat(2,2), mat(2,3),
mat(3,0), mat(3,1), mat(3,2), mat(3,3));
Сравните это с правильным использованием iostreams:
cout << mat << '\n';
Вы должны определить правильную перегрузку для оператора < < который имеет примерно структуру printf-thingy, но существенная разница заключается в том, что теперь у вас есть что-то повторно используемое и типичное; конечно, вы также можете сделать что-то повторно используемое для printf-like, но потом у вас есть printf снова (что, если вы замените членов матрицы новым FixedPoint
?), помимо других нетривиальных функций, например. вы должны передать файлы FILE *.
Строки формата C-стиля не лучше для I18N, чем iostreams
Обратите внимание, что строки формата часто считаются спасением с интернационализацией, но в этом отношении они не лучше, чем iostream:
printf ("Guten Morgen, Sie sind %f Meter groß und haben %d Kinder",
someFloat, someInt);
printf ("Good morning, you have %d children and your height is %f meters",
someFloat, someInt); // Note: Position changed.
// ^^ not the best example, but different languages have generally different
// order of "variables"
I.e., старые стили формата C не имеют позиционной информации столько же, сколько iostreams.
Возможно, вы захотите рассмотреть boost:: format, который предлагает поддержку для указания позиции в строке формата явно. Из раздела примеров:
cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "333"; // 'simple' style.
Некоторые printf-реализации предоставляют позиционные аргументы, но они нестандартны.
Должен ли я никогда использовать строки формата C?
Помимо производительности (как указал Ян Худек), я не вижу причины. Но имейте в виду:
"Мы должны забыть о небольшой эффективности, скажем, около 97% времени: преждевременная оптимизация - это корень всего зла, но мы не должны упускать наши возможности в этих критических 3%. Хорошего программиста не убаюкивать самодовольство по таким рассуждениям, он будет разумно внимательно смотреть на критический код, но только после того, как этот код был идентифицирован" - Knuth
и
"Узкие места встречаются в неожиданных местах, поэтому не пытайтесь догадываться и вводить взломанную скорость, пока не убедитесь, что там, где есть узкое место". - Щука
Да, printf-реализации обычно быстрее, чем iostreams, как правило, быстрее, чем boost:: format (из небольшого и конкретного теста, который я написал, но он должен во многом зависеть от ситуации в частности: if printf = 100%, тогда iostream = 160% и boost:: format = 220%)
Но не слепо опускайте мысли об этом: сколько времени вы потратите на обработку текста? Как долго ваша программа запускается до выхода?
Имеет ли смысл вообще возвращаться к строкам формата С-стиля, свободному типу безопасности, снижению рефакторитивности,
увеличивают вероятность очень тонких ошибок, которые могут скрыть себя годами и могут только проявить себя
в ваши любимые клиенты?
Лично я не откажусь, если не смогу получить более 20% ускорения. Но поскольку мои приложения
тратить практически все свое время на другие задачи, чем на струнную обработку, мне никогда не приходилось. Некоторые парсеры
Я писал, что тратил практически все время на обработку строк, но их общая продолжительность работы настолько мала
что это не стоит усилий по тестированию и проверке.
Некоторые загадки
Наконец, я хотел бы задать некоторые загадки:
Найти все ошибки, потому что компилятор не будет (он может только предложить, если он хорош):
shared_ptr<float> f(new float);
fscanf (stdout, "%u %s %f", f)
Если ничего другого, что случилось с этим?
const char *output = "in total, the thing is 50%"
"feature complete";
printf (output);
Ответ 10
Я использую iostreams, главным образом потому, что это облегчает скрипку с потоком позже (если мне это нужно). Например, вы можете узнать, что вы хотите отображать вывод в каком-то окне трассировки - это относительно легко сделать с cout и cerr. Вы можете, конечно, возиться с трубами и другими материалами в unix, но это не так переносимо.
Мне нравится форматирование с использованием printf, поэтому я обычно сначала форматирует строку, а затем отправляю ее в буфер. С Qt я часто использую QString:: sprintf (хотя они рекомендуют использовать QString:: arg). Я также посмотрел на boost.format, но не смог привыкнуть к синтаксису (слишком много%). Я должен действительно взглянуть на это.
Ответ 11
То, что я пропустил в iolibraries, - это форматированный ввод.
iostreams не имеет хорошего способа репликации scanf(), и даже boost не имеет требуемого расширения для ввода.
Ответ 12
stdio лучше для чтения двоичных файлов (например, для факельных блоков в вектор < unsigned char > и с использованием .resize() и т.д.). См. Функцию read_rest в файле .hh в http://nuwen.net/libnuwen.html для примера.
Потоки С++ могут забиваться на большом количестве байтов при чтении двоичных файлов, вызывающих ложное eof.
Ответ 13
Так как iostreams стали стандартом, вы должны использовать их, зная, что ваш код будет работать с новыми версиями компилятора. Я думаю, что в настоящее время большинство компиляторов очень хорошо знают об iostreams, и не должно быть никаких проблем с их использованием.
Но если вы хотите придерживаться функций * printf, на мой взгляд, проблем не будет.