Эффективное использование библиотеки C ++ iomanip - PullRequest
7 голосов
/ 16 марта 2011

Я создал Vector класс в C ++, и он отлично работает для моих задач. Сейчас я его чищу и наткнулся на следующий фрагмент кода:

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

Код кажется ужасно повторяющимся. Как вы можете «сохранить» формат (все операторы setiosflags, setw и setprecision) так, чтобы вы могли сказать что-то вроде «печатать символы стандартным способом, но числа с этим заданным форматом».

Спасибо!

Редактировать

Согласно комментарию Роба Адамса, я изменил свой уродливый код (который, как указывали другие, испортил бы точность для «следующего парня»), на более сжатый (и правильный):

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

Ответы [ 3 ]

10 голосов
/ 16 марта 2011

Только std::setw() является временным. Два других вызова setiosflags и setprecision имеют постоянный эффект.

Итак, вы можете изменить свой код на:

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

См. Также этот другой вопрос , где они решили, что нельзя сделать ширину постоянной.

1 голос
/ 16 марта 2011

Как правило, вы не используете стандартные манипуляторы напрямую. В В этом случае, например, вы можете определить манипулятор 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;
}

Конечно, это изменит большую часть форматирования для любого позже пользователь. В общем, было бы лучше использовать временный объект класса, который сохраняет состояние и восстанавливает его в деструкторе. Я обычно получаю свои манипуляторы от что-то вроде:

Заголовок: Класс StateSavingManip { общественности: 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 << ']';

(Я уверен, что какая-то умная душа найдет способ избегая их, но они никогда не беспокоили меня, поэтому я не беспокоили.)

1 голос
/ 16 марта 2011

Все, кроме setw (), на самом деле это уже делает. Они "липкие".

Ваша настоящая проблема в том, что происходит со следующим выводом после этого ...

...