Gibt es eine C ++ Standard Template Library - Klasse , die eine effiziente String - Verkettung Funktionalität, ähnlich wie C # 's bietet String oder Java String ?
Gibt es eine C ++ Standard Template Library - Klasse , die eine effiziente String - Verkettung Funktionalität, ähnlich wie C # 's bietet String oder Java String ?
Antworten:
HINWEIS Diese Antwort hat in letzter Zeit einige Aufmerksamkeit erhalten. Ich befürworte dies nicht als Lösung (es ist eine Lösung, die ich in der Vergangenheit vor der STL gesehen habe). Es ist ein interessanter Ansatz , und nur dann angewandt werden , über soll std::string
oder std::stringstream
wenn nach dem Code Profilieren Sie entdecken , dies macht eine Verbesserung.
Normalerweise benutze ich entweder std::string
oder std::stringstream
. Ich hatte noch nie Probleme damit. Normalerweise würde ich zuerst etwas Platz reservieren, wenn ich die grobe Größe der Saite im Voraus kenne.
Ich habe in der fernen Vergangenheit gesehen, wie andere Leute ihren eigenen optimierten String-Builder hergestellt haben.
class StringBuilder {
private:
std::string main;
std::string scratch;
const std::string::size_type ScratchSize = 1024; // or some other arbitrary number
public:
StringBuilder & append(const std::string & str) {
scratch.append(str);
if (scratch.size() > ScratchSize) {
main.append(scratch);
scratch.resize(0);
}
return *this;
}
const std::string & str() {
if (scratch.size() > 0) {
main.append(scratch);
scratch.resize(0);
}
return main;
}
};
Es werden zwei Zeichenfolgen verwendet, eine für den Großteil der Zeichenfolge und die andere als Arbeitsbereich zum Verketten kurzer Zeichenfolgen. Die Anhänge werden optimiert, indem die kurzen Anhängevorgänge in einer kleinen Zeichenfolge zusammengefasst und dann an die Hauptzeichenfolge angehängt werden, wodurch die Anzahl der erforderlichen Neuzuweisungen für die Hauptzeichenfolge verringert wird, wenn diese größer wird.
Ich habe diesen Trick mit std::string
oder nicht benötigt std::stringstream
. Ich denke, es wurde mit einer String-Bibliothek eines Drittanbieters vor std :: string verwendet, es ist so lange her. Wenn Sie eine Strategie wie dieses Profil anwenden, verwenden Sie zuerst Ihre Bewerbung.
scratch
Saite hier wirklich etwas bewirkt. Die Anzahl der Neuzuweisungen des Hauptstrings hängt weitgehend von seiner endgültigen Größe ab, nicht von der Anzahl der Anhängeoperationen, es sei denn, die string
Implementierung ist wirklich schlecht (dh es wird kein exponentielles Wachstum verwendet). Das "Stapeln" append
hilft also nicht, denn sobald der Basiswert string
groß ist, wächst er nur gelegentlich in beide Richtungen. Darüber hinaus werden eine Reihe redundanter Kopiervorgänge hinzugefügt und möglicherweise mehr Neuzuweisungen (daher Aufrufe von new
/ delete
), da Sie an eine kurze Zeichenfolge anhängen.
str.reserve(1024);
dass es schneller sein würde als dieses Ding
Der C ++ - Weg wäre, std :: stringstream oder einfach nur String-Verkettungen zu verwenden. C ++ - Zeichenfolgen sind veränderbar, sodass die Leistungsaspekte der Verkettung weniger wichtig sind.
In Bezug auf die Formatierung können Sie alle gleichen Formatierungen in einem Stream vornehmen, jedoch auf andere Weise, ähnlich wie beicout
. oder Sie können einen stark typisierten Funktor verwenden, der dies kapselt und eine String.Format-ähnliche Schnittstelle bereitstellt, z. B. boost :: format
StringBuilder
besteht darin, die Ineffizienz des unveränderlichen grundlegenden String-Typs von Java abzudecken . Mit anderen Worten, es StringBuilder
handelt sich um Patchwork, daher sollten wir froh sein, dass wir in C ++ keine solche Klasse benötigen.
O(n)
im Allgemeinen endet .
Die std::string.append
Funktion ist keine gute Option, da sie nicht viele Arten von Daten akzeptiert. Eine nützlichere Alternative ist die Verwendung std::stringstream
; wie so:
#include <sstream>
// ...
std::stringstream ss;
//put arbitrary formatted data into the stream
ss << 4.5 << ", " << 4 << " whatever";
//convert the stream buffer into a string
std::string str = ss.str();
Sie können .append () verwenden, um Zeichenfolgen einfach zu verketten.
std::string s = "string1";
s.append("string2");
Ich denke, Sie könnten sogar in der Lage sein:
std::string s = "string1";
s += "string2";
Was die Formatierungsvorgänge von C # betrifft StringBuilder
, glaube ich snprintf
(oder sprintf
wenn Sie das Risiko eingehen möchten, fehlerhaften Code zu schreiben ;-)), dass die Konvertierung in ein Zeichenarray und die Rückkonvertierung in eine Zeichenfolge die einzige Option ist.
Da std::string
in C ++ veränderbar ist, können Sie das verwenden. Es hat eine += operator
und eine append
Funktion.
Wenn Sie numerische Daten anhängen müssen, verwenden Sie die std::to_string
Funktionen.
Wenn Sie noch mehr Flexibilität in Form der Serialisierung eines Objekts in eine Zeichenfolge wünschen, verwenden Sie die std::stringstream
Klasse. Sie müssen jedoch Ihre eigenen Streaming-Operator-Funktionen implementieren, damit sie mit Ihren eigenen benutzerdefinierten Klassen funktionieren.
Ein praktischer String Builder für c ++
Wie viele Leute zuvor geantwortet haben, ist std :: stringstream die Methode der Wahl. Es funktioniert gut und hat viele Konvertierungs- und Formatierungsoptionen. IMO hat es jedoch einen ziemlich unangenehmen Fehler: Sie können es nicht als Einzeiler oder als Ausdruck verwenden. Sie müssen immer schreiben:
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
Das ist ziemlich ärgerlich, besonders wenn Sie Strings im Konstruktor initialisieren möchten.
Der Grund ist, dass a) std :: stringstream keinen Konvertierungsoperator in std :: string hat und b) die Operatoren << () des Stringstreams keine Stringstream-Referenz zurückgeben, sondern stattdessen eine std :: ostream-Referenz - die nicht weiter als String-Stream berechnet werden kann.
Die Lösung besteht darin, std :: stringstream zu überschreiben und ihm bessere Übereinstimmungsoperatoren zu geben:
namespace NsStringBuilder {
template<typename T> class basic_stringstream : public std::basic_stringstream<T>
{
public:
basic_stringstream() {}
operator const std::basic_string<T> () const { return std::basic_stringstream<T>::str(); }
basic_stringstream<T>& operator<< (bool _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (char _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (signed char _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned char _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (short _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned short _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (int _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned int _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (long long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned long long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (float _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (double _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (long double _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (void* _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::streambuf* _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::ostream& (*_val)(std::ostream&)) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::ios& (*_val)(std::ios&)) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::ios_base& (*_val)(std::ios_base&)){ std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (const T* _val) { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val)); }
basic_stringstream<T>& operator<< (const std::basic_string<T>& _val) { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val.c_str())); }
};
typedef basic_stringstream<char> stringstream;
typedef basic_stringstream<wchar_t> wstringstream;
}
Damit können Sie Dinge wie schreiben
std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )
sogar im Konstruktor.
Ich muss gestehen, dass ich die Leistung nicht gemessen habe, da ich sie noch nicht in einer Umgebung verwendet habe, in der der Stringaufbau noch stark genutzt wird, aber ich gehe davon aus, dass sie nicht viel schlimmer sein wird als std :: stringstream, da alles erledigt ist über Referenzen (außer der Konvertierung in einen String, aber das ist auch eine Kopieroperation in std :: stringstream)
std::stringstream
so verhalte.
Der Seilcontainer kann sich lohnen, wenn eine Zeichenfolge in die zufällige Stelle der Zielzeichenfolge oder für lange Zeichenfolgen eingefügt / gelöscht werden muss. Hier ist ein Beispiel aus der Implementierung von SGI:
crope r(1000000, 'x'); // crope is rope<char>. wrope is rope<wchar_t>
// Builds a rope containing a million 'x's.
// Takes much less than a MB, since the
// different pieces are shared.
crope r2 = r + "abc" + r; // concatenation; takes on the order of 100s
// of machine instructions; fast
crope r3 = r2.substr(1000000, 3); // yields "abc"; fast.
crope r4 = r2.substr(1000000, 1000000); // also fast.
reverse(r2.mutable_begin(), r2.mutable_end());
// correct, but slow; may take a
// minute or more.
Ich wollte aus folgenden Gründen etwas Neues hinzufügen:
Bei einem ersten Versuch konnte ich nicht schlagen
std::ostringstream
's operator<<
Effizienz, aber mit mehr Versuchen konnte ich einen StringBuilder erstellen, der in einigen Fällen schneller ist.
Jedes Mal, wenn ich eine Zeichenfolge anhänge, speichere ich irgendwo einen Verweis darauf und erhöhe den Zähler der Gesamtgröße.
Die wirkliche Art, wie ich es endlich implementiert habe (Horror!), Ist die Verwendung eines undurchsichtigen Puffers (std :: vector <char>):
für Byte []
für verschobene Zeichenfolgen (Zeichenfolgen mit angehängt std::move
)
std::string
Objekt (wir haben Eigentum)für Saiten
std::string
Objekt (kein Besitz)Es gibt auch eine kleine Optimierung: Wenn die zuletzt eingefügte Zeichenfolge verschoben wurde, sucht sie nach frei reservierten, aber nicht verwendeten Bytes und speichert dort weitere Bytes, anstatt den undurchsichtigen Puffer zu verwenden (um Speicherplatz zu sparen, wird sie tatsächlich etwas langsamer , hängt vielleicht auch von der CPU ab, und es kommt sowieso selten vor, dass Zeichenfolgen mit zusätzlichem reserviertem Speicherplatz angezeigt werden.)
Dies war schließlich etwas schneller als std::ostringstream
, hat aber einige Nachteile:
ostringstream
Fazit? verwenden
std::ostringstream
Es behebt bereits den größten Engpass, während es sich nicht lohnt, mit der Minenimplementierung einige Prozentpunkte Geschwindigkeit zu erreichen.
std::ostringstream
.