C'è una classe C ++ Standard Template libreria che fornisce funzionalità di concatenazione di stringhe efficiente, simile a C # 's StringBuilder o di Java StringBuffer ?
C'è una classe C ++ Standard Template libreria che fornisce funzionalità di concatenazione di stringhe efficiente, simile a C # 's StringBuilder o di Java StringBuffer ?
Risposte:
NOTA questa risposta ha ricevuto qualche attenzione recentemente. Non sto sostenendo questo come una soluzione (è una soluzione che ho visto in passato, prima della STL). È un approccio interessante e dovrebbe essere applicato solo std::string
o std::stringstream
se dopo aver profilato il tuo codice scopri che questo migliora.
Normalmente uso std::string
o std::stringstream
. Non ho mai avuto problemi con questi. Normalmente riserverei prima un po 'di spazio se conosco in anticipo la dimensione approssimativa della stringa.
Ho visto altre persone creare il proprio costruttore di stringhe ottimizzato in un lontano passato.
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;
}
};
Utilizza due stringhe una per la maggior parte della stringa e l'altra come area scratch per concatenare stringhe brevi. Ottimizza gli appendi raggruppando le operazioni di aggiunta breve in una piccola stringa, quindi aggiungendole alla stringa principale, riducendo così il numero di riallocazioni richieste sulla stringa principale man mano che aumenta.
Non ho richiesto questo trucco con std::string
o std::stringstream
. Penso che sia stato usato con una libreria di stringhe di terze parti prima di std :: string, è stato tanto tempo fa. Se adotti una strategia come questo profilo, prima l'applicazione.
scratch
stringa realizzi davvero nulla qui. Il numero di riallocazioni della stringa principale sarà in gran parte una funzione della sua dimensione finale, non il numero di operazioni di accodamento, a meno che l' string
implementazione non sia veramente scadente (cioè, non usi la crescita esponenziale). Quindi "raggruppare" il append
non aiuta perché una volta che il sottostante string
è grande crescerà solo occasionalmente in entrambi i modi. Inoltre, aggiunge una serie di operazioni di copia ridondanti e può comportare più riallocazioni (quindi chiamate a new
/ delete
) poiché si sta aggiungendo una stringa breve.
str.reserve(1024);
sarebbe più veloce di questa cosa
Il modo C ++ sarebbe usare std :: stringstream o semplicemente concatenazioni di stringhe semplici. Le stringhe C ++ sono modificabili, quindi le considerazioni sulle prestazioni della concatenazione sono meno preoccupanti.
per quanto riguarda la formattazione, puoi fare la stessa formattazione su uno stream, ma in modo diverso, simile acout
. oppure puoi usare un functor fortemente tipizzato che incapsula questo e fornisce un'interfaccia simile a String.Format, ad es. boost :: format
StringBuilder
esiste è quello di coprire l'inefficienza del tipo String di base immutabile di Java . In altre parole StringBuilder
è patchwork, quindi dovremmo essere contenti di non aver bisogno di una tale classe in C ++.
O(n)
in generale.
La std::string.append
funzione non è una buona opzione perché non accetta molte forme di dati. Un'alternativa più utile è usare std::stringstream
; così:
#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();
Puoi usare .append () per semplicemente concatenare stringhe.
std::string s = "string1";
s.append("string2");
Penso che potresti anche essere in grado di fare:
std::string s = "string1";
s += "string2";
Per quanto riguarda le operazioni di formattazione di C # StringBuilder
, credo snprintf
(o sprintf
se vuoi rischiare di scrivere codice errato ;-)) in una matrice di caratteri e riconvertire in una stringa è l'unica opzione.
Dato che std::string
in C ++ è mutabile, puoi usarlo. Ha una += operator
e una append
funzione.
Se è necessario aggiungere dati numerici, utilizzare le std::to_string
funzioni.
Se si desidera una flessibilità ancora maggiore sotto forma di poter serializzare qualsiasi oggetto su una stringa, utilizzare la std::stringstream
classe. Ma dovrai implementare le tue funzioni di operatore di streaming affinché funzioni con le tue classi personalizzate.
Un conveniente generatore di stringhe per c ++
Come molte persone hanno risposto prima, std :: stringstream è il metodo di scelta. Funziona bene e ha molte opzioni di conversione e formattazione. IMO ha un difetto piuttosto scomodo: non puoi usarlo come una fodera o come espressione. Devi sempre scrivere:
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
che è piuttosto fastidioso, specialmente quando si desidera inizializzare le stringhe nel costruttore.
Il motivo è che a) std :: stringstream non ha un operatore di conversione in std :: stringa eb) l'operatore << () di Stringstream non restituisce un riferimento di stringa, ma invece un riferimento std :: ostream - che non può essere ulteriormente calcolato come flusso di stringhe.
La soluzione è quella di sovrascrivere std :: stringstream e dargli un migliore abbinamento degli operatori:
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;
}
Con questo, puoi scrivere cose come
std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )
anche nel costruttore.
Devo confessare che non ho misurato le prestazioni, dal momento che non l'ho ancora usato in un ambiente che fa ancora un uso pesante della costruzione di stringhe, ma suppongo che non sarà molto peggio di std :: stringstream, poiché tutto è fatto tramite riferimenti (tranne la conversione in stringa, ma questa è un'operazione di copia anche in std :: stringstream)
std::stringstream
non si comporti in questo modo.
Il contenitore Rope può valere la pena se è necessario inserire / eliminare la stringa nel luogo casuale della stringa di destinazione o per sequenze di caratteri lunghi. Ecco un esempio dell'implementazione di 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.
Volevo aggiungere qualcosa di nuovo a causa di quanto segue:
Ad un primo tentativo non sono riuscito a battere
std::ostringstream
'S operator<<
efficienza, ma con più tentativi sono stato in grado di creare uno StringBuilder che è più veloce in alcuni casi.
Ogni volta che aggiungo una stringa, memorizzo solo un riferimento ad essa da qualche parte e aumento il contatore della dimensione totale.
Il vero modo in cui l'ho finalmente implementato (Horror!) È usare un buffer opaco (std :: vector <char>):
per byte []
per stringhe spostate (stringhe aggiunte con std::move
)
std::string
oggetto (abbiamo la proprietà)per archi
std::string
oggetto (nessuna proprietà)C'è anche una piccola ottimizzazione, se l'ultima stringa inserita è stata spostata, controlla i byte riservati ma non utilizzati gratuitamente e memorizza ulteriori byte lì invece di usare il buffer opaco (questo è per risparmiare un po 'di memoria, in realtà lo rende leggermente più lento , forse dipende anche dalla CPU, ed è raro vedere comunque stringhe con spazio extra riservato)
Questo è stato finalmente leggermente più veloce di std::ostringstream
ma ha alcuni aspetti negativi:
ostringstream
conclusione? uso
std::ostringstream
Risolve già il più grande collo di bottiglia, mentre guadagnare alcuni punti% in velocità con l'implementazione della miniera non vale gli aspetti negativi.
std::ostringstream
.