适应性
任何尝试对非平凡数据类型进行 printf
的操作都会导致未定义的行为:
struct Foo {
virtual ~Foo() {}
operator float() const { return 0.f; }
};
printf ("%f", Foo());
std::string foo;
printf ("%s", foo);
上述printf调用会导致未定义的行为。虽然您的编译器可能会发出警告,但这些警告不是标准所要求的,而且对于仅在运行时已知的格式字符串是不可能发出警告的。
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;
}
cout << p;
cout << p;
some_file << p;
C:
// inline everywhere
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);
或者:
int person_fprint(FILE *f, const Person *p) {
return fprintf(f, "%s, %s", p->first_name, p->second_name);
}
int person_print(const Person *p) {
return person_fprint(stdout, p);
}
Person p;
....
person_print(&p);
请注意在C语言中使用正确的调用参数/签名(例如:
person_fprint(stderr, ...
,
person_fprint(myfile, ...
),而在C ++中,“FILE”参数会自动从表达式“派生”出来。更精确等效于此推导的示例实际上更像是这样的:
FILE *fout = stdout;
...
fprintf(fout, "Hello World!\n");
person_fprint(fout, ...);
fprintf(fout, "\n");
国际化
我们重用了我们的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());
请自行判断。
我认为这个问题在今天(2017年)已经不那么重要了。也许只是一种直觉,但是I18N并不是普通的C或C++程序员每天都会做的事情。此外,它仍然是一件麻烦的事情。
性能
- 你有测量过printf性能的实际意义吗?你的瓶颈应用程序真的很懒惰,计算结果的输出是一个瓶颈吗?你确定你需要使用C++吗?
- 可怕的性能惩罚是为了满足那些想要使用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。
在C++11中,由于可变参数模板,您可以拥有一个类型安全、I18N友好的printf。并且通过使用用户定义的字面量,您将能够使它们非常高效,即可以编写完全静态的实现。
我有一个概念证明。当时对C++11的支持还不如现在成熟,但您可以得到一个想法。
时间适应性
...
struct Frob {
unsigned int x;
};
...
... printf ("%u", frob.x); ...
... printf ("%u", frob.x); ...
... printf ("%u", frob.x); ...
... printf ("%u", frob.x); ...
后来,你的数据变得非常庞大,你必须采取措施。
// foo.h
...
unsigned long long x;
...
这是一个有趣的练习,要保持它没有漏洞。特别是当其他非耦合项目使用foo.h时。
其他。
endl
也会刷新流,就像您编写了printf("(%d,%d)\n", x, y); fflush(stdout);
一样。如果在循环中重复执行此操作,可能会导致大量的性能损耗。要在C ++中获得类似于printf语句的真正等效语句,您应该编写cout << "(" << x << "," << y << ")\n";
。 - Didier Trosset