2013-02-27 12 views
17

Sto imparando C++. cout è un'istanza della classe std::ostream. Come posso stampare una stringa formattata con esso?Come usare C++ std :: ostream con la formattazione simile a printf?

Posso ancora utilizzare printf, ma voglio imparare più metodo di stile C++ che può prendere tutti i vantaggi di C++. Penso che questo dovrebbe essere possibile con std::ostream ma non riesco a trovare il modo corretto.

+1

questa pagina non è una guida completa per lo streaming formattazione [** Output Formatting **] (http://arachnoid.com/cpptutor/student3.html) –

+1

Non dovresti ne ho davvero bisogno perché puoi fare cose come 'cout <<" l'età di mio figlio è "<< 3 << endl;' invece di 'printf (" L'età di mia figlia è% u \ n ", 3);' – Daniel

+3

' cout << "l'età di mio figlio è" << 3 << endl; 'non è localizzabile; in lingua non inglese potresti avere un ordine di parole diverse. Quindi, questa sintassi non è accettabile per le applicazioni mutilingue. L'unico modo per forzare questa sintassi a funzionare è fare 'switch' che dipenderà dall'ID della lingua, e tale soluzione è brutta. 'printf' è molto meglio, perché il traduttore può tradurre l'intera stringa di formato e cambiare l'ordine delle parole senza la modifica del codice sorgente su ogni lingua non comune. – Vitaliy

risposta

22

L'unica cosa che si può fare con std::ostream è direttamente il noto << -syntax:

int i = 0; 
std::cout << "this is a number: " << i; 

e ci sono vari IO manipulators che può essere usato per influenzare la formattazione, il numero di cifre, ecc di numeri interi , numeri in virgola mobile ecc.

Tuttavia, questo non è lo stesso delle stringhe formattate di printf. C++ 11 non include alcuna funzione che consente di utilizzare la formattazione di stringhe nello stesso modo in cui viene utilizzata con printf (eccetto lo printf stesso, che è ovviamente possibile utilizzare in C++, se lo si desidera).

In termini di librerie che forniscono printf funzionalità in stile, c'è boost::format, che consente al codice come questo (copiato dalla sinossi):

std::cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50; 

noti inoltre che v'è una proposal for inclusion di printf in stile formattazione in una versione futura dello standard. Se questo viene accettato, la sintassi come il seguito potrebbe diventare disponibile:

std::cout << std::putf("this is a number: %d\n",i); 
+4

+1 per il metodo boost :: format – oDDsKooL

+0

E la generazione di stringhe? Né modo di creare oggetti stringa con la formattazione? – Eonil

+2

@Eonil Sì. Prima di tutto, puoi usare un oggetto ['std :: ostringstream'] (http://en.cppreference.com/w/cpp/io/basic_stringstream) allo stesso modo di' std :: cout' sopra. Quando lo hai riempito di contenuto (usando l'operatore '<<'), puoi usare la sua funzione' .str() 'per ottenere la stringa formattata. E 'boost :: format' restituisce comunque una stringa. Non ho incluso questo nella risposta perché la tua domanda riguarda specificamente 'std :: cout'. – jogojapan

0

Quando ho bisogno sia la sicurezza rispetto ai tipi di cout e la formattazione rapida e semplice di semplici variabili di printf(), ho mescolare i due in questo modo. Questa è una brutta soluzione, ma ottiene le cose fatte per me quando ho bisogno di cose di uscita come "2014/02/07 10:05" insieme ad alcune entità più complesse:

#include <stdio> 
#include <stdarg> 
#include <stdlib> 
#include <iostream> 

#pragma hdrstop 

using namespace std; 


char* print(char* fmt, ...) 
{ 
    static char buffer[80] = ""; 

    va_list argptr; 
    va_start(argptr,fmt); 

    vsprintf(buffer, fmt, argptr); 

    va_end(argptr); 

    return buffer; 
} 

#pragma argsused 
int main(int argc, char* argv[]) 
{ 

cout << print("\n%06d\n%6d\n%6d\n%010.3f",1,12,123,123.456); 

system("PAUSE>NUL"); 

return 0; 

} 
+0

Almeno usa 'vsnprintf' per evitare il tipo più ovvio di errore: sovraccarico del buffer. –

+0

Avendo statico locale si finisce per avere problemi di sicurezza del thread – UVV

-1

Larghezza campo

L'impostazione della larghezza del campo è molto semplice. Per ciascuna variabile, basta precederla con "setw (n)". Come questo:

#include <iostream> 
#include <iomanip> 

using namespace std; 

int main() 
{ 
    const int max = 12; 
    const int width = 6; 
    for(int row = 1;row <= max;row++) { 
     for(int col = 1;col <= max;col++) { 
      cout << setw(width) << row * col; 
     } 
     cout << endl; 
    } 
    return 0; 
} 

noti come "setw (n)" controlla l'ampiezza del campo, in modo che ogni numero è stampato all'interno di un campo che rimane la stessa larghezza, indipendentemente dalla larghezza del numero stesso.

- Da "Programming/C++ tutorial" di P. Lutus.

1

Per attuare printf si potrebbe usare C++ 11 parametri di modello:

#include <iostream> 
#include <string> 

inline std::ostream & mprintf(std::ostream & ostr, const char * fstr) throw() 
{ 
    return ostr << fstr; 
} 

template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr, 
     const char * fstr, const T & x) throw() 
{ 
    size_t i=0; 
    char c = fstr[0]; 

    while (c != '%') 
    { 
     if(c == 0) return ostr; // string is finished 
     ostr << c; 
     c = fstr[++i]; 
    }; 
    c = fstr[++i]; 
    ostr << x; 

    if(c==0) return ostr; // 

    // print the rest of the stirng 
    ostr << &fstr[++i]; 
    return ostr; 
} 


template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr, 
     const char * fstr, const T & x, Args... args) throw() 
{ 
    size_t i=0; 
    char c = fstr[0]; 

    while (c != '%') 
    { 
     if(c == 0) return ostr; // string is finished 
     ostr << c; 
     c = fstr[++i]; 
    }; 
    c = fstr[++i]; 
    ostr << x; 

    if(c==0) return ostr; // string is finished 

    return mprintf(ostr, &fstr[++i], args...); 
} 

int main() 
{ 
    int c = 50*6; 
    double a = 34./67.; 
    std::string q = "Hello!"; 

    // put only two arguments 
    // the symbol after % does not matter at all 
    mprintf(std::cout, "%f + %f = %a \n", c, a); 

    // print string object: for real printf one should write q.c_str() 
    mprintf(std::cout, "message: \"%s\". \n", q); 

    // the last argument will be ignored 
    mprintf(std::cout, "%z + %f\n", (long)a, 12, 544); 

} 

uscita

300 + 2 = %a 
message: "Hello!". 
2 + 12 

Questo un codice molto semplice e può essere migliorata.

1) Il vantaggio è che usa < < per stampare gli oggetti al flusso, in modo da poter mettere argomenti arbitrari che possono essere emesse tramite < <.

2) Ignora il tipo dell'argomento nella stringa formattata: dopo% può sopportare un simbolo arbitrario anche uno spazio. Il flusso di output decide come stampare l'oggetto corrispondente. Compatibile anche con printf.

3) Uno svantaggio è che non è possibile stampare il simbolo di percentuale '%', è necessario migliorare leggermente il codice.

4) Non può stampare i numeri formattati, come% 4.5f

5) Se il numero di argomenti è meno di quanto previsto dalla stringa formattata, la funzione basta stampare il resto della stringa.

6) Se il numero di argomenti è maggiore di quanto previsto da stringa formattata, quindi gli argomenti rimasti vengono ignorati

Si può migliorare il codice per fare 2) -6) per imitare completamente il comportamento printf. Tuttavia, se si seguono le regole di printf, solo 3) e 4) devono essere risolti essenzialmente.

0

Esempio di output:

2017-12-20T16:24:47,604144+01:00 Hello, World! 

codice (con l'uso put_printf dimostrata in put_timestamp):

#include <assert.h> 
#include <chrono> 
#include <iomanip> 
#include <iostream> 

class put_printf { 
    static constexpr size_t failed = std::numeric_limits<size_t>::max(); // for any explicit error handling 
    size_t stream_size; // excluding '\0'; on error set to 0 or to "failed" 
    char buf_stack[2048+1]; // MAY be any size that fits on the stack (even 0), SHOULD be (just) large enough for most uses (including '\0') 
    std::unique_ptr<char[]> buf_heap; // only used if the output doesn't fit in buf_stack 
public: 
    explicit put_printf(const char *format, ...) 
      #if __GNUC__ 
      __attribute__ ((format (printf, 2, 3))) // most compelling reason for not using a variadic template; parameter 1 is implied "this" 
      #endif 
      { 
     va_list args; 
     va_start(args, format); 
     const int res = vsnprintf(buf_stack, sizeof(buf_stack), format, args); 
     va_end(args); 
     if (res < 0) { // easily provoked, e.g., with "%02147483646i\n", i.e., more than INT_MAX-1 significant characters (only observed, no guarantee seen) 
      stream_size = failed; 
     } else if (res < sizeof(buf_stack)) { // preferred path 
      stream_size = res; 
     } else { // not artificially constrained 
      try { 
       const size_t buf_size = static_cast<size_t>(res) + 1; // avoids relying on "res < INT_MAX" (only observed, no guarantee seen) 
       buf_heap.reset(new char[buf_size]); // observed to work even beyond INT_MAX=2^32-1 bytes 
       va_start(args, format); 
       if (vsnprintf(buf_heap.get(), buf_size, format, args) == res) stream_size = res; 
       else stream_size = failed; // can't happen 
       va_end(args); 
      } catch (const std::bad_alloc&) { // insufficient free heap space (or an environment-specific constraint?) 
       stream_size = failed; 
      } 
     } 
    } 
    friend std::ostream& operator<<(std::ostream& os, const put_printf& self) { 
     if (self.stream_size == failed) { 
      // (placeholder for any explicit error handling) 
      return os; 
     } else { 
      // using write() rather than operator<<() to avoid a separate scan for '\0' or unintentional truncation at any internal '\0' character 
      return os.write((self.buf_heap ? self.buf_heap.get() : self.buf_stack), self.stream_size); 
     } 
    } 
}; 

class put_timestamp { 
    const bool basic = false; 
    const bool local = true; 
public: 
    friend std::ostream& operator<<(std::ostream& os, const put_timestamp& self) { 
     const auto now = std::chrono::system_clock::now(); 
     const std::time_t now_time_t = std::chrono::system_clock::to_time_t(now); 
     struct tm tm; if ((self.local ? localtime_r(&now_time_t, &tm) : gmtime_r(&now_time_t, &tm)) == nullptr) return os; // TODO: explicit error handling? 
     static_assert(4 <= sizeof(int), ""); 
     const int microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch() % std::chrono::seconds(1)).count(); 
     assert(0 <= microseconds && microseconds < 1000000); // TODO: (how) do we know? 
     // TODO: doesn't "point" in "decimal_point()" imply "dot"/"full stop"/"period", unlike an obviously neutral term like "mark"/"separator"/"sign"? 
     const char decimal_sign = std::use_facet<std::numpunct<char>>(os.getloc()).decimal_point() == '.' ? '.' : ','; // full stop accepted, comma preferred 
     // TODO: all well and good for a locale-specific decimal sign, but couldn't the locale also upset microseconds formatting by grouping digits? 
     os << std::put_time(&tm, self.basic ? "%Y%m%dT%H%M%S" : "%FT%T") << put_printf("%c%06i", decimal_sign, microseconds); 
     if (! self.local) return os << "Z"; 
     const int tz_minutes = std::abs(static_cast<int>(tm.tm_gmtoff))/60; 
     return os << put_printf(self.basic ? "%c%02i%02i" : "%c%02i:%02i", 0 <= tm.tm_gmtoff ? '+' : '-', tz_minutes/60, tz_minutes % 60); 
    } 
}; 

int main() { 
    // testing decimal sign 
    ///std::cout.imbue(std::locale("en_GB")); 
    ///std::cout.imbue(std::locale("fr_FR")); 

    std::cout << put_timestamp() << " Hello, World!\n"; 
    #if 0 
    typedef put_printf pf; // just to demo local abbreviation 
    std::cout << "1: " << pf("%02147483646i\n" , 1 ) << std::endl; // res < 0 
    std::cout << "2: " << pf("%02147483643i%i\n", 1, 100) << std::endl; // res < 0 
    std::cout << "3: " << pf("%02147483643i%i\n", 1, 10) << std::endl; // works 
    std::cout << "4: " << pf("%02147483646i" , 1 ) << std::endl; // works 
    #endif 
    return 0; 
} 

Commenti su put_printf:

// Reasons for the name "put_printf" (and not "putf" after all): 
// - put_printf is self-documenting, while using the naming pattern also seen in std::put_time; 
// - it is not clear whether the proposed std::putf would support exactly the same format syntax; 
// - it has a niche purpose, so a longer name is not an objection, and for frequent local uses 
//  it is easy enough to declare an even shorter "typedef put_printf pf;" or so. 
// Evaluation of delegating to vsnprintf() with intermediate buffer: 
// (+) identical result without implementation and/or maintenance issues, 
// (?) succeeds or fails as a whole, no output of successful prefix before point of failure 
// (-) (total output size limited to INT_MAX-1) 
// (-) overhead (TODO: optimal buf_stack size considering cache and VM page locality?) 
// Error handling (an STL design problem?): 
// - std::cout.setstate(std::ios_base::failbit) discards further std::cout output (stdout still works), 
//  so, to be aware of an error in business logic yet keep on trucking in diagnostics, 
//  should there be separate classes, or a possibility to plug in an error handler, or what? 
// - should the basic or default error handling print a diagnostic message? throw an exception? 
// TODO: could a function "int ostream_printf(std::ostream& os, const char *format, ...)" 
//   first try to write directly into os.rdbuf() before using buf_stack and buf_heap, 
//   and would that significantly improve performance or not? 
Problemi correlati