2016-04-28 14 views
10

Ich verwende sprintf Funktion in C++ 11, auf folgende Weise:Mit sprintf mit std :: string in C++

std::string toString() 
{ 
    std::string output; 
    uint32_t strSize=512; 
    do 
    { 
     output.reserve(strSize); 
     int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u", 
      INDEX_RECORD_TYPE_SERIALIZATION_HEADER, 
      FORAMT_VERSION, 
      contentType, 
      contentFormatVersion, 
      magic, 
      segmentId); 

     strSize *= 2; 
    } while (ret < 0); 

    return output; 
} 

Gibt es einen besseren Weg, dies zu tun, als jedes Mal zu überprüfen, ob die reservierten Platz war genug? Für die zukünftige Möglichkeit, weitere Dinge hinzuzufügen.

+1

Verwenden Sie 'snprintf'? Weil 'sprintf', wie in Ihrem Code gezeigt, keine Möglichkeit hat, die Puffergröße zu bestimmen. 'snprintf' würde auch die erforderliche Puffergröße zurückgeben, so dass Sie einfach den zurückgegebenen Wert +1 als neue' strSize' verwenden könnten. –

+1

Dieser Code ist sehr falsch. 'reserve' ändert die Größe der Zeichenkette nicht, und' sprintf' gibt keine negative zurück, nur weil Sie außerhalb der Grenzen geschrieben haben. Sie müssen den Platz, den Sie benötigen * vor dem Schreiben außerhalb der Grenzen zuweisen. –

+0

Verwandte Frage: http://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf –

Antwort

13

Ihr Konstrukt - Schreiben in den Puffer von c_str() empfing - ist undefiniertes Verhalten, auch wenn Sie die Zeichenfolge Kapazität vorher geprüft. (Der Rückgabewert ist ein Zeiger auf const char, und die Funktion markiert selbst const, aus einem Grund.)

Sie C und C++ nicht mischen, besonders nicht in interne Objektdarstellung zu schreiben. (Das ist sehr grundlegende OOP brechen.) Verwenden Sie C++, für die Typensicherheit und nicht in Conversion Specifier/Parameter Mismatches, wenn für nichts anderes.

std::ostringstream s; 
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER 
    << " Version=" << FORMAT_VERSION 
    // ...and so on... 
    ; 
std::string output = s.str(); 

Alternative:

std::string output = "Type=" + std::to_string(INDEX_RECORD_TYPE_SERIALIZATION_HEADER) 
        + " Version=" + std::to_string(FORMAT_VERSION) 
        // ...and so on... 
        ; 
+0

Wie kann ich die String-Formatierung in ostringstream simulieren, wie magic =% 04x und so weiter? –

+1

@danieltheman: ['setw'] (http://en.cppreference.com/w/cpp/io/manip/setw), [' setfill'] (http://en.cppreference.com/w/cpp/io/manip/setfill). – DevSolar

+0

Ihr Kommentar * 'Der Rückgabewert ist ein Zeiger auf ** const ** char, und die Funktion selbst markiert ** const **, aus einem Grund' * impliziert, dass der vorgestellte Code * aufgrund von 'const nicht einmal kompilieren sollte 'Qualifikationskonflikt bei' sprintf (output.c_str(), ... 'Ausdruck. Also ist es falsch OP * verwendet *' sprintf' die Art, wie er beschrieben hat, weil ein vorgestellter Auszug kein lauffähiger Code ist ... – CiaPan

1

Ihr Code ist falsch. reserve reserviert Speicher für die Zeichenfolge, ändert jedoch nicht seine Größe. Das Schreiben in den Puffer, der von c_str zurückgegeben wird, ändert seine Größe ebenfalls nicht. Also glaubt die Zeichenkette immer noch, dass ihre Größe 0 ist, und Sie haben gerade etwas in den ungenutzten Platz im Puffer der Zeichenkette geschrieben. (Wahrscheinlich. Technisch hat der Code Undefined Behavior, weil das Schreiben in c_str undefiniert ist, also könnte alles passieren).

Was Sie wirklich tun möchte, ist vergessen sprintf und ähnliche C-Stil-Funktionen, und verwenden Sie die C++ Art und Weise der String-Formatierung — String-Streams:

std::ostringstream ss; 
ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER 
    << " Version=" << FORAMT_VERSION 
    << /* ... the rest ... */; 
return ss.str(); 
0

Ja, es ist!

In C ist die bessere Möglichkeit, eine Datei mit dem null Gerät zuzuordnen und eine Dummy-printf der gewünschten Ausgabe machen, zu erfahren, wie viel Platz wäre es tatsächlich gedruckt nehmen, wenn. Ordnen Sie dann den entsprechenden Puffer und sprintf die gleichen Daten zu.

In C++ können Sie den Ausgabestream auch mit einem Null-Gerät verknüpfen und die Anzahl der mit std::ostream::tellp gedruckten Zeichen testen. Allerdings ist die Verwendung von ostringstream eine bessere Lösung - siehe die Antworten von DevSolar oder Angew.

+0

Vollständig fehlt der Punkt, dass Sie nicht in den Puffer schreiben können, der von 'c_str()' zurückgegeben wird, und unnötigerweise sogar kompliziert, da es 'snprintf()'/'snprintf_s' gibt. – DevSolar

+0

'snprintf' ist besser als der Null-Device-Hack. Aber Sie müssen diesen Hack für 'swprintf' verwenden. –

6

Die C++ Muster in anderen Antworten gezeigt sind schöner, aber der Vollständigkeit halber, hier ist eine richtige Art und Weise mit sprintf:

auto format = "your %x format %d string %s"; 
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/); 
std::string output(size + 1, '\0'); 
std::sprintf(&output[0], format, /* Arguments go here*/); 

Achten Sie auf

  • Sie Ihre Zeichenfolge resize müssen. reserve ändert nicht die Größe des Puffers. In meinem Beispiel konstruiere ich eine korrekt dimensionierte Zeichenfolge direkt.
  • c_str() gibt const char* zurück. Sie dürfen es nicht an sprintf übergeben.
  • std::string Puffer war nicht garantiert, um zusammenhängend vor C++ 11 und dies beruht auf dieser Garantie. Wenn Sie exotische C++ 11-konforme Plattformen unterstützen müssen, die die Seilimplementierung für std::string verwenden, ist es wahrscheinlich besser, zuerst sprint in std::vector<char> zu kopieren und dann den Vektor in die Zeichenfolge zu kopieren.
  • Dies funktioniert nur, wenn die Argumente nicht zwischen der Größenberechnung und Formatierung geändert werden; Verwenden Sie entweder lokale Kopien von Variablen oder Thread-Synchronisationsgrundelemente für Multithread-Code.
+0

Interessant; Ich bin mir nicht ganz sicher, ob das Konstrukt (Schreiben nach '& output [0]') dem Buchstaben des Standards folgt. Dies wird wahrscheinlich neben allen Implementierungen von 'std :: string 'funktionieren, aber hinsichtlich der Definiertheit habe ich Zweifel. – DevSolar

+0

@DevSolar Wenn ich mich richtig erinnere, ist die Kontinuität von 'std :: string' Puffer seit C++ 11 garantiert. Zuvor war es nicht garantiert.Soweit ich weiß, gibt es keine anderen Gründe dafür, dass dies nicht funktioniert. – user2079303

+0

@ user2079303 Zeichenkettenkonstruktor, der eine Zahl darin erhält? Ich denke nicht, dass es so etwas in C++ gibt, kannst du bitte etwas ausarbeiten? Ich spreche darüber: Std :: String-Ausgabe (Größe + 1); –

1

Wir können Code mischen von hier https://stackoverflow.com/a/36909699/2667451 und hier https://stackoverflow.com/a/7257307 und führt so sein:

template <typename ...Args> 
std::string stringWithFormat(const std::string& format, Args && ...args) 
{ 
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...); 
    std::string output(size + 1, '\0'); 
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...); 
    return output; 
}