在新的C++代码中,我倾向于使用C++ iostream库而不是C stdio库。
我注意到一些程序员似乎坚持使用stdio,认为它更方便移植。
这是真的吗?哪种更好使用呢?
Disadvantages of iostreams: verbose
Advantages of iostreams: easy to extend for new non POD types.
#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;
}
它太啰嗦了。
考虑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
是令人误导的,充其量也只能算是一种错误的做法。
operator<<
,这样客户端就可以像这样使用它:std::cout << stats;
。这比使用printf(...)
或自定义打印函数要好得多,后者需要接受一个文件句柄作为参数。 - Sebastian MachBoost Format Library提供了一种类型安全、面向对象的printf风格字符串格式化替代方案,它是iostreams的补充,由于巧妙地使用了operator%,不会遭受通常由于冗长而产生的问题。如果您不喜欢使用iostream的operator<<进行格式化,请考虑使用它而不是使用普通的C printf。
回到不好的时代,C++标准委员会一直在修改语言,iostreams也是一个不断变化的目标。如果你使用iostreams,你就需要每年或每隔一段时间重写你的代码的部分。因此,我总是使用自1989年以来没有显著变化的stdio。
如果我今天要做东西,我会使用iostreams。
如果和我的情况一样,在学习 C++ 之前是学习了 C 的,那么使用 stdio 库可能会更加自然。使用 iostream 和 stdio 都有其优缺点,但是我在使用 iostream 时确实会感到遗憾不能使用 printf()。
原则上我会使用iostreams,在实践中,我也用到了太多格式化小数等内容,使得iostreams难以阅读,所以我使用stdio。Boost::format是一种改进,但对我来说还不够令人满意。在实践中,由于大多数现代编译器都进行参数检查,因此stdio几乎是类型安全的。
这是一个领域,我仍然对任何解决方案都不完全满意。
// foo.h
...
float foo;
...
而在某些地方...
// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...
// foo.h
...
FixedPoint foo;
...
but somewhere ...
// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...
如果你仍然使用旧的printf/scanf,那么它们还是可以编译通过的,但你可能会遇到随机的段错误,而你不记得为什么会出现这种情况。
如果你认为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 ("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"
也就是说,旧式的C格式字符串和iostreams一样缺乏位置信息。
您可能需要考虑使用boost::format,它提供了明确指定格式字符串中位置的支持。以下是他们示例部分的内容:
cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "333"; // 'simple' style.
有些printf实现提供了位置参数,但它们是非标准的。
除了性能(正如Jan Hudec所指出的那样),我没有看到任何理由。但请记住:
“我们应该忘记小效率,大约97%的时间:过早优化是万恶之源。然而,在关键的3%中,我们不应该放弃机会。一个好的程序员不会被这种推理所冷落,他将明智地仔细查看关键代码;但只有在确定了该代码之后。”- Knuth
和
“瓶颈出现在令人惊讶的地方,所以不要试图预测并放入速度优化,直到你已经证明那是瓶颈所在的地方。”- Pike
是的,printf实现通常比iostreams快,通常比boost :: format快(根据我编写的一个小而特定的基准测试,但它应该在特定情况下大部分取决于情况:如果printf = 100%,则iostream = 160%,而boost :: format = 220%)
但不要盲目省略思考:您真正花费多少时间在文本处理上?您的程序在退出之前运行多长时间? 是否有必要回退到C风格的格式化字符串,丧失类型安全性,减少重构能力, 增加可能会隐藏多年并可能仅在您最喜欢的客户面前显露自己的非常微妙的错误的概率?
就我个人而言,如果我不能获得超过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);
对于二进制IO,我倾向于使用stdio的fread和fwrite。对于格式化的内容,我通常会使用IO流,尽管正如Mikael所说,非平凡(非默认?)格式化可能会很麻烦。
虽然C++ iostreams API有很多好处,但一个显著的问题是它在i18n方面存在问题。问题在于参数替换的顺序可能会因文化差异而有所不同。经典例子如下:
// i18n UNSAFE
std::cout << "Dear " << name.given << ' ' << name.family << std::endl;
虽然在英语中这种方法可行,但在汉语中姓氏排在名字前面。
当涉及到将您的代码翻译成外国市场时,翻译片段充满了风险,因此新的本地化可能需要对代码进行更改,而不仅仅是不同的字符串。
boost::format似乎结合了stdio(一个可以使用不同顺序的参数的单个格式字符串)和iostreams(类型安全性,可扩展性)的优点。
name.formal_name()
方法,在基于 name.culture
(例如)返回正确的结果。 - paxdiablocout << student_name << " has a GPA of " << gpa;
。 - R Samuel Klatchko我想念iolibraries的是格式化输入。
iostreams没有很好的方法来复制scanf(),即使使用boost也没有所需的输入扩展。
fprintf
可以在任何FILE
对象上工作,这个对象可以从任何文件描述符创建。因此,它也同样适用于各种输入/输出机制:任何可以实现为文件描述符的东西。 - Tom