【问题标题】:Using various format specifiers of c in c++在 c++ 中使用 c 的各种格式说明符
【发布时间】:2025-12-31 07:25:11
【问题描述】:

在 c 中,我们可以使用各种格式说明符,例如

  • %nd 其中 n 是一个数字,以打印总共覆盖至少 n 个空格的数字
  • %0nd 同上,除了用 0 预填充 " %05d ",3 => 00003
  • %.nf 设置小数点后 n 的精度
  • 等等....

那么有没有办法将这些与 std::cout 一起使用?

我最近在 coursera 的一门课程(c++ 为 c 程序员)中得到了一些负面反馈,因为我想使用 printf 而不是 cout :(

【问题讨论】:

  • 也许这会对你有所帮助。 *.com/questions/275484/cout-formatting
  • Boost.Format 有时可能是两全其美的东西。它是类型安全的,并且比 C++ 的 <iomanip> 更紧凑。可能在课程中不太有用,但很高兴了解。
  • @chris 遗憾的是,它使用% 作为插入运算符以及格式说明符,这导致代码实际上不可读。 (Python 尝试了这个,最后切换到使用 stringformat 的成员函数。该解决方案在 C++ 中效果不佳,因为它需要类型安全的可变参数工具。)当然,即使使用 @ 987654327@,您需要使用操纵器,将格式信息与文本分开(维护必不可少的东西)。

标签: c++ format-specifiers


【解决方案1】:

对于 %nd %0nd,C++ 等效项是 std::setw()std::setfill()

#include <iostream>     // std::cout, std::endl
#include <iomanip>      // std::setfill, std::setw

int main () {
  std::cout << std::setfill ('x') << std::setw (10);
  std::cout << 77 << std::endl;
  return 0;
}

输出:xxxxxxxx77

%.nf可以替换为std::setprecisionstd::fixed

#include <iostream>     // std::cout, std::fixed, std::scientific

int main () {
    double a = 3.1415926534;
    double b = 2006.0;
    double c = 1.0e-10;

    std::cout.precision(5);

    std::cout << "fixed:\n" << std::fixed;
    std::cout << a << '\n' << b << '\n' << c << '\n';
    return 0;
}

输出:

fixed:
3.14159
2006.00000
0.00000

【讨论】:

  • 是的,但你永远不想在生产代码中使用它们中的任何一个(可能除了std::setw)(尽管它们对于快速测试很方便)。另外:&lt;ios&gt;&lt;istream&gt; 中也定义了许多操纵器。 &lt;iomanip&gt; 只定义了带参数的那些。
  • @JamesKanze:我知道。我认为上面的操纵器足以供OP使用。
  • 他的要求之一是%.nf。为此,您需要std::fixed,它位于&lt;ios&gt;。 (注意std::setiosflags不能用于设置基数、浮点格式或调整字段。)
  • @JamesKanze 和 user1920482,cout
  • @HarendraSingh 这是cout &lt;&lt; setprecision(n),但cout.precision(n)。第一个是机械手(我认为我从未使用过);第二个成员函数(操纵器将调用),通常在自定义操纵器中使用,作为更大的格式化选项集的一部分。 ostream 的成员函数(或更常见的是它的基类)和标准操纵器被设置为非常低级别的物理信息;在任何大型应用程序中,您都需要指定 logical 元素。
【解决方案2】:

C++ streams 不要使用格式说明符,例如 C 的 printf()-type 函数;他们使用manipulators

例如:

#include <iostream>
#include <iomanip>


int main()
{
    std::cout << std::fixed << std::setprecision(6) << 42.0 << std::endl;
}

输出:

42.000000

See it run!

【讨论】:

    【解决方案3】:

    C++ 中通常的解决方案是定义操纵器 你试图格式化什么,而不是破解物理值 直接在输出点。 (一个可能的例外是 宽度,其中std::setw 可能直接有用。)因此,对于 例如,当实际输出一些东西时,你不会指定 零填充,或固定,带 2 位小数,但类似于:

    std::cout << temperature(2) << theTemporature;
    

    temperature 类似于:

    class temperature
    {
        int myMinWidth;
    public:
        temperature( int minWidth )
            : myMinWidth( minWidth )
        {
        }
        friend std::ostream& operator<<( std::ostream& dest, temperature const& manip )
        {
            dest.setf( std::ios_base::fixed, std::ios_base::floatfield );
            dest.precision( 2 );
            dest.width( myMinWidth );
            return dest;
        }
    };
    

    有关可用的格式修改列表,请参阅 std::ios_base 的规范,以及以下字段 std::ios_base::fmtflags.

    如果您要进行大量输出,您可能需要修改它 在完整的末尾恢复原始格式标志 表达。 (除宽度外的所有格式信息都是 粘性的,所以在这里强制固定格式会让你得到固定的 程序其余部分的格式,不一定是什么 你想要。)我使用以下作为我所有的基类 操纵者:

    class StateSavingManip
    {
    public:
        void operator()( std::ios& stream ) const;
    protected:
        StateSavingManip() : myStream( nullptr ) {}
        ~StateSavingManip();
    private:
        virtual void setState( std::ios& stream ) const = 0;
    private:
        mutable std::ios* myStream;
        mutable std::ios::fmtflags mySavedFlags;
        mutable int mySavedPrec;
        mutable char mySavedFill;
    };
    

    实现:

    namespace {
    int getXAlloc() ;
    int ourXAlloc = getXAlloc() + 1 ;
    
    int
    getXAlloc()
    {
        if ( ourXAlloc == 0 ) {
            ourXAlloc = std::ios::xalloc() + 1 ;
            assert( ourXAlloc != 0 ) ;
        }
        return ourXAlloc - 1 ;
    }
    }
    
    StateSavingManip::~StateSavingManip()
    {
        if ( myStream != nullptr ) {
            myStream->flags( mySavedFlags ) ;
            myStream->precision( mySavedPrec ) ;
            myStream->fill( mySavedFill ) ;
            myStream->pword( getXAlloc() ) = NULL ;
        }
    }
    
    void
    StateSavingManip::operator()( 
        std::ios&           stream ) const
    {
        void*&              backptr = stream.pword( getXAlloc() ) ;
        if ( backptr == nullptr ) {
            backptr      = const_cast< StateSavingManip* >( this ) ;
            myStream     = &stream ;
            mySavedFlags = stream.flags() ;
            mySavedPrec  = stream.precision() ;
            mySavedFill  = stream.fill() ;
        }
        setState( stream ) ;
    }
    

    注意pword 字段的使用,以确保只有第一个 临时操纵器恢复格式;析构函数将 以相反的构造顺序调用,但顺序 如果您有更多,通常不会指定构造 而不是表达式中的一个这样的操纵器。

    最后:使用这种技术并非一切皆有可能:如果你 想系统地为温度附加度数符号, 没有办法这样做。在这种情况下,您需要定义 一个温度类,并为其重载&lt;&lt; 运算符;这 允许一切可以想象的事情(比你想象的要多得多 使用printf 样式格式实现)。

    【讨论】:

      【解决方案4】:

      C++ 流操纵器 (iomanip) 专门设计用于支持所有标准 c 格式说明符操作,只是具有完全不同的接口。例如。 setfillsetw 用于 %02d 的宽度和填充部分。

      当然,如果你真的需要格式字符串(例如,因为它使 i18n 对你来说更容易),那么你应该看看boost::format,如果你有 C++11,那么你可以轻松编写小型可变参数模板包装器围绕它,使格式调用看起来更像 printf。

      无论你做什么,请尽量不要使用 printf。它不是类型安全的,并且对于用户定义类型的输出操作不可扩展。

      【讨论】:

      • boost::format 对你来说真的不多。插入运算符 (%) 的错误选择会导致代码相当不可读。如果您需要位置参数处理(相当于 X/Open 对printf 格式的扩展),这可能是一个不错的选择,但对于格式本身,您需要使用操纵器,因为出于维护原因,您不需要不希望在文本字符串中混合物理格式。
      【解决方案5】:

      有流操作器,如果你需要的话。
      但我想你想知道这个东西:
      coutprintf() 更聪明。 假设你有:

      int x = 34;
      cout<<x;
      

      现在编译器会调用:

      ostream& operator<<(ostream& stream, int arg);
      

      为你。此函数将在控制台中打印输出(因为在您的情况下streamcout)。对于所有可用的基本数据类型,标准库为此 &lt;&lt; 运算符提供重载。

      【讨论】:

      • 首先,这个函数not在控制台打印输出:它输出到stream去的任何地方(可能是一个文件、一个字符串或任何东西别的)。其次,这并没有解决他的问题:如何修改用于输出的格式。第三,你真的应该提到你可以为你自己的类型重载&lt;&lt;;这确实是最重要的问题之一。