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

问题描述 投票:67回答:19

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

c++ formatting
19个回答
68
投票

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

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

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


4
投票

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

分享和享受。


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()


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


1
投票

fmt library结合了iostreams的安全性和可扩展性以及(s)printf的可用性和性能,您可以获得两全其美的效果。例:

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

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

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


0
投票

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


0
投票

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


0
投票

这取决于实际情况。没有什么是完美的。我用两个。 Streams适用于自定义类型,因为您可以在ostream中重载>>运算符。但是当谈到间距等时,最好使用printf()。 stringstream和like比C风格的strcat()更好。所以使用一个适合这种情况的方法。


0
投票

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


-1
投票

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

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

只有在cpp代码中不使用printf的原因我才能想到它不是面向对象的。

它更多的是个人选择。


48
投票

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

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

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

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

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


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-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 ++程序员每天都能完成的事情。另外,无论如何,这都是一种痛苦。

性能

  1. 你有没有测量过printf性能的实际意义?您的瓶颈应用是否严重如此懒惰以至于计算结果的输出是瓶颈?你确定你需要C ++吗?
  2. 可怕的性能损失是为了满足那些想要混合使用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/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潜力:使用printf会有很多空间来提交错误,特别是当你在混合中输入用户输入基础字符串时(想想你的I18N团队)。你必须注意正确地转义每个这样的格式字符串,你必须确保传递正确的参数等等。
  • IO-Streams使我的二进制文件更大:如果这是一个比可维护性,代码质量,可重用性更重要的问题,那么(在验证问题之后!)使用printf。

19
投票

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


19
投票

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


7
投票

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


6
投票

流是规范的方式。尝试使用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。一致性和格式。


5
投票

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

Google C++ style guide同意。

除了日志记录界面要求之外,不要使用流。请改用类似printf的例程。

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


5
投票

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

但我应该指出安全方面。

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

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

© www.soinside.com 2019 - 2024. All rights reserved.