it-swarm.com.de

Wie verwende ich C++ std :: ostream mit einer printf-ähnlichen Formatierung?

Ich lerne C++. cout ist eine Instanz der std::ostream-Klasse. Wie kann ich formatierte Zeichenfolge damit drucken?

Ich kann immer noch printf verwenden, aber ich möchte mehr über C++ - Methoden lernen, die alle C++ - Vorteile nutzen können. Ich denke, das sollte mit std::ostream möglich sein, aber ich finde keinen richtigen Weg.

27
Eonil

Das einzige, was Sie mit std::ostream direkt machen können, ist die bekannte <<- Syntax:

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

Und es gibt verschiedene Manipulatoren IO , mit denen die Formatierung, die Anzahl der Stellen usw. von Ganzzahlen, Fließkommazahlen usw. beeinflusst werden kann.

Dies ist jedoch nicht das Gleiche wie die formatierten Zeichenfolgen von printf. C++ 11 enthält keine Funktion, mit der Sie die String-Formatierung auf dieselbe Weise verwenden können, wie sie mit printf verwendet wird (mit Ausnahme von printf selbst, das Sie natürlich in C++ verwenden können, wenn Sie möchten.

In Bezug auf Bibliotheken, die printf-style Funktionalität bieten, gibt es boost::format , der Code wie diesen (aus der Übersicht kopiert) ermöglicht:

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

Beachten Sie auch, dass es in einer zukünftigen Version des Standards einen Vorschlag für die Aufnahme von printf- Formatierung gibt. Wenn dies akzeptiert wird, kann folgende Syntax verfügbar sein:

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

Um printf zu implementieren, könnte man C++ 11-Vorlagenparameter verwenden:

#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 );

}

Ausgabe

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

Dies ist ein sehr einfacher Code und kann verbessert werden.

1) Der Vorteil ist, dass << zum Drucken von Objekten in den Stream verwendet wird, sodass Sie beliebige Argumente einfügen können, die über << ausgegeben werden können.

2) Es ignoriert den Typ des Arguments in der formatierten Zeichenfolge: Nach% kann ein beliebiges Symbol stehen, sogar ein Leerzeichen. Der Ausgabestrom entscheidet, wie das entsprechende Objekt gedruckt wird. Es ist auch mit printf kompatibel.

3) Ein Nachteil besteht darin, dass das Prozentzeichen '%' nicht gedruckt werden kann. Der Code muss geringfügig verbessert werden.

4) Formatierte Zahlen wie% 4.5f können nicht gedruckt werden

5) Wenn die Anzahl der Argumente geringer ist als durch eine formatierte Zeichenfolge vorhergesagt, druckt die Funktion nur den Rest der Zeichenfolge.

6) Wenn die Anzahl der Argumente größer ist als durch eine formatierte Zeichenfolge vorhergesagt, werden die verbleibenden Argumente ignoriert

Man kann den Code verbessern, um 2) -6) zu machen, um das printf-Verhalten vollständig nachzuahmen. Wenn Sie jedoch die Regeln von printf befolgen, müssen im Wesentlichen nur 3) und 4) behoben werden.

3
user3754430

Feldbreite

Das Einstellen der Feldbreite ist sehr einfach. Stellen Sie für jede Variable einfach "setw (n)" voran. So was:

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

Beachten Sie, wie "setw (n)" die Feldbreite steuert, so dass jede Zahl .__ ist. wird innerhalb eines Feldes gedruckt, das unabhängig von der .__-Breite gleich bleibt. Breite der Zahl selbst.

- Von "Programmierung/C++ - Tutorial" von P. Lutus .

1
Zephyr

Ich empfehle, Ostringstream anstelle von Ostream zu verwenden. Siehe folgendes Beispiel:

#include <vector>
#include <string>
#include <iostream>
#include "CppUnitTest.h"

#define _CRT_NO_VA_START_VALIDATION

std::string format(const std::string& format, ...)
{
    va_list args;
    va_start(args, format);
    size_t len = std::vsnprintf(NULL, 0, format.c_str(), args);
    va_end(args);
    std::vector<char> vec(len + 1);
    va_start(args, format);
    std::vsnprintf(&vec[0], len + 1, format.c_str(), args);
    va_end(args);
    return &vec[0];
}

verwendungsbeispiel: 

std::ostringstream ss;
ss << format("%s => %d", "Version", Version) << std::endl;
Logger::WriteMessage(ss.str().c_str()); // write to unit test output
std::cout << ss.str() << std::endl; // write to standard output
1
serup

Beispielausgabe:

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

Code (mit Verwendung von put_printf 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;
}

Kommentare zu 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?
1
RFST

Ich schrieb unabhängig, kam aber mit einer Antwort ähnlich zu user3283405 

Meine Lösung verwendet vasprintf (), um die Formatierung zu erreichen, und verwendet den Operator, der << of std :: ostream überlädt, um den Speicher an der richtigen Stelle freizugeben. 

Verwendungszweck: 

std::cout << putf(const char *format, ...); //Same format as C printf(3)

Code:

#define _GNU_SOURCE
#include <cstdarg>
#include <iostream>
#include <cstdio>

struct putf_r{
        char *s;
};

putf_r putf(const char *fmt, ...){
        va_list ap;
        va_start(ap, fmt);
        putf_r a;
#pragma GCC diagnostic Push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
        vasprintf(&a.s, fmt, ap);
#pragma GCC diagnostic pop
        va_end(ap);
        return a;
}

std::ostream& operator<<(std::ostream& os, putf_r a){
        os<<a.s;
        free(a.s);
        return os;
}

int main(){
        std::cout << putf("%3d\n", 23) << putf("%a\n", 256.);
}

Beachten Sie, dass der Compiler das Format in putf () nicht prüft. Das Compiler-Flag -Wformat-nonliteral warnt Sie jedoch nicht vor verdächtigem Code in putf (), und Sie müssen das unkontrollierte Formatzeichenfolge problem nicht selbst beachten.
Detaillierte Informationen finden Sie auf GitHub

0
WingTillDie

Wenn ich sowohl die Typsicherheit von cout als auch die schnelle und einfache Formatierung einfacher Variablen von printf () brauche, mische ich die beiden so. Dies ist eine hässliche Lösung, aber es gibt Dinge für mich erledigt, wenn ich Dinge wie "02/07/2014 10:05 am" zusammen mit einigen komplexeren Entitäten ausgeben muss:

#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
user3283405