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

时间:2010-01-07 00:55:36

标签: c++ formatting

我通常使用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声明吗?

20 个答案:

答案 0 :(得分:68)

我的学生首先学习cincout,然后再学习printf,绝大多数会更喜欢printf(或更常见的是fprintf)。我自己发现printf模型的可读性足以让我把它移植到其他编程语言中。 Olivier Danvy也是如此,他甚至让它变得类型安全。

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

免责声明:我是一个可怕的C ++程序员。

答案 1 :(得分:48)

如果您希望自己的计划,请远离iostreams。问题是,如果句子由多个片段组成,就像使用iostream一样,就不可能正确地定位你的字符串。

除了消息片段的问题,您还有一个订购问题。考虑一个打印学生姓名和平均成绩点的报告:

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

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

如果您想要两全其美(类型安全且能够使用i18n),请使用Boost.Format

答案 2 :(得分:21)

适应性

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 - 参数”是从表达式自动“派生”。这种推导的更精确等价实际上更像是这样:

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的人。这是一个功能,而不是一个bug!
  3. 如果你一直使用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
    

    判断......你自己。

    否。你不会禁止我的printf。

    由于可变参数模板,你可以在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 时。

    其他

    • Bug Potential :使用printf会有很多空间来提交错误,特别是当你在混合中输入用户输入基础字符串时(想想你的I18N团队)。你必须注意正确地转义每一个这样的格式字符串,你必须确保传递正确的参数等等。

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

答案 3 :(得分:19)

我使用printf是因为我讨厌丑陋的<<cout<<语法。

答案 4 :(得分:19)

使用boost :: format。你得到类型安全,std :: string支持,类似接口的printf,使用cout的能力,以及许多其他好东西。你不会回去。

答案 5 :(得分:7)

完全没有理由。我认为只是一些奇怪的意识形态驱使人们只使用C ++库,即使旧的C库仍然有效。我是一个C ++人,我也使用C函数。从来没有遇到任何问题。

答案 6 :(得分:6)

Streams是规范的方式。尝试将此代码与printf

配合使用
template <typename T>
void output(const T& pX)
{
    std::cout << pX << std::endl;
}
祝你好运。

我的意思是,您可以让操作员将您的类型输出到ostream,并且不用麻烦就像其他任何类型一样使用它。 printf不符合C ++的一般性,或者更具体地说是模板。

不仅仅是可用性。还有一致性。在我的所有项目中,我都有cout(和cerrclog)也可以输出到文件中。如果您使用printf,则会跳过所有这些。此外,一致性本身是一件好事;混合coutprintf虽然完全有效,但很难看。

如果您有一个对象,并且想要使其可输出,那么最简单的方法是为该类重载operator<<。那你打算怎么用printf呢?您最终会遇到与coutprintf混淆的代码。

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

答案 7 :(得分:5)

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

Google C++ style guide同意。

  

不要使用溪流,除非在哪里   日志记录界面所需的。使用   而是类似printf的例程。

     

有各种各样的利弊   使用流,但在这种情况下,如在   许多其他情况,一致性胜过   辩论。不要在你的流中使用溪流   代码。

答案 8 :(得分:5)

总的来说,我同意(讨厌&lt;&lt;语法,特别是如果你需要复杂的格式化)

但我应该指出安全方面。

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

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

答案 9 :(得分:4)

使用符合您需求和偏好的任何内容。如果您对printf感到满意,那么请务必使用它。如果你对iostreams感到满意,那就坚持下去吧。混合搭配最符合您的要求。毕竟这是软件 - 有更好的方法和更糟糕的方法,但很少有一种方式。

分享并享受。

答案 10 :(得分:3)

我不喜欢printf。它缺乏类型安全性使得使用起来很危险,而且需要记住格式说明符是一种痛苦。聪明地做正确事情的模板化运营商要好得多。所以我总是在C ++中使用C ++流。

当然,很多人更喜欢printf,因为其他的原因,列举在其他地方。

答案 11 :(得分: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()

答案 12 :(得分: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()格式字符串攻击)。

答案 13 :(得分:1)

fmt library将iostream的安全性和可扩展性与(s)printf的可用性和性能相结合,可以充分利用这两个方面的优势。例如:

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

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

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

答案 14 :(得分:0)

我几乎总是将printf用于临时调试语句。对于更永久的代码,我更喜欢'c'流,因为它们是 C ++ Way 。虽然boost :: format看起来很有前景,可能会取代我的流使用(特别是对于复杂格式的输出),但很长一段时间内我可能没有什么能替代printf。

答案 15 :(得分:0)

C ++ streams 被高估了,毕竟它们实际上只是具有重载运算符<<的类。 我已多次读过流是 C ++方式,因为printf是 C方式,但它们都是C ++中提供的库功能,所以你应该使用最适合的。
我更喜欢printf,但我也使用了流,它提供了更清晰的代码,并且可以防止你将%占位符与参数匹配。

答案 16 :(得分:0)

这取决于具体情况。没有什么是完美的。我用两个。 Streams适用于自定义类型,因为您可以重载&gt;&gt; ostream中的运算符。但是当谈到间距等时,最好使用printf()。 stringstream和like比C风格的strcat()更好。因此,请使用适合该情况的那个。

答案 17 :(得分:0)

我已经阅读过警告,说cout和cerr对多线程不安全。如果是真的,这是避免使用它们的一个很好的理由。注意:我使用GNU g ++和openMP。

答案 18 :(得分:0)

(请参见the fmt library homepage

在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和流更快。

答案 19 :(得分:-1)

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

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

我认为不能在cpp代码中使用printf的原因不是面向对象。

更多的是个人选择。