it-swarm.com.de

std :: string formatiert wie sprintf

Ich muss std::string mit sprintf formatieren und in den Dateistream senden. Wie kann ich das machen?

365
Max Frai

Sie können dies nicht direkt tun, da Sie keinen Schreibzugriff auf den zugrunde liegenden Puffer haben (bis C++ 11; siehe Dietrich Epps comment ). Sie müssen es zuerst in einem c-String tun und dann in einen std :: string kopieren:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Aber ich bin nicht sicher, warum Sie nicht einfach einen String-Stream verwenden würden. Ich gehe davon aus, dass Sie bestimmte Gründe haben, dies nicht nur zu tun:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
273
Doug T.

C++ 11-Lösung, die intern vsnprintf() verwendet:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

Ein sicherer und effizienter (ich habe es getestet, und es ist schneller):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

Der fmt_str wird als Wert übergeben, um den Anforderungen von va_start zu entsprechen.

HINWEIS: Die "sicherere" und "schnellere" Version funktioniert auf einigen Systemen nicht. Daher sind beide noch aufgeführt. "Schneller" hängt auch davon ab, ob der Vorbelegungsschritt korrekt ist, andernfalls wird die Variable strcpy langsamer.

235
Erik Aronesty

Mit C++ 11std::snprintf wird dies eine ziemlich einfache und sichere Aufgabe. Ich sehe viele Antworten auf diese Frage, die offenbar vor der Zeit von C++ 11 geschrieben wurden und feste Pufferlängen und -variablen verwenden. Ich würde dies aus Gründen der Sicherheit, der Effizienz und der Klarheit nicht empfehlen.

#include <memory>
#include <iostream>
#include <string>
#include <cstdio>

using namespace std; //Don't if you're in a header-file

template<typename ... Args>
string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    unique_ptr<char[]> buf( new char[ size ] ); 
    snprintf( buf.get(), size, format.c_str(), args ... );
    return string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

Das obige Code-Snippet ist lizenziert unter CC0 1.0 .

Zeile für Zeile Erklärung:

Ziel: Mit char* in einen std::snprintf schreiben und dann in einen std::string konvertieren.

Zuerst bestimmen wir die gewünschte Länge des Char-Arrays.

Von cppreference.com :

Rückgabewert

[...] Wenn die resultierende Zeichenfolge aufgrund der Begrenzung von buf_size abgeschnitten wird, Die Funktion gibt die Gesamtzahl der Zeichen (ohne das abschließende Null-Byte) zurück, die geschrieben worden wären, wenn das Limit .__ war. nicht auferlegt.

Das heißt, die gewünschte Größe ist die Anzahl der Zeichen plus eins, so dass der Nullterminator hinter allen anderen Zeichen steht und er vom String-Konstruktor wieder abgeschnitten werden kann. Dieses Problem wurde in den Kommentaren von @ alexk7 erläutert.

Dann weisen wir ein neues Zeichenfeld zu und weisen es einem std::unique_ptr zu. Dies wird im Allgemeinen empfohlen, da Sie delete nicht erneut manuell eingeben müssen.

Beachten Sie, dass dies kein sicherer Weg ist, einen unique_ptr mit benutzerdefinierten Typen zuzuordnen, da Sie den Speicher nicht freigeben können, wenn der Konstruktor eine Ausnahme auslöst!

Danach können wir natürlich snprintf für den vorgesehenen Zweck verwenden und die formatierte Zeichenfolge in den char[] schreiben und danach einen neuen std::string erstellen und daraus zurückgeben.


Sie können ein Beispiel in Aktion hier sehen.


Wenn Sie auch std::string in der Argumentliste verwenden möchten, werfen Sie einen Blick auf this Gist


Zusätzliche Informationen für Visual Studio Benutzer:

Wie in diese Antwort erläutert, hat Microsoft std::snprintf in _snprintf umbenannt (ja, ohne std::). MS hat es weiterhin als veraltet festgelegt und empfiehlt stattdessen die Verwendung von _snprintf_s , jedoch akzeptiert _snprintf_s den Puffer nicht als null oder kleiner als die formatierte Ausgabe und berechnet in diesem Fall nicht die Länge der Ausgaben. Um die Abwertungswarnungen während der Kompilierung zu entfernen, können Sie die folgende Zeile am Anfang der Datei einfügen, die die Verwendung von _snprintf enthält:

#pragma warning(disable : 4996)
185
iFreilicht

boost::format() bietet die gewünschte Funktionalität:

Wie aus der Boost-Format-Bibliotheken-Übersicht:

Ein Formatobjekt wird aus einer Formatzeichenfolge erstellt und erhält dann durch wiederholte Aufrufe des Operators% ..__ Argumente. Jedes dieser Argumente wird dann in Zeichenfolgen konvertiert, die wiederum gemäß der Formatzeichenfolge zu einer Zeichenfolge zusammengefasst werden.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"
100
kennytm

Leider verwenden die meisten Antworten hier Varargs, die von Natur aus unsicher sind, es sei denn, Sie verwenden etwas wie das format-Attribut von GCC, das nur mit Literal-Formatstrings funktioniert. Sie können am folgenden Beispiel sehen, warum diese Funktionen unsicher sind:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

dabei ist string_format eine Implementierung aus der Antwort von Erik Aronesty. Dieser Code wird zwar kompiliert, stürzt jedoch höchstwahrscheinlich ab, wenn Sie versuchen, ihn auszuführen:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Es ist möglich, eine sichere printf zu implementieren und sie mithilfe von (variadischen) Vorlagen auf das Format std::string zu erweitern. Dies wurde in der {fmt} -Bibliothek gemacht, die eine sichere Alternative für sprintf darstellt und std::string zurückgibt:

std::string format_str = "The answer is %d";
std::string result = fmt::sprintf(format_str, 42);

{fmt} verfolgt die Argumenttypen und wenn der Typ nicht mit der Formatspezifikation übereinstimmt, liegt kein Segmentierungsfehler, nur eine Ausnahmebedingung oder ein Fehler bei der Kompilierung vor, wenn constexpr-Formatzeichenfolgenprüfung verwendet wird.

Disclaimer: Ich bin der Autor von {fmt}.

30
vitaut

Wenn Sie nur eine printf-artige Syntax haben möchten (ohne selbst printf aufzurufen), schauen Sie sich die Datei Boost-Format an.

18
Timo Geusch

[edit '17/8/31] Hinzufügen einer variadisch vorgelagerten Version 'vtspf (..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

dies ist effektiv eine durch Kommas getrennte Version (anstelle von) der manchmal behindernden <<- Operatoren, die folgendermaßen verwendet werden:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[edit] Angepasst, um die Technik in der Antwort von Erik Aronesty (oben) zu verwenden:

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[vorherige Antwort]
Eine sehr späte Antwort, aber für diejenigen, die wie ich den Sprintf-Weg mögen: Ich habe geschrieben und verwende die folgenden Funktionen. Wenn Sie es mögen, können Sie die% -Optionen erweitern, um sie besser an die Sprintf-Optionen anzupassen. die dort vorhandenen sind derzeit für meine Bedürfnisse ausreichend ..__ Sie verwenden stringf () und stringfappend () genauso wie Sie sprintf würden. Denken Sie daran, dass die Parameter für ... POD-Typen sein müssen.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}
15
slashmais

Ich habe mein eigenes mit vsnprintf geschrieben, so dass es string zurückgibt, anstatt einen eigenen Puffer erstellen zu müssen.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

So kannst du es gerne verwenden

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
15

Um std::string 'sprintf' zu formatieren, rufen Sie snprintf (Argumente nullptr und 0) auf, um die benötigte Pufferlänge zu erhalten. Schreiben Sie Ihre Funktion mit einer variablen C++ 11-Vorlage wie folgt:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return std::move(str);
}

Kompilieren Sie mit C++ 11-Unterstützung, zum Beispiel in GCC: g++ -std=c++11

Verwendungszweck:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
13
user2622016

Meine zwei Cents zu dieser sehr beliebten Frage.

Um die manpage von printf-ähnlichen Funktionen zu zitieren :

Bei erfolgreicher Rückkehr geben diese Funktionen die Anzahl der gedruckten Zeichen zurück (mit Ausnahme des Null-Bytes, das zum Beenden der Ausgabe in Zeichenfolgen verwendet wird).

Die Funktionen snprintf () und vsnprintf () schreiben nicht mehr als Größenbytes (einschließlich des abschließenden Nullbytes ('\ 0')). Wenn die Ausgabe aufgrund dieses Grenzwerts abgeschnitten wurde, ist der Rückgabewert die Anzahl der Zeichen (mit Ausnahme des abschließenden Nullbytes), die in den letzten String geschrieben worden wären, wenn genügend Speicherplatz zur Verfügung stand. Ein Rückgabewert von size oder mehr bedeutet also, dass die Ausgabe abgeschnitten wurde.

Mit anderen Worten, eine sinnvolle C++ 11-Implementierung sollte Folgendes sein:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}

Das funktioniert ganz gut :)

Variadic-Vorlagen werden nur in C++ 11 unterstützt. Die Antwort von pixelpoint zeigt eine ähnliche Technik mit älteren Programmierstilen.

Es ist komisch, dass C++ so etwas nicht aus der Box hat. Sie haben kürzlich to_string() hinzugefügt, was meiner Meinung nach ein großer Schritt nach vorne ist. Ich frage mich, ob sie dem .format schließlich einen std::string-Operator hinzufügen werden ...

Bearbeiten

Wie alexk7 darauf hinweist, wird für den Rückgabewert von +1 A std::snprintf benötigt, da für das \0-Byte Platz benötigt wird. Intuitiv wird bei den meisten Architekturen, denen +1 fehlt, die required-Ganzzahl teilweise mit einem 0 überschrieben. Dies geschieht nach der Auswertung von required als Aktualparameter für std::snprintf, daher sollte der Effekt nicht sichtbar sein.

Dieses Problem kann sich jedoch beispielsweise bei der Compiler-Optimierung ändern: Was ist, wenn sich der Compiler für die Variable required für ein Register entscheidet? Dies sind Fehler, die manchmal zu Sicherheitsproblemen führen.

10
Dacav

So macht es Google: StringPrintf (BSD-Lizenz)
und Facebook machen es auf eine ganz ähnliche Art und Weise: StringPrintf (Apache License)
Beide bieten auch eine praktische StringAppendF.

10
PW.
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

Verwendung von C99 snprintf und C++ 11 

7
emerge

Basierend auf der Antwort von Erik Aronesty:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}

Dies vermeidet die Notwendigkeit, const vom Ergebnis von .c_str(), das in der ursprünglichen Antwort enthalten war, wegzuwerfen. 

6
ChetS

Getestet, Antwort auf Produktionsqualität

Diese Antwort behandelt den allgemeinen Fall mit standardkonformen Techniken. Der gleiche Ansatz wird als Beispiel auf CppReference.com am unteren Rand der Seite angegeben. Im Gegensatz zu ihrem Beispiel entspricht dieser Code den Anforderungen der Frage und wird in Robotik- und Satellitenanwendungen im Feld getestet. Es hat auch das Kommentieren verbessert. Die Designqualität wird weiter unten erläutert.

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

Vorhersagbarer linearer Wirkungsgrad

Zwei Durchgänge sind für eine sichere, zuverlässige und vorhersagbare wiederverwendbare Funktion gemäß den Fragenspezifikationen erforderlich. Vermutungen über die Verteilung der Größen von Variablen in einer wiederverwendbaren Funktion sind ein schlechter Programmierstil und sollten vermieden werden. In diesem Fall sind willkürlich große Variablendarstellungen von Varigs ein Schlüsselfaktor bei der Wahl des Algorithmus.

Das Wiederholen eines Überlaufs ist exponentiell ineffizient. Dies ist ein weiterer Grund, der diskutiert wurde, als der C++ 11-Normenausschuss den obigen Vorschlag erörterte, um einen Testlauf bereitzustellen, wenn der Schreibpuffer null ist.

Bei der obigen produktionsbereiten Implementierung ist der erste Durchlauf ein solcher Trockenlauf, um die Zuweisungsgröße zu bestimmen. Es findet keine Zuordnung statt. Das Parsen von printf-Direktiven und das Lesen von vargs wurde über Jahrzehnte hinweg extrem effizient gemacht. Wiederverwendbarer Code sollte vorhersehbar sein, auch wenn eine kleine Ineffizienz für triviale Fälle geopfert werden muss.

Sicherheit und Zuverlässigkeit

Andrew Koenig sagte zu einer kleinen Gruppe von uns nach seinem Vortrag auf einer Veranstaltung in Cambridge: "Benutzerfunktionen sollten sich nicht auf die Ausnutzung eines Ausfalls für unübliche Funktionalität verlassen." Wie üblich ist seine Weisheit seitdem in den Akten wahr. Behobene und geschlossene Sicherheitsprobleme weisen häufig auf Wiederholungshacks in der Beschreibung der vor dem Update ausgenutzten Lücke hin.

Dies ist im Vorschlag für die Überarbeitung der formellen Standards für das Nullpuffermerkmal in Alternative zu sprintf, C9X-Revisionsvorschlag, ISO IEC Dokument WG14 N645/X3J11 96-008 erwähnt. Eine beliebig lange Zeichenfolge, die pro Druckanweisung "% s" innerhalb der Einschränkungen der Verfügbarkeit des dynamischen Speichers eingefügt wird, stellt keine Ausnahme dar und sollte nicht zur Erzeugung "ungewöhnlicher Funktionen" verwendet werden.

Sehen Sie sich den Vorschlag neben dem Beispielcode an, der unten auf der C++ Reference.org-Seite angegeben ist, auf die im ersten Absatz dieser Antwort verwiesen wird.

Auch das Testen von Fehlerfällen ist selten so robust wie Erfolgsfälle.

Portabilität

Alle großen O.S. Hersteller bieten Compiler an, die std :: vsnprintf vollständig als Teil der C++ 11-Standards unterstützen. Hosts, die Produkte von Anbietern ausführen, die keine Distributionen mehr pflegen, sollten aus vielen Gründen mit g ++ oder clang ++ ausgestattet sein.

Stack-Verwendung

Die Stapelverwendung im ersten Aufruf von std :: vsnprintf ist kleiner oder gleich der des zweiten Aufrufs und wird vor Beginn des zweiten Aufrufs freigegeben. Wenn der erste Aufruf die Stack-Verfügbarkeit überschreitet, schlägt auch std :: fprintf fehl.

5
Douglas Daseeco

C++ 20 std::format

Es ist angekommen! Die Funktion ist beschrieben unter: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html und verwendet ein Python-ähnliches .format() Syntax.

Ich erwarte, dass die Verwendung wie folgt sein wird:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

Ich werde es versuchen, wenn GCC unterstützt wird. GCC 9.1.0 mit g++-9 -std=c++2a unterstützt es immer noch nicht.

Die API fügt einen neuen std::format -Header hinzu:

Die vorgeschlagene Formatierungs-API ist im neuen Header <format> definiert und sollte keinen Einfluss auf den vorhandenen Code haben.

Die vorhandene Bibliothek fmt gibt an, sie zu implementieren, wenn Sie die Polyfüllung benötigen: https://github.com/fmtlib/fmt

Implementierung von C++ 20 std::format.

und wurde zuvor erwähnt bei: std :: string formatierung wie sprintf

inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}
5
pixelpoint

Wenn Sie sich in einem System befinden, das asprintf (3) hat, können Sie es problemlos einpacken:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}
4
Thomas Perl

Sie könnten dies versuchen:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );
4
EddieV223

string hat nicht das, was Sie brauchen, aber std :: stringstream. Verwenden Sie einen Stringstream, um die Zeichenfolge zu erstellen, und extrahieren Sie die Zeichenfolge. Hier ist eine umfassende Liste der Dinge, die Sie tun können. Zum Beispiel:

cout.setprecision(10); //stringstream is a stream like cout

erhalten Sie eine Genauigkeit von 10 Dezimalstellen, wenn Sie einen Doppel- oder Float-Druck ausführen.

3
Hassan Syed

Nachfolgend die leicht modifizierte Version von @iFreilicht, aktualisiert nach C++ 14 (Verwendung der make_unique-Funktion anstelle der Rohdeklaration) und Unterstützung für std::string-Argumente (basierend auf Kenny Kerr article )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

Ausgabe:

i = 3, f = 5.000000, s = hello world

Fühlen Sie sich frei, diese Antwort mit der Originalantwort zu mischen, falls Sie dies wünschen.

3

Nahm die Idee aus Dacav und pixelpoint's answer . Ich habe ein bisschen rumgespielt und habe folgendes bekommen:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

Mit sane Programmierpraxis glaube ich, dass der Code ausreichen sollte, jedoch bin ich immer noch offen für sicherere Alternativen, die noch einfach genug sind und C++ 11 nicht erfordern.


Und hier ist eine weitere Version, die einen Anfangspuffer verwendet, um einen zweiten Aufruf von vsnprintf() zu verhindern, wenn der Anfangspuffer bereits ausreicht.

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(Es stellt sich heraus, dass diese Version der Antwort von Piti Ongmongkolkul nur - ähnlich ist , nur dass sie new und delete[] nicht verwendet und beim Erstellen von std::string auch eine Größe angibt. 

Die Idee, new und delete[] nicht zu verwenden, besteht darin, die Verwendung des Stacks über den Heap zu implizieren, da keine Zuordnungs- und Deallocation-Funktionen aufgerufen werden müssen. Wenn sie jedoch nicht ordnungsgemäß verwendet werden, kann es bei einigen (möglicherweise alten) Überläufen gefährlich werden oder vielleicht auch nur verwundbare Systeme. Wenn dies ein Problem ist, empfehle ich dringend, stattdessen new und delete[] zu verwenden. Beachten Sie, dass es hier nur um die Zuordnungen geht, da vsnprintf() bereits mit Grenzwerten aufgerufen wird. Wenn Sie also einen Grenzwert angeben, der auf der für den zweiten Puffer zugewiesenen Größe basiert, werden diese ebenfalls verhindert.)

3
konsolebox

Dies ist der Code, den ich dazu in meinem Programm benutze ... Es ist nichts Besonderes, aber es macht den Trick ... Beachten Sie, dass Sie Ihre Größe entsprechend anpassen müssen. MAX_BUFFER ist für mich 1024.

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}
3
Dave

Ich verwende das normalerweise:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

Nachteil: Nicht alle Systeme unterstützen Vasprint

3

Sehr einfache Lösung.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
2
Pasha
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
1
user5685202

Sie können die C++ - Ausgabe in cout mit der iomanip-Headerdatei ..__ formatieren. Stellen Sie sicher, dass Sie die iomanip-Headerdatei einschließen, bevor Sie Hilfsfunktionen wie

Hier ist ein Codeausschnitt, den ich in der Vergangenheit verwendet habe, um die durchschnittliche Wartezeit in dem Vektor zu drucken, den ich "angesammelt" habe.

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

Hier ist eine kurze Beschreibung, wie wir C++ - Streams formatieren können . http://www.cprogramming.com/tutorial/iomanip.html

1
vinkris

UPDATE 1 : Es wurden fmt::format - Tests hinzugefügt

Ich habe mich selbst mit Methoden befasst, die hier vorgestellt wurden, und dabei diametral entgegengesetzte Ergebnisse erzielt als hier erwähnt.

Ich habe 4 Funktionen über 4 Methoden verwendet:

  • variadic function + vsnprintf + std::unique_ptr
  • variadic function + vsnprintf + std::string
  • variadic template function + std::ostringstream + std::Tuple + utility::for_each
  • Funktion fmt::format Aus der Bibliothek fmt

Für das Test-Backend hat das googletest verwendet.

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::Tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

Die Implementierung von for_each Stammt von hier: über Tuple iterieren

#include <type_traits>
#include <Tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::Tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::Tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

Die Tests:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_Tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_Tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

Der UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unsued.hpp :

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

unused.cpp :

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

[~ # ~] ergibt [~ # ~] :

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_Tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_Tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_Tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_Tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

Wie Sie sehen können, ist die Implementierung durch vsnprintf + std::string Gleich fmt::format, Jedoch schneller als durch vsnprintf + std::unique_ptr ist schneller als durch die std::ostringstream.

Die Tests wurden in Visual Studio 2015 Update 3 Kompiliert und laufen um Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

1
Andry

Es kann Probleme geben, wenn der Puffer nicht groß genug ist, um die Zeichenfolge zu drucken. Sie müssen die Länge des formatierten Strings bestimmen, bevor Sie eine formatierte Nachricht dort drucken. Ich erstelle einen eigenen Helfer (getestet unter Windows und Linux GCC ), und Sie können es verwenden.

String.cpp: http://Pastebin.com/DnfvzyKP
String.h: http://Pastebin.com/7U6iCUMa

String.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#Elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

Mir ist klar, dass dies viele Male beantwortet wurde, aber dies ist kürzer:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

beispiel:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

Siehe auch http://rextester.com/NJB14150

1
Patrick Beard

dies kann ausprobiert werden. einfach. verwendet allerdings keine Nuancen der String-Klasse.

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <string>
#include <exception>
using namespace std;

//---------------------------------------------------------------------

class StringFormatter
{
public:
    static string format(const char *format, ...);
};

string StringFormatter::format(const char *format, ...)
{
    va_list  argptr;

    va_start(argptr, format);

        char   *ptr;
        size_t  size;
        FILE   *fp_mem = open_memstream(&ptr, &size);
        assert(fp_mem);

        vfprintf (fp_mem, format, argptr);
        fclose (fp_mem);

    va_end(argptr);

    string ret = ptr;
    free(ptr);

    return ret;
}

//---------------------------------------------------------------------

int main(void)
{
    string temp = StringFormatter::format("my age is %d", 100);
    printf("%s\n", temp.c_str());

    return 0;
}
1
ksridhar

Alle Antworten hier scheinen eines oder mehrere dieser Probleme zu haben: (1) es funktioniert möglicherweise nicht in VC++ (2), es erfordert zusätzliche Abhängigkeiten wie boost oder fmt (3), seine zu komplizierte benutzerdefinierte Implementierung und wahrscheinlich nicht gut getestet.

Der folgende Code behebt alle oben genannten Probleme.

#include <string>
#include <cstdarg>
#include <memory>

std::string stringf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    #ifndef _MSC_VER

        //GCC generates warning for valid use of snprintf to get
        //size of result string. We suppress warning with below macro.
        #ifdef __GNUC__
        #pragma GCC diagnostic Push
        #pragma GCC diagnostic ignored "-Wformat-nonliteral"
        #endif

        size_t size = std::snprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'

        #ifdef __GNUC__
        # pragma GCC diagnostic pop
        #endif

        std::unique_ptr<char[]> buf(new char[ size ] ); 
        std::vsnprintf(buf.get(), size, format, args);
        return std::string(buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
    #else
        int size = _vscprintf(format, args);
        std::string result(++size, 0);
        vsnprintf_s((char*)result.data(), size, _TRUNCATE, format, args);
        return result;
    #endif
    va_end(args);
}    

int main() {
    float f = 3.f;
    int i = 5;
    std::string s = "hello!";
    auto rs = stringf("i=%d, f=%f, s=%s", i, f, s.c_str());
    printf("%s", rs.c_str());
    return 0;
}

Anmerkungen:

  1. Ein separater VC++ - Codezweig ist erforderlich, da VC++ beschlossen hat, snprintf abzulehnen, wodurch Compiler-Warnungen für andere hoch gewählte Antworten generiert werden. Da ich immer im "Warnungen als Fehler" -Modus laufe, ist das für mich kein Problem.
  2. Die Funktion akzeptiert char * anstelle von std::string. Dies ist darauf zurückzuführen, dass diese Funktion meistens mit der literalen Zeichenfolge aufgerufen wird, die in der Tat char * und nicht std::string lautet. Falls Sie std::string als Formatparameter haben, rufen Sie einfach .c_str() auf.
  3. Der Name der Funktion lautet "stringf" und nicht "string_format", um mit printf, scanf usw. Schritt zu halten.
  4. Es wird nicht auf Sicherheitsprobleme eingegangen (d. H. Schlechte Parameter können möglicherweise einen Seg-Fehler anstelle einer Ausnahme verursachen). Wenn Sie dies benötigen, sind Sie mit boost oder fmt librarys besser aufgehoben. Meine Präferenz hier wäre fmt, weil es nur eine Kopf- und Quelldatei ist, die im Projekt abgelegt wird und weniger seltsame Formatierungssyntax als Boost hat. Beide sind jedoch nicht mit Printf-Formatzeichenfolgen kompatibel, daher ist das Folgende in diesem Fall immer noch nützlich. 
  5. Der stringf-Code durchläuft die GCC-Compilation im strikten Modus . Dies erfordert zusätzliche #pragma-Makros, um Fehlalarme in GCC-Warnungen zu unterdrücken.

Der obige Code wurde getestet,

1
Shital Shah

Eine Lösung, die ich favorisiert habe, ist, dies mit sprintf direkt in den std :: string-Puffer zu tun, nachdem der Puffer groß genug gemacht wurde:

#include <string>
#include <iostream>

using namespace std;

string l_output;
l_output.resize(100);

for (int i = 0; i < 1000; ++i)
{       
    memset (&l_output[0], 0, 100);
    sprintf (&l_output[0], "\r%i\0", i);

    cout << l_output;
    cout.flush();
}

Also, std :: string erstellen, die Größe ändern, direkt auf den Puffer zugreifen ...

1
Xelous

Ich habe es mit regulären Ausdrücken versucht. Ich habe es als Beispiel für ints und const implementiert, aber Sie können andere Typen hinzufügen ( POD types, aber mit Zeigern können Sie alles drucken).

#include <assert.h>
#include <cstdarg>

#include <string>
#include <sstream>
#include <regex>

static std::string
formatArg(std::string argDescr, va_list args) {
    std::stringstream ss;
    if (argDescr == "i") {
        int val = va_arg(args, int);
        ss << val;
        return ss.str();
    }
    if (argDescr == "s") {
        const char *val = va_arg(args, const char*);
        ss << val;
        return ss.str();
    }
    assert(0); //Not implemented
}

std::string format(std::string fmt, ...) {
    std::string result(fmt);
    va_list args;
    va_start(args, fmt);
    std::regex e("\\{([^\\{\\}]+)\\}");
    std::smatch m;
    while (std::regex_search(fmt, m, e)) {
        std::string formattedArg = formatArg(m[1].str(), args);
        fmt.replace(m.position(), m.length(), formattedArg);
    }
    va_end(args);
    return fmt;
}

Hier ist ein Beispiel für die Verwendung:

std::string formatted = format("I am {s} and I have {i} cats", "bob", 3);
std::cout << formatted << std::endl;

Ausgabe:

Ich bin Bob und ich habe 3 Katzen

1
ElefEnt

Poco Foundation library verfügt über eine sehr praktische Formatfunktion, die std :: string sowohl im Formatstring als auch in den Werten unterstützt:

1
riot_starter

Aktualisierung einiger Antworten, Unterschied ist, dass die Funktion std :: string für% s korrekt akzeptiert

namespace format_helper
{

    template <class Src>
    inline Src cast(Src v)
    {
        return v;
    }

    inline const char *cast(const std::string& v)
    {
        return v.c_str();
    }
};

template <typename... Ts>
inline std::string stringfmt (const std::string &fmt, Ts&&... vs)
{
    using namespace format_helper;
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), cast(std::forward<Ts>(vs))...);//not counting the terminating null character.
    std::string result;
    //because we use string as container, it adds extra 0 automatically
    result.resize(required , 0);
    //and snprintf will use n-1 bytes supplied
    std::snprintf(const_cast<char*>(result.data()), required + 1, fmt.c_str(), cast(std::forward<Ts>(vs))...);

    return result;
}

Live: http://cpp.sh/5ajsv

1
Alex Zaharov

Für Visual C :

std::wstring stringFormat(const wchar_t* fmt, ...)
{
    if (!fmt) {
        return L"";
    }

    std::vector<wchar_t> buff;
    size_t size = wcslen(fmt) * 2;
    buff.resize(size);
    va_list ap;
    va_start(ap, fmt);
    while (true) {
        int ret = _vsnwprintf_s(buff.data(), size, _TRUNCATE, fmt, ap);
        if (ret != -1)
            break;
        else {
            size *= 2;
            buff.resize(size);
        }
    }
    va_end(ap);
    return std::wstring(buff.data());
}
0
Jichao

Ich werde jetzt eine Version für Visual Studio schreiben, hoffentlich wird es irgendwann jemand portabel machen. (Verdächtige müssen _vsnwprintf durch vsnwprintf und so ähnlich ersetzen.)

Sie müssen veraltete Warnungen deaktivieren, indem Sie _CRT_SECURE_NO_WARNINGS aus der Projektkonfiguration verwenden.

Ich benutze _vsnwprintf mit dem ersten Parameter als nullptr, um die Puffergröße schätzen zu können, den Wstring-Puffer zu reservieren und die Zeichenfolge direkt in den Puffer zu formatieren.

Sie sind nicht sicher, warum Sie die veraltete Warnung deaktivieren müssen, da sichere Versionen desselben Methodenaufrufs (_vsnwprintf_s) nullptr nicht als Eingabe verwenden können. Verdächtige müssen dem Microsoft C++ - Team gemeldet werden.

Dies ist nur eine Unicode-Version. Wenn Sie 'w' entfernen und wchar_t durch char ersetzen, können Sie es wahrscheinlich auch mit ascii funktionieren lassen.

//
//  Formats wstring according to format.
//
wstring wformat(const wchar_t* format, ...)
{
    va_list args;
    va_start(args, format);
    int size = _vsnwprintf(nullptr, 0, format, args);
    size++; // Zero termination
    wstring ws;
    ws.resize(size);
    _vsnwprintf(&ws[0], size, format, args);
    va_end(args);
    return ws;
}
0
TarmoPikaro

Dies ist eine Windows-spezifische Lösung, mit der Compiler-Warnungen in Visual Studio vermieden werden, ohne sie zum Schweigen zu bringen. Die fraglichen Warnungen beziehen sich auf die Verwendung einer std :: string mit va_start, die irrtümlicherweise eine Warnung erzeugt, und auf veraltete printf-Varianten. 

template<typename ... va>
std::string Format( const std::string& format, va ... args )
{
    std::string s;
    s.resize( _scprintf( format.c_str(), args ... ) + 1 );
    s.resize( _snprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
    return s;
}

template<typename ... va>
std::wstring Format( const std::wstring& format, va ... args )
{
    std::wstring s;
    s.resize( _scwprintf( format.c_str(), args ... ) + 1 );
    s.resize( _snwprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
    return s;
}

std::string s = Format( "%hs %d", "abc", 123 );
std::wstring ws = Format( L"%hs %d", "abc", 123 );
0
Lose

Hier meine (einfache Lösung):

std::string Format(const char* lpszFormat, ...)
{
    // Warning : "vsnprintf" crashes with an access violation
    // exception if lpszFormat is not a "const char*" (for example, const string&)

    size_t  nSize     = 1024;
    char    *lpBuffer = (char*)malloc(nSize);

    va_list lpParams;

    while (true)
    {
        va_start(lpParams, lpszFormat);

        int nResult = vsnprintf(
            lpBuffer,
            nSize,
            lpszFormat,
            lpParams
        );

        va_end(lpParams);

        if ((nResult >= 0) && (nResult < (int)nSize) )
        {
            // Success

            lpBuffer[nResult] = '\0';
            std::string sResult(lpBuffer);

            free (lpBuffer);

            return sResult;
        }
        else
        {
            // Increase buffer

            nSize =
                  (nResult < 0)
                ? nSize *= 2
                : (nResult + 1)
            ;

            lpBuffer = (char *)realloc(lpBuffer, nSize);
        }
    }
}
0