C++ iomanip 库的有效使用

9

我在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;
}

不完全是 https://dev59.com/c3RC5IYBdhLWcg3wG9Xp 的复制。 - Robᵩ
4个回答

11

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;
} 

另请参见这个问题,在那里他们决定您无法使宽度永久。


非常感谢 - 你不仅解决了我的问题,还教会了我关于流概念的一些东西。谢谢! - Escualo

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 提到的问题:

Your real problem is what happens to the next output after this one...

它也能正确处理 Vector 与 I/O 操作符相同的工作。
std::format 可用之前,你可以使用基于 {fmt} 库 的库。

2
除了setw()函数以外,其他函数已经实现了此功能。它们是“粘性的”。
你真正的问题是接下来的输出会发生什么...

2

通常情况下,你不会直接使用标准的操作符。例如,在这种情况下,您可以定义一个名为 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 { public: StateSavingManip( StateSavingManip const& other ) ;

    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 << ']';

我相信有些聪明的人会想出一种方法来避免它们,但它们从未困扰过我,所以我没有介意。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接