有效使用C ++ iomanip库

时间:2011-03-16 16:28:06

标签: c++ stream iostream iomanip

我在C ++中创建了一个Vector类,它对我的​​问题非常有用。我现在正在清理它,我遇到了以下代码:

std::ostream& operator<<(std::ostream &output, const Vector &v){
  output<<"["
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._x<<", "
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._y<<", "
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._z<<"]";
  return output;
} 

代码允许将矢量打印为std::cout<<v<<std::endl;。每个数字有23个空格,其中16个是小数。文本右对齐,以便打印:

 1.123456123456e+01
-1.123456123456e+01

而不是

1.123456123456e+01
-1.123456123456e+01

代码似乎非常重复。如何“存储”格式(所有setiosflagssetwsetprecision语句),以便您可以说“以标准方式打印字符,但数字与这个给定的格式“。

谢谢!

修改

根据Rob Adams的评论,我改变了我的丑陋代码(正如其他人所指出的那样,会让“下一个人”的精确度变得更加简洁(和正确)):

std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  output<<"["
    <<std::setw(23)<<v._x<<", "
    <<std::setw(23)<<v._y<<", "
    <<std::setw(23)<<v._z
    <<"]";
  output.flags(f);
  output.precision(p);
  return output;
}

4 个答案:

答案 0 :(得分:10)

只有std::setw()是暂时的。其他两个电话setiosflagssetprecision会产生永久性影响。

因此,您可以将代码更改为:

std::ostream& operator<<(std::ostream &output, const Vector &v){
  output<<"["
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._x<<", "
    <<std::setw(23)
    <<v._y<<", "
    <<std::setw(23)
    <<v._z<<"]";
  return output;
} 

但是现在你已经为下一个人加了旗子和精确度。试试这个:

std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  output<<"["
    <<std::setw(23)
    <<v._x<<", "
    <<std::setw(23)
    <<v._y<<", "
    <<std::setw(23)
    <<v._z<<"]";
  output.flags(f);
  output.precision(p);
  return output;
} 

最后,如果你必须摆脱常量23的重复,你可以做这样的事情(但我不推荐它):

struct width {
  int w;
  width(int w) : w(w) {}
  friend std::ostream& operator<<(std::ostream&os, const width& w) {
    return os << std::setw(width.w);
  }
};


std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  width w(23);
  output<<"["
    <<w
    <<v._x<<", "
    <<w
    <<v._y<<", "
    <<w
    <<v._z<<"]";
  output.flags(f);
  output.precision(p);
  return output;
} 

另见this other question,他们决定不能使宽度永久。

答案 1 :(得分:3)

在 C++20 中,您将能够:

std::ostream& operator<<(std::ostream& output, const Vector& v){
  const int width = 23, precision = 16;
  return output << std::format(
      "[{0:{3}.{4}e}, {1:{3}.{4}e}, {2:{3}.{4}e}]",
      v._x, v._y, v._z, width, precision);
} 

与 I/O 操作符不同,std::format 不会改变 ostream 的格式化状态,从而避免了 Bo Persson 提到的问题:

<块引用>

你真正的问题是这个输出之后的下一个输出会发生什么......

I/O 操纵器也能与 Vector 一起正常工作。

std::format 可用之前,您可以使用它所基于的 the {fmt} library

答案 2 :(得分:1)

除了setw()之外的所有内容实际上都已经这样做了。它们是“粘性的”。

你真正的问题是在这之后的下一个输出会发生什么......

答案 3 :(得分:1)

通常,您不直接使用标准操纵器。在 例如,在这种情况下,您可以定义一个操纵器 fromVector,并使用:

output << '['
       << fromVector << v.x << ", "
       << fromVector << v.y << ", "
       << fromVector << v.z << ']';

这样,如果你想改变宽度和精度 向量中的元素,你只需要在一个地方做。

在这种情况下,操纵器没有参数,所有这些都是 需要的是一个简单的功能:

std::ostream& fromVector(std::ostream& stream)
{
    stream.setf(std::ios::right, std::ios::adjustfield);
    stream.setf(std::ios::scientific, std::ios::floatfield);
    stream.precision(16);
    stream.width(32);
    return stream;
}

当然,这将改变任何格式的大部分格式 以后的用户。一般来说,使用它会更好 一个临时类对象,它保存状态并恢复它 在析构函数中。我通常派出我的操纵者 类似的东西:

头:     class StateSavingManip     {     上市:                             StateSavingManip(                                     StateSavingManip const&amp;其他);

    virtual             ~StateSavingManip() ;
    void                operator()( std::ios& stream ) const ;

protected:
                        StateSavingManip() ;

private:
    virtual void        setState( std::ios& stream ) const = 0 ;

private:
    StateSavingManip&   operator=( StateSavingManip const& ) ;

private:
    mutable std::ios*   myStream ;
    mutable std::ios::fmtflags
                        mySavedFlags ;
    mutable int         mySavedPrec ;
    mutable char        mySavedFill ;
} ;

inline std::ostream&
operator<<(
    std::ostream&       out,
    StateSavingManip const&
                        manip )
{
    manip( out ) ;
    return out ;
}

inline std::istream&
operator>>(
    std::istream&       in,
    StateSavingManip const&
                        manip )
{
    manip( in ) ;
    return in ;
}

源:     int getXAlloc();     int ourXAlloc = getXAlloc()+ 1;

int
getXAlloc()
{
    if ( ourXAlloc == 0 ) {
        ourXAlloc = std::ios::xalloc() + 1 ;
        assert( ourXAlloc != 0 ) ;
    }
    return ourXAlloc - 1 ;
}
}

StateSavingManip::StateSavingManip()
    :   myStream( NULL )
{
}

StateSavingManip::StateSavingManip(
    StateSavingManip const&
                        other )
{
    assert( other.myStream == NULL ) ;
}

StateSavingManip::~StateSavingManip()
{
    if ( myStream != NULL ) {
        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 == NULL ) {
        backptr      = const_cast< StateSavingManip* >( this ) ;
        myStream     = &stream ;
        mySavedFlags = stream.flags() ;
        mySavedPrec  = stream.precision() ;
        mySavedFill  = stream.fill() ;
    }
    setState( stream ) ;
}

如果你这样做,你必须在之后添加括号 操纵者,例如:

output << '['
       << fromVector() << v.x << ", "
       << fromVector() << v.y << ", "
       << fromVector() << v.z << ']';

(我确信一些聪明的灵魂会找到一种方法 避开他们,但他们从来没有打扰过我,所以我没有 打扰。)