我通常使用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也是如此,他甚至让它变得类型安全。
如果你有一个能够对printf
进行类型检查调用的编译器,我认为没有理由不在C ++中使用fprintf
和朋友。
免责声明:我是一个可怕的C ++程序员。
使用符合您需求和偏好的任何东西。如果您对printf感到满意,那么请务必使用它。如果你对iostreams感到满意,那就坚持下去吧。混合搭配最符合您的要求。毕竟这是软件 - 有更好的方法和更糟糕的方法,但很少有一种方法。
分享和享受。
我不喜欢printf。它缺乏类型安全性使得使用起来很危险,而且需要记住格式说明符是一种痛苦。聪明地做正确事情的模板化运营商要好得多。所以我总是在C ++中使用C ++流。
当然,许多人更喜欢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, ...);
在阅读了其他一些答案后,我可能不得不自己切换到boost::format()
!
即使问题相当陈旧,我想加上我的两分钱。
使用printf()打印用户创建的对象
如果你考虑一下,这很简单 - 你可以将你的类型字符串化并将字符串发送到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()格式字符串攻击)。
fmt library结合了iostreams的安全性和可扩展性以及(s)printf
的可用性和性能,您可以获得两全其美的效果。例:
std::string = fmt::format("The answer is {}", 42);
该库支持类似Python和printf格式的字符串语法。
免责声明:我是fmt library的作者。
我几乎总是将printf用于临时调试语句。对于更永久的代码,我更喜欢'c'流,因为它们是C ++方式。虽然boost :: format看起来很有前景,可能会取代我的流使用(特别是对于复杂格式的输出),但很长一段时间内我可能没有什么能替代printf。
C ++流被高估了,毕竟它们实际上只是具有重载运算符<<
的类。
我已多次读过流是C ++方式,因为printf是C方式,但它们都是C ++中提供的库特性,所以你应该使用最适合的方法。
我主要更喜欢printf,但我也使用了流,它提供了更清晰的代码,并且不必将%占位符与参数匹配。
这取决于实际情况。没有什么是完美的。我用两个。 Streams适用于自定义类型,因为您可以在ostream中重载>>运算符。但是当谈到间距等时,最好使用printf()。 stringstream和like比C风格的strcat()更好。所以使用一个适合这种情况的方法。
我已经阅读过警告说cout和cerr对多线程不安全。如果是真的,这是避免使用它们的一个很好的理由。注意:我使用GNU g ++和openMP。
cpp中的流是优选的,因为它们遵循面向对象的cpp范例,除了类型安全之外。
另一方面,printf更像是一种功能性方法。
只有在cpp代码中不使用printf的原因我才能想到它不是面向对象的。
它更多的是个人选择。
如果您希望自己的计划,请远离iostreams。问题是,如果句子由多个片段组成,就像使用iostream一样,就不可能正确地本地化你的字符串。
除了消息片段的问题,您还有一个订购问题。考虑一个打印学生姓名和平均成绩点的报告:
std::cout << name << " has a GPA of " << gpa << std::endl;
当您将其翻译成另一种语言时,另一种语言的语法可能需要您在名称前显示GPA。 AFAIK,iostreams无法重新排序插值。
如果你想要两全其美(类型安全和能够使用i18n),请使用Boost.Format。
任何printf
非POD的尝试都会导致未定义的行为:
struct Foo {
virtual ~Foo() {}
operator float() const { return 0.f; }
};
printf ("%f", Foo());
std::string foo;
printf ("%s", foo);
上面的printf调用产生了未定义的行为。您的编译器可能会向您发出警告,但标准不要求这些警告,并且对于仅在运行时已知的格式字符串不可能。
IO流:
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);
要么:
// re-usable (not common in my experience)
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
-argument”自动从表达式“派生”。这个推导的更精确等价实际上更像是这样的:
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 ++程序员每天都能完成的事情。另外,无论如何,这都是一种痛苦。
如果你持续使用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
判断......你自己。
借助可变参数模板,您可以在C ++ 11中使用类型安全的I18N友好printf。并且您将能够使用用户定义的文字使它们非常,非常高效,即可以编写完全静态的化身。
I have a proof of concept。那时候,对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因为我讨厌丑陋的<<cout<<
语法。
使用boost :: format。你得到类型安全,std :: string支持,类似接口的printf,使用cout的能力,以及许多其他好东西。你不会回去。
没有理由。我认为只是一些奇怪的意识形态驱使人们只使用C ++库,即使旧的C lib仍然有效。我是一个C ++人,我也使用C函数。从来没有遇到任何问题。
流是规范的方式。尝试使用printf
使用此代码:
template <typename T>
void output(const T& pX)
{
std::cout << pX << std::endl;
}
祝好运。
我的意思是,你可以让运算符允许你的类型输出到ostream
,并且没有麻烦使用它就像任何其他类型。 printf
不符合C ++的一般性,或更具体的模板。
不仅仅是可用性。还有一致性。在我的所有项目中,我都有cout(和cerr
和clog
)也将输出到文件中。如果你使用printf
,你会跳过所有这些。此外,一致性本身是一件好事;混合cout
和printf
,虽然完全有效,但是很难看。
如果你有一个对象,并且想要使它成为可输出的,那么最干净的方法就是为该类重载operator<<
。你打算怎么用printf
呢?你将最终得到与cout
和printf
混淆的代码。
如果您真的想要格式化,请在维护流接口的同时使用Boost.Format。一致性和格式。
使用printf。不要使用C ++流。 printf为您提供了更好的控制(例如浮动精度等)。代码通常也更短,更易读。
除了日志记录界面要求之外,不要使用流。请改用类似printf的例程。
使用流有各种各样的利弊,但在这种情况下,与许多其他情况一样,一致性胜过辩论。不要在代码中使用流。
总的来说我同意(讨厌<<语法特别是如果你需要复杂的格式化)
但我应该指出安全方面。
printf("%x",2.0f)
printf("%x %x",2)
printf("%x",2,2)
可能不会被编译器注意到,但可能会使您的应用程序崩溃。