提问人:Ferruccio 提问时间:9/23/2008 最后编辑:CommunityFerruccio 更新时间:6/15/2015 访问量:13924
C++ 代码中应该使用哪个 C I/O 库?[已结束]
Which C I/O library should be used in C++ code? [closed]
问:
在新的 C++ 代码中,我倾向于使用 C++ iostream 库而不是 C stdio 库。
我注意到一些程序员似乎坚持使用 stdio,坚持认为它更便携。
真的是这样吗?什么更好用?
答:
如果你像我一样,在学习C++之前学习过C,那么使用stdio库似乎更自然。iostream 与 stdio 各有利弊,但我在使用 iostream 时确实想念 printf()。
回到过去糟糕的日子里,C++标准委员会一直在搞语言,而iostreams是一个移动的目标。如果您使用 iostreams,那么您就有机会每年左右重写部分代码。正因为如此,我一直使用 stdio,自 1989 年以来没有显着变化。
如果我今天在做一些事情,我会使用 iostreams。
这太啰嗦了。
思考用于执行以下操作的 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、Python 的 printf 样式格式,或者 PHP、Bash、Perl、Ruby 及其字符串插值。format
iostream
因为这个用例充其量是错误的。
评论
operator<<
std::cout << stats;
printf(...)
回答原始问题:
使用 stdio 可以完成的任何事情都可以使用 iostream 库完成。
Disadvantages of iostreams: verbose
Advantages of iostreams: easy to extend for new non POD types.
C++ 比 C 向前迈出的一步是类型安全。
IOSTREAMS 被设计为显式类型安全。因此,对对象的赋值也显式检查了被赋值对象的类型(在编译器时)(如果需要,会生成编译时错误)。因此,可以防止运行时内存溢出或将浮点值写入 char 对象等。
另一方面,scanf()/printf() 和 family 依赖于程序员获得正确的格式字符串,并且没有类型检查(我相信 gcc 有一个扩展可以提供帮助)。因此,它是许多错误的根源(因为程序员的分析不如编译器完美[并不是说编译器比人类更完美])。
只是为了澄清科林·詹森(Colin Jensen)的评论。
- 自上一个标准发布以来,iostream 库一直很稳定(我忘记了实际年份,但大约 10 年前)。
澄清 Mikael Jansson 的评论。
- 他提到的使用格式样式的其他语言有明确的保护措施,以防止 C stdio 库的危险副作用(在 C 语言中,但不是提到的语言)导致运行时崩溃。
注意我同意 iostream 库有点冗长。但我愿意忍受冗长,以确保运行时安全。但是我们可以通过使用 Boost 格式库来减轻冗长。
#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;
}
评论
fprintf
FILE
对于二进制 IO,我倾向于使用 stdio 的 fread 和 fwrite。对于格式化的东西,我通常会使用 IO Stream,尽管正如 Mikael 所说,non-trival(非默认?)格式可以是 PITA。
评论
stdio 更适合读取二进制文件(例如将块转换为向量<无符号字符>并使用 .resize() 等)。有关示例,请参阅 http://nuwen.net/libnuwen.html 中 file.hh 中的 read_rest 函数。
C++ 流在读取二进制文件时可能会阻塞大量字节,从而导致错误的 eof。
评论
Boost 格式库为 printf 样式的字符串格式设置提供了一种类型安全、面向对象的替代方案,并且是对 iostreams 的补充,由于巧妙地使用了运算符,因此不会出现通常的冗长问题。如果您不喜欢使用 iostream 的运算符进行格式化,我建议您考虑使用纯 C printf <<。
我使用 iostreams,主要是因为这样以后可以更轻松地摆弄流(如果我需要的话)。例如,你可能会发现你想在某个跟踪窗口中显示输出 -- 这对于 cout 和 cerr 来说相对容易做到。当然,你可以在 unix 上摆弄管道和其他东西,但这并不那么便携。
我确实喜欢类似 printf 的格式,所以我通常先格式化一个字符串,然后将其发送到缓冲区。对于Qt,我经常使用QString::sprintf(尽管他们建议使用QString::arg)。我也看过boost.format,但无法真正习惯语法(%太多)。不过,我真的应该看一看。
由于 iostreams 已成为标准,因此您应该使用它们,因为您知道您的代码肯定会适用于较新版本的编译器。我想现在大多数编译器都非常了解 iostreams,使用它们应该没有任何问题。
但是如果你想坚持使用 *printf 函数,在我看来没有问题。
评论
原则上我会使用 iostreams,在实践中我做了太多格式化的小数等,使 iostreams 太不可读,所以我使用 stdio。Boost::format 是一种改进,但对我来说还不够激励。在实践中,stdio 几乎是类型安全的,因为大多数现代编译器无论如何都会进行参数检查。
在这个领域,我仍然对任何解决方案都不完全满意。
评论
我对 iolibraries 的怀念是格式化的输入。
IOTreates 没有很好的方法来复制 scanf(),甚至 boost 也没有输入所需的扩展名。
虽然 C++ iostreams API 有很多好处,但一个重要的问题是围绕 i18n。问题在于,参数替换的顺序可能因区域性而异。典型的例子是这样的:
// i18n UNSAFE
std::cout << "Dear " << name.given << ' ' << name.family << std::endl;
虽然这适用于英语,但在中文中,姓氏是第一位的。
在为国外市场翻译代码时,翻译代码片段充满了危险,因此新的 l10ns 可能需要更改代码,而不仅仅是更改不同的字符串。
boost::format 似乎结合了 stdio(一个单一格式字符串,可以按不同的顺序使用参数,然后它们出现)和 IOSTREAMS(类型安全、可扩展性)的优点。
评论
name.formal_name()
name.culture
cout << student_name << " has a GPA of " << gpa;
我将比较 C++ 标准库中的两个主流库。
不应在 C++ 中使用基于 C 样式格式字符串的字符串处理例程。
有几个原因可以减少它们的使用:
- 非类型安全
- 您不能将非 POD 类型传递给可变参数列表(即,既不能传递给 scanf+co.,也不能传递给 printf+co.)。 或者你进入了未定义行为的黑暗据点
- 容易出错:
- 您必须设法使格式字符串和“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 仍然会编译,只是你现在得到随机的段错误,你不记得为什么。
iostream 的详细程度
如果您认为 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';
你必须为 operator 定义一个适当的重载<<它大致具有 printf-thingy 的结构,但显着的区别在于你现在有一些可重用和类型安全的东西;当然,你也可以为类似 printf 的东西做一些可重用的东西,但随后你又有了 printf(如果你用新的替换矩阵成员呢?),除了其他非平凡的事情,例如你必须传递 FILE* 句柄。FixedPoint
对于 I18N,C 样式格式字符串并不比 iostream 更好
请注意,format-strings 通常被认为是国际化的救星,但在这方面它们并不比 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"
也就是说,旧式 C 格式字符串和 iostream 一样缺乏位置信息。
您可能需要考虑 boost::format,它支持在格式字符串中显式声明位置。从他们的示例部分:
cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "333"; // 'simple' style.
一些 printf 实现提供了位置参数,但它们是非标准的。
我不应该使用 C 样式格式的字符串吗?
除了性能(正如 Jan Hudec 所指出的),我看不出有什么原因。但请记住:
“我们应该忘记小效率,比如说大约 97% 的时间:过早优化是万恶之源。然而,我们不应该在关键的3%中放弃我们的机会。一个好的程序员不会被这样的推理所哄骗而沾沾自喜,他会明智地仔细研究关键的代码;但只有在识别出该代码之后“ - Knuth
和
“瓶颈发生在令人惊讶的地方,所以在你证明这就是瓶颈所在之前,不要试图进行二次猜测和速度黑客。”
是的,printf-implementations 通常比 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);
评论