我应该在我的C ++代码中使用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
语句吗?
我的学生先学习cin
和cout
,然后学习printf
,压倒性地喜欢printf
(或者更通常的fprintf
)。 我自己发现printf
模型足够可读,我已经将它移植到其他编程语言。 Olivier Danvy也是如此 ,他甚至把它变成了types安全的。
假如你有一个编译器能够检查对printf
调用,我没有理由不使用fprintf
和C ++中的朋友。
免责声明:我是一个可怕的C ++程序员。
如果你曾经希望在你的程序中,远离iostreams。 问题是,如果句子是由多个片段组成的,就像使用iostream所做的那样,就不可能正确地定位你的string。
除了消息片段的问题之外,还有一个命令的问题。 考虑一个打印学生姓名和平均成绩的报告:
std::cout << name << " has a GPA of " << gpa << std::endl;
当你翻译成另一种语言时,另一种语言的语法可能需要你在名称前面显示GPA。 AFAIK,iostreams没有办法对内插值重新sorting。
如果你想两全其美(types安全,能够国际化),使用Boost.Format 。
使用boost :: format。 你得到types安全,std :: string支持,printf类似的界面,使用cout的能力,以及其他很多好东西。 你不会回去的。
适应性
任何尝试printf
非POD都会导致未定义的行为:
struct Foo { virtual ~Foo() {} operator float() const { return 0.f; } }; printf ("%f", Foo()); std::string foo; printf ("%s", foo);
上面的printf调用产生未定义的行为。 您的编译器可能会警告您,但这些警告不是标准所要求的,对于只在运行时才知道的格式string是不可能的。
IOstream:
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; }
C:
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);
C ++:
cout << p; cout << p; some_file << p;
自我评判
国际化
我们重用我们的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性能的实际意义? 你的瓶颈应用程序是否严重懒惰,计算结果的输出是一个瓶颈? 你确定你需要C ++吗?
- 可怕的性能损失是为了满足那些想要使用printf和cout的用户。 这是一个function,而不是一个错误!
如果你一直使用iostream,你可以
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中使用types安全的,I18N友好的printf,这要归功于可变参数模板。 而且你将能够使用用户定义的文字非常,非常高效地执行它们,也就是说可以写出一个完全静态的化身。
我有一个概念的certificate 。 那时候,对C ++ 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中犯了很多错误,特别是当你把用户inputstring混合在一起(想想你的I18N团队)。 您必须小心妥善地转义每一个这样的格式string,您必须确保传递正确的参数等。
-
IO-Streams使我的二进制变大 :如果这是比可维护性,代码质量,可重用性更重要的问题,那么(在validation问题之后!)使用printf。
我使用printf是因为我讨厌丑陋的<<cout<<
语法。
没有理由。 我认为这只是一些奇怪的意识形态,驱使人们只使用C ++库,即使旧的C库仍然有效。 我是一个C ++的人,我也使用C函数。 从来没有与他们有任何问题。
stream是规范的方式。 尝试使这个代码与printf
工作:
template <typename T> void output(const T& pX) { std::cout << pX << std::endl; }
祝你好运。
我的意思是说,你可以让运营商让你的types输出到ostream
的,而不用像任何其他types的麻烦。 printf
不符合C ++的一般性,或更具体的模板。
有更多的可用性。 还有一致性。 在我所有的项目中,我都有cout(以及cerr
和clog
)也可以输出到一个文件中。 如果您使用printf
,则跳过所有这些。 另外,一致性本身是一件好事, 混合cout
和printf
,而完全有效,是丑陋的。
如果你有一个对象,并且想要使其成为可输出的,那么最简洁的方法就是为该类重载operator<<
。 你打算如何使用printf
呢? 你最终会遇到与cout
和printf
混杂在一起的代码。
如果您确实想要格式化,请在维护stream接口的同时使用Boost.Format。 一致性和格式。
使用printf。 不要使用C ++stream。 printf给你更好的控制(如浮点精度等)。 代码通常也更短,更易读。
谷歌C + +风格指南同意。
不要使用stream,除非日志logging界面需要。 改用printf-like例程。
使用stream的方式有各种各样的优点和缺点,但在这种情况下,和许多其他情况一样,一致性胜过了辩论。 不要在代码中使用stream。
总的来说,我同意(憎恨<<语法,特别是如果你需要复杂的格式)
但是我应该指出安全问题。
printf("%x",2.0f) printf("%x %x",2) printf("%x",2,2)
可能不会被编译器注意到,但可能会导致应用程序崩溃。
使用任何适合您的需求和偏好。 如果你对printf感到满意,那么一定要用它。 如果你喜欢iostreams坚持他们。 根据您的要求混合搭配。 毕竟,这是软件 – 有更好的方法和更糟糕的方法,但很less有只有一种方法。
分享和享受。
我不喜欢printf。 它缺乏types安全性,使用起来很危险,加上需要记住格式说明符是一个痛苦。 聪明地做正确事情的模板操作员要好得多。 所以我总是在C ++中使用C ++stream。
当然,许多人更喜欢printf, 其他原因,在其他地方列举。
我经常“回退”使用printf()
,但更经常snprintf()
更容易格式化输出。 当用C ++编程时,我用这个包装器写了一段时间,就像这样调用(使用上面的例子): 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, ...);
更新
在阅读了其他一些答案之后,我可能不得不做一个switch boost::format()
我自己!
我几乎总是使用printf作为临时debugging语句。 对于更长久的代码,我更喜欢'C'stream,因为它们是C ++的方式 。 虽然boost :: format看起来很有前景,可能会替代我的stream的使用(特别是对于格式复杂的输出),但很可能没有什么东西会替代printf。
C ++ stream被高估了,毕竟它们实际上只是带有重载运算符<<
。
我已经阅读过很多次, C ++的方式是printf,但是C ++都提供了库函数,所以你应该使用最适合的方法。
我主要更喜欢printf,但我也使用了stream,它提供了更清晰的代码,并防止您必须将%占位符与参数匹配。
这取决于实际情况。 没有什么是完美的。 我用两个。 stream对于自定义types是很好的,因为你可以在ostream中重载>>操作符。 但是当涉及到间距等,最好使用printf()。 stringstream和like样式都比C风格的strcat()好。 所以用一个适合的情况。
即使这个问题比较老,我想补充两分钱。
用printf()打印用户创build的对象
这很简单,如果你考虑一下 – 你可以将你的typesstring化,并把string发送到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
遗憾的是没有(有C ++ 11 to_string())标准化的C ++接口来对象化…
printf()陷阱
一个标志 – %n
唯一一个是输出参数 – 它指望int的指针。 它将成功写入的字符数写入该指针指向的位置。 技巧性的使用会触发溢出,这是安全漏洞(参见printf()格式的string攻击)。
我读过警告说cout和cerr对于multithreading是不安全的。 如果属实,这是避免使用它们的一个很好的理由。 注意:我使用openMP的GNU g ++。
溪stream在cpp中是优选的,因为它们遵循cpp的面向对象的范例,除了types安全之外。
另一方面,printf更多的是一种function性的方法。
唯一的理由不使用printf在cpp代码,我能想到的不是面向对象。
更多的是个人select。