2011-03-16 6 views
7

Ho creato una classe Vector in C++ e funziona perfettamente per i miei problemi. Ora sto pulirlo, e mi sono imbattuto nel seguente frammento di codice:Uso efficace della libreria 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; 
} 

Il codice consente di stampare un vettore come std::cout<<v<<std::endl;. Ogni numero ha 23 spazi, di cui 16 sono i decimali. Il testo è allineato a destra in modo che stamperà:

1.123456123456e+01 
-1.123456123456e+01 

Invece di

1.123456123456e+01 
-1.123456123456e+01 

Il codice sembra terribilmente ripetitivo. Come si può "memorizzare" il formato (tutte le istruzioni , setw e setprecision) in modo tale che si può dire qualcosa come "stampare i caratteri in modo standard, ma i numeri con questo dato formato".

Grazie!

Modifica

Come da commento Rob Adams', ho cambiato il mio codice brutto (che, come sottolineato da altri, sarebbe rovinare la precisione per il 'prossimo ragazzo') per una più sintetica (e corretto):

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

Non proprio un duplicato di http://stackoverflow.com/questions/405039/permanent-stdsetw –

risposta

10

Solo std::setw() è temporaneo. Le altre due chiamate, e setprecision hanno un effetto permanente.

Quindi, si potrebbe modificare il codice per:

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

Ma ora hai borked le bandiere e di precisione per il ragazzo successivo. Prova a modificare:

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

Infine, se dovete assolutamente sbarazzarsi della duplicazione della costante 23, si potrebbe fare qualcosa di simile (ma io non lo consiglio):

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

Vedere anche this other question, dove hanno deciso che non è possibile rendere permanente la larghezza.

+0

Mille grazie - non solo risolvere il mio problema, ma anche ta mi ha dato qualcosa riguardo al concetto generale di flusso. Grazie! – Escualo

1

Tutto tranne il setw() effettivamente lo fa già. Sono "appiccicosi".

Il vostro vero problema è ciò che accade alla prossima uscita dopo questo ...

1

In generale, non si utilizza direttamente i manipolatori standard. In questo caso, per esempio, si potrebbe definire un manipolatore fromVector, e l'uso che:

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

questo modo, se si desidera modificare la larghezza e la precisione di elementi del vettore, si hanno solo a fallo in un posto.

In questo caso, in cui il manipolatore non ha argomenti, tutto quello che serve è una semplice funzione:

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

Naturalmente, questo avrà cambiato gran parte della formattazione per qualsiasi tardi utente. In generale, sarebbe meglio usare un oggetto di classe temporaneo che salva lo stato e lo ripristina nel distruttore. Io di solito traggo le mie manipolatori da qualcosa di simile:

intestazione: class StateSavingManip { pubblico: StateSavingManip ( StateSavingManip const & altro);

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

fonte: 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) ; 
} 

Se si fa questo, si dovrà aggiungere parentesi dopo il manipolatore, ad esempio:

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

(sono sicuro che qualche anima intelligente sarà capire un modo di evitare loro, ma non mi hanno mai infastidito, quindi non ho dato fastidio a me,