2011-03-16 9 views
7

Stworzyłem klasę Vector w C++ i działa doskonale na moje problemy. Jestem teraz czyszczenie go i wpadłem na następujący fragment kodu:Efektywne wykorzystanie biblioteki iomanip 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; 
} 

Kod umożliwia drukowanie wektor jako std::cout<<v<<std::endl;. Każda liczba ma 23 spacje, z których 16 to cyfry dziesiętne. Tekst jest wyrównany do prawej tak, że będzie drukować:

1.123456123456e+01 
-1.123456123456e+01 

Zamiast

1.123456123456e+01 
-1.123456123456e+01 

Kod wydaje się strasznie monotonne. Jak możesz "przechowywać" format (wszystkie instrukcje , i setprecision) w taki sposób, że możesz powiedzieć coś w stylu "wypisz znaki w standardowy sposób, ale numery o tym samym formacie".

Dziękujemy!

Edit

Zgodnie komentarza Roba Adamsa, zmieniłem brzydki kod (który, jak wskazano przez innych, by zepsuć dokładność dla «next guy») do bardziej zwięzły (i poprawne):

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

Niezupełnie duplikat http://stackoverflow.com/questions/405039/permanent-stdsetw –

Odpowiedz

10

Tylko std::setw() ma charakter tymczasowy. Pozostałe dwa wywołania: i setprecision mają trwały efekt.

Tak, można zmienić swój kod do:

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

Ale teraz już borked flagi i precyzja do kolejnego faceta. Spróbuj to zamiast:

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

Wreszcie, jeśli koniecznie musisz pozbyć się powielania stałej 23, można zrobić coś takiego (ale nie polecam go):

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

Zobacz także this other question, gdzie zdecydowano, że nie można ustawić szerokości na stałe.

+0

Wielkie dzięki - nie tylko rozwiązać mój problem, ale także ta powiedz mi coś o ogólnej koncepcji strumienia. Dzięki! – Escualo

1

Wszystko oprócz setw() faktycznie to już robi. Są "lepkie".

Twój prawdziwym problemem jest to, co dzieje się z kolejnego wyjścia po tym jednym ...

1

Zwykle nie używasz standardowych manipulatorów bezpośrednio. W tym przypadku, na przykład, można zdefiniować manipulatora fromVector i używać że:

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

ten sposób, jeśli chcesz zmienić szerokość i precyzję elementów w wektorze, trzeba tylko zrób to w jednym miejscu.

W tym przypadku, jeżeli manipulator ma żadnych argumentów, wszystko, co potrzebne jest prostą funkcją:

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

Oczywiście, będzie to się zmieniło większość formatowania dla każdego późniejszego użytkownika. Ogólnie rzecz biorąc, lepszym rozwiązaniem byłoby użycie tymczasowego obiektu klasy, który zapisuje stan i przywraca go w destruktorze. I zazwyczaj wywodzą swoje manipulatory z coś takiego:

nagłówek: klasa StateSavingManip { publiczny: StateSavingManip ( StateSavingManip const & inne);

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

źródło: 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) ; 
} 

Jeśli to zrobisz, będziesz musiał dodać nawiasy po manipulatora, np:

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

(jestem pewien, że jakiś sprytny dusza będzie wymyślić jakiś sposób na uniknięcie im, ale nigdy mi nie przeszkadzało, więc nie przeszkadza mi ).