我应该在C ++代码中使用printf吗?


71

我通常使用coutcerr向控制台写入文本。但是有时我发现使用好的旧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语句的原因吗?


3
对于控制台I / O ES马赫特nichts(没关系)。从总体上看,printf与C ++流不兼容。C ++流使您可以轻松地将控制台输出转换为文件。(尽管您可以使用进行类似操作fprintf)。
Thomas Matthews 2010年

那使用sprintf + cout呢?
拉里·渡边

14
注意,您的两条线并不严格等效。endl也可以清空流,因为如果你写了printf("(%d,%d)\n", x, y); fflush(stdout);这会增加一个的性能损失,如果在一个循环中重复执行。要获得与C ++中的printf语句cout << "(" << x << "," << y << ")\n";
完全相同的结果,

2
@bobobobo,“注释”的错字。
斯特拉格2012年

Answers:


73

我的学生,谁学习cincout第一,然后学习printf后,绝大多数喜欢printf(或者更通常fprintf)。我本人已经发现该printf模型具有足够的可读性,因此已将其移植到其他编程语言中。所以有奥利维尔·丹维,谁已经连上了类型安全。

如果您有一个能够对调用进行类型检查的编译器printf,我认为没有理由不使用fprintfC ++和朋友。

免责声明:我是一位糟糕的C ++程序员。


17
我经常使用Java String.format。在C ++中,我经常使用Boost.Format,这是iostreams友好的,但也有些printf兼容。
克里斯·杰斯特·杨

5
* printf缺乏类型安全性可以得到缓解,但不会被检查这些调用的编译器所消除,因为将变量用作格式字符串是一个完全有效的用例。例如:i18n。此功能可以通过多种方式爆炸,甚至不好笑。我只是不再使用它了。我们可以使用完美的格式化程序,例如boost :: format或Qt :: arg。
rpg 2010年

@rpg:我认为要在可读性和类型安全之间进行权衡。我认为对于不同的应用程序进行不同的权衡是合理的。一旦您进入i18n,可读性无论如何已经超出了一半,在这种情况下,我同意类型安全胜过printf。有趣的是,boost::format工作方式与Olivier Danvy的ML代码的工作方式有些相似。
Norman Ramsey 2010年

2
@Norman拉姆塞:我看到type safety problemprintf()多次提到。printf()的类型安全问题到底是什么?
Lazer 2010年

@诺曼·拉姆齐(Norman Ramsey):如果类型不检查,我不确定会出现什么问题。你能举个例子吗?我一直printf()在用C。从来没有遇到问题。
Lazer

49

如果您希望使用i18n程序,请远离iostream。问题是,如果句子由多个片段组成(如iostream),则可能无法正确定位字符串。

除了消息片段的问题之外,您还遇到了排序问题。考虑一个打印学生姓名及其平均成绩的报告:

std::cout << name << " has a GPA of " << gpa << std::endl;

将其翻译为另一种语言时,另一种语言的语法可能需要您在名称之前显示GPA。AFAIK,iostreams无法重新排序插值。

如果您希望两全其美(输入安全性并能够使用i18n),请使用Boost.Format


5
通过boost :: format()中的位置指定格式化参数的功能非常适合本地化。
Ferruccio 2010年

11
但是boost::format给您带来诸如类型安全之类的好处。printf刚刚炸毁。
jalf

2
好答案。只是想补充一下,Boost Format的替代方案在提供相同级别的安全性的同时,速度快了好几倍:github.com/vitaut/formatgithub.com/c42f/tinyformat
vitaut 2013年

22

适应性

printf非POD的任何尝试都会导致不确定的行为:

struct Foo { 
    virtual ~Foo() {}
    operator float() const { return 0.f; }
};

printf ("%f", Foo());

std::string foo;
printf ("%s", foo);

上面的printf-calls产生不确定的行为。您的编译器可能确实会警告您,但是这些警告不是标准所必需的,并且对于仅在运行时才知道的格式字符串是不可能的。

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");

I18N

我们重复使用我们的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 ++程序员每天都会做的事情。另外,无论如何,这都是解剖学上的痛苦。

性能

  1. 您是否衡量了printf性能的实际意义?您的瓶颈应用程序是否真的如此懒惰,以至于计算结果的输出就是瓶颈?您确定您完全需要C ++吗?
  2. 可怕的性能损失是要满足那些想同时使用printf和cout的用户。这是功能,不是错误!

如果您始终使用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/nullcat RESULTS):

C ..............: 1.1 sec
C++, sync with C: 1.76 sec
C++, non-sync ..: 1.01 sec

判断...你自己。

不,您不会禁止我打印。

借助可变参数模板,您可以在C ++ 11中为类型安全的I18N友好的printf设置字符。使用用户定义的文字,您将可以使它们具有非常出色的性能,即可以编写完全静态的化身。

我有一个概念证明。那时,对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有很多出错的空间,尤其是当您将用户输入的基本字符串混合在一起时(例如您的I18N团队)。您必须注意正确地转义每个这样的格式字符串,必须确保传递正确的参数,等等。

  • IO-Streams使我的二进制文件更大:如果这是比可维护性,代码质量,可重用性更重要的问题,那么(在确认问题之后!)请使用printf。


2
现代编译器会诊断格式规范和参数之间的不匹配。
vitaut

1
谢谢-这是一个很好的解释!@vitaut他们通常不能。例如,如果格式规范本身不是常量字符串文字,而是来自配置文件。在大多数商用软件中,对于18n并为了保持通信的一致性,这并不是一个假设的示例,大多数字符串不是就地文字,而是并非总是按文字分隔。
死灵法师2013年

1
@agksmehx:非常不错的示例,它可以验证格式字符串检查的可靠性。
塞巴斯蒂安·马赫


19

使用boost :: format。您将获得类型安全性,std :: string支持,类似于printf的界面,使用cout的能力以及许多其他好处。你不会回去的。


7

完全没有理由。我认为,即使好的旧C库仍然有效,也只是一些奇怪的意识形态促使人们开始只使用C ++库。我是C ++专家,我也经常使用C函数。从来没有任何问题。


4
c库质量很好,并且易于使用。我同意人们不应该感到使用c ++库的压力。
马特·乔纳

1
我的锤子也从未遇到过问题。它既简单又健壮,我知道用它盖房子时会发生什么。
塞巴斯蒂安·马赫

@phresnel,这是正确的:到目前为止,使用锤子敲钉子是完成任务的最佳工具。据称,岩石也可以工作,但不如锤子好。
mingos

@mingos:我更喜欢用水泥,螺丝,螺栓固定在一起的房屋(但是一把好锤子可以做到)。用锤子将砖头抬到三楼并在屋顶上锯切梁有点困难,但它的工作原理是:)我有时也使用C函数,尤其是在与其他软件交谈时,但是在很大程度上,我更喜欢编译器通过强大和静态的类型安全性为我捕获错误。这样,如果我在某个地方更改了部分代码并进行了完全重新编译,那么我可以依靠的地方到处都会出错(但这只是我推理的冰山一角)。
塞巴斯蒂安·马赫

1
一个误用的功能也是不反对使用它在所有:)一个原因。我不主张使用printfover cout(尽管其他人也这样做-链接到该问题的另一个答案),并且我同意在某些情况cout显然更合适的选择。但是,我坚信,仅出于“ C ++方式”的目的而使用coutoverprintf仅仅是宗教性的bs。
mingos

6

流是规范的方式。尝试使用以下代码printf

template <typename T>
void output(const T& pX)
{
    std::cout << pX << std::endl;
}

祝好运。

我的意思是,您可以让运算符将您的类型输出到ostream,而不必像其他任何类型一样麻烦地使用它。printf不适合C ++或更一般的模板。

不仅仅是可用性。也有一致性。在我所有的项目中,我都将cout(和cerrclog)也输出到文件中。如果使用printf,则跳过所有这些。另外,一致性本身是一件好事。混合coutprintf,尽管完全有效,却很难看。

如果您有一个对象,并且想要使其成为可输出的,则最干净的方法是operator<<对该类进行重载。那你打算怎么用printf呢?您将最终得到混杂有couts和printfs的代码。

如果您确实要格式化,请在维持流接口的同时使用Boost.Format。一致性格式。


9
您没有因为自己的观点正确而被否决,而是因为您的观点无关紧要而被否决。我们知道printf不能做到这一点,它不是设计来做到的。就像在说,“哦,我敢打赌,你不能用C上课”。那一点也将是正确的,因为该语言并非旨在如此设计的。
鲍勃·迪伦

10
几乎没有关系。您在问是否应该使用printf。为了保持一致,。流将始终有效,printf而不会始终有效。不一致的代码很难看。如果我cout还要打印到日志文件该怎么办?(是的,我所有的项目都这样做!)现在,您只需绕过所有这些内容。除了保存按键之外,还有更多需要考虑的问题。
GManNickG 2010年

1
因此,您必须为T编写一个重载<<操作符,而不是print(T)函数。
马丁·贝克特

1
@bobobobo:所以你建议那printf(stdout, "Hello "); person.print(stdout); printf("! I see you've subscribed to the "); tags.print(stdout); printf("tags, nice!");比不那么疯狂,更漂亮cout << "Hello " << person << "! I see you've subscribed to the " << tags << ", nice!";
塞巴斯蒂安·马赫

1
您的代码假定pX具有的运算符方法<<。如果我认为pX有一种方法,(char *)我可以用printf()同一个(char *)演员。这样做没有任何优势cout。就我个人而言,我非常不喜欢<<运算符的繁琐含义,因此我倾向于使用printf()或其他解决方案。
比尔·韦曼

5

使用printf。不要使用C ++流。printf为您提供更好的控制(例如浮点精度等)。该代码通常也更短,更易读。

Google C ++样式指南同意。

除非记录接口要求,否则不要使用流。请改用类似printf的例程。

使用流有多种利弊,但在这种情况下,就像在许多其他情况下一样,一致性胜过争论。不要在代码中使用流。


+1的链接。这不是10条诫命,但他们肯定会直起头来。
ojrac 2010年

2
在我看来,尽管《 Google C ++样式指南》在许多方面都非常出色,但他们所指的一致性胜过他们自己的代码。请记住,Google诞生已有10年了,他们重视代码的一致性(这是一件非常好的事情)。之所以不使用printf,是因为人们在以前的代码版本中使用过printf,并且希望保持一致。如果不是这种情况,我相信他们会改用流。
杰夫

1
??!您也可以使用iostream进行精确控制。
塞巴斯蒂安·马赫2012年

5

总的来说,我同意(讨厌<<语法,尤其是在需要复杂格式的情况下)

但我要指出安全方面。

printf("%x",2.0f)
printf("%x %x",2)
printf("%x",2,2)

编译器可能不会注意到它,但可能会使您的应用程序崩溃。


1
在这里同意。看起来很烦人,但是在编译器中尽可能多地施加来自编译器的限制总是一个好主意。启用所有警告,将所有警告视为错误,并尽可能使用电围栏+ gdb。但这是一般的编码技巧;)
mingos 2010年

有趣的是,xCode IDE会检测到您在此处提到的这些内容(以其智能感知。)
bobobobo 2010年

代码不一定总是在ide或本地系统中流行,也不总是由同一方设计。格式字符串取决于平台(因为变量在不同平台上的大小不同。在vs 2010中long是64位。在gcc中是32位。Boom,欠载)。但是,令我惊讶的是,很少有人知道标准格式的标头带有用于正确格式字符串的字符串文字。
斯威夫特-星期五派

4

使用适合您需求和偏好的任何东西。如果您对printf感到满意,则一定要使用它。如果您对iostream更加满意,请坚持使用它们。混合搭配以最适合您的要求。毕竟,这是软件-有更好的方法和更糟糕的方法,但是很少只有一种方法。

分享并享受。


3

我不喜欢printf。它缺乏类型安全性,因此使用起来很危险,再加上需要记住格式说明符的麻烦。巧妙地做正确的事的模板化运算符要好得多。所以我总是在C ++中使用C ++流。

当然,由于其他原因,许多人更喜欢printf,在其他地方已列举。


3

我经常“退回”使用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()自己!


2

您可以使用fmt库获得两全其美的功能,该将iostream的安全性和可扩展性与易用性和性能相结合(s)printf。例:

std::string = fmt::format("The answer is {}", 42);

该库支持类似Python和printf格式的字符串语法。

免责声明:我是fmt库的作者。


1

即使这个问题比较老,我还是要加两分钱。

使用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()格式字符串攻击)。


恐怕这不是该printf接口的扩展。如果您现在可以写的话printf("%[MyClass] is awesome, my_object")
塞巴斯蒂安·马赫

@phresnel我同意。这只是说可以用printf打印用户创建的对象。这是一种解决方法,是的,但是由于缺乏C ++ 11之前的实际vararg函数功能,操作员链接解决方法也是如此。现在创建的C ++ I / O库可能会使用initializer_list或可变长度模板函数。
milleniumbug

尽管操作员链接是iostreams接口的一部分,并且扩展该接口的可能性也是该接口的一部分,所以iostreams是可扩展的,而printf却不是,这仍然成立。这就是我想指出的问题:)
塞巴斯蒂安·马赫

我同意如果新的io-library成为C ++的一部分,现在将如何使用可变参数模板。
塞巴斯蒂安·马赫

@phresnel更改了答案中的措词。
milleniumbug

1

(请参见fmt库主页

在C ++ 20中,格式化部分的fmt库是标准化的:

std::format("({},{})\n", x, y) // returns a std::string

您可以使用format_to以下方法避免动态分配开销:

std::format_to(/* output iterator */, "({},{})\n", x, y);

应该将其视为规范的格式化方式,因为它结合了流的优点:

  • 安全:磁带库是完全类型安全的。自动内存管理可防止缓冲区溢出。使用异常或在编译时报告格式字符串中的错误。

  • 可扩展性:重载operator<<很容易,而扩展printf则不是那么容易。

以及printf

  • 易于使用%支持语法,而不是冗长的操纵符。该{}语法也被引入,以消除符。

  • 性能:测量表明,fmt库是迄今为止C ++中最快的输出方法。比printf和更快。


0

我几乎总是将printf用于临时调试语句。对于更永久的代码,我更喜欢'c'流,因为它们是C ++ Way。尽管boost :: format看起来很有希望,并且可能会代替我的流用法(尤其是对于格式复杂的输出),但是很长一段时间内,可能没有什么可以代替printf。


0

C ++被高估了,毕竟它们实际上只是带有重载运算符的类<<
我已经读过很多次了,流是C ++方式,而printf是C方式,但是它们都是C ++中可用的库功能,因此您应该使用最适合的方式。
我最喜欢printf,但我也使用了流,它提供了更简洁的代码,并避免了将%占位符与参数匹配的情况。


不仅有你相匹配的占位符现在,而且在将来。请参阅我更新的文章“时间适应性”一节以及其他课程。
塞巴斯蒂安·马赫

0

这取决于实际情况。没有什么是完美的。我都用。流适用于自定义类型,因为您可以在ostream中重载>>运算符。但是,当涉及到间距等时,最好使用printf()。stringstream和like优于C风格的strcat()。因此,请使用适合该情况的一种。


为什么printf的间距更好?
塞巴斯蒂安·马赫

@phresnel cout << str1 <<“” << str2 <<'\ n'; 比printf(“%s%s”,str1,str2)差很多;
量子


-1

在cpp中,流是首选的,因为它们遵循cpp的面向对象范例,而且类型安全。

另一方面,printf更像是一种功能方法。

我能想到的在cpp代码中不使用printf的唯一原因不是面向对象的。

它更多是个人选择。


1
使用C ++并不意味着您必须使所有对象都面向对象,尤其是控制台打印并不是真正需要OO范式的东西。
Petruza 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.