Equivalente C ++ di StringBuffer / StringBuilder?


184

C'è una classe C ++ Standard Template libreria che fornisce funzionalità di concatenazione di stringhe efficiente, simile a C # 's StringBuilder o di Java StringBuffer ?


3
la risposta breve è: Sì, STL ha una classe per quello ed è std::ostringstream.
CoffeDeveloper

Ehi, @andrew. Potete per favore cambiare la risposta accettata? C'è una chiara risposta vincente e non è la risposta attualmente accettata.
null

Risposte:


53

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::stringo std::stringstreamse dopo aver profilato il tuo codice scopri che questo migliora.

Normalmente uso std::stringo 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::stringo 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.


13
Reinventare la ruota. std :: stringstream è la risposta corretta. Vedi buone risposte di seguito.
Kobor42,

13
@ Kobor42 Sono d'accordo con te come faccio notare sulla prima e sull'ultima riga della mia risposta.
Iain

1
Non penso che la scratchstringa 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' stringimplementazione non sia veramente scadente (cioè, non usi la crescita esponenziale). Quindi "raggruppare" il appendnon 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.
BeeOnRope,

@BeeOnRope Sono d'accordo con te.
Iain

sono abbastanza sicuro che str.reserve(1024);sarebbe più veloce di questa cosa
hanshenrik,

160

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


59
Le stringhe C ++ sono modificabili : esattamente. L'intero motivo StringBuilderesiste è 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 ++.
bobobobo,

57
Le stringhe immutabili di @bobobobo hanno però altri vantaggi, i suoi cavalli per i corsi
jk.

8
Le concatenazioni di stringhe semplici non creano un nuovo oggetto, quindi lo stesso problema con l'immutabilità in Java? Considera tutte le variabili come stringhe nell'esempio seguente: a = b + c + d + e + f; Non chiamerà operatore + su bec, quindi operatore + sul risultato d, ecc.?
Serge Rogatch,

9
Aspetta un minuto, la classe di stringhe standard sa come mutare se stessa, ma ciò non significa che l'inefficienza non ci sia. Per quanto ne so std :: string non può semplicemente estendere la dimensione del suo carattere interno *. Ciò significa che la mutazione in un modo che richiede più personaggi richiede una riallocazione e una copia. Non è diverso da un vettore di caratteri ed è sicuramente meglio riservare lo spazio di cui hai bisogno in quel caso.
Trygve Skogsholm,

7
@ TrygveSkogsholm - non è diverso da un vettore di caratteri, ma ovviamente la "capacità" della stringa può essere maggiore della sua dimensione, quindi non tutti gli accodamenti necessitano di una riallocazione. In generale, le stringhe utilizzeranno una strategia di crescita esponenziale in modo da aggiungere ancora ammortizzare a un'operazione di costo lineare. È diverso dalle stringhe immutabili di Java in cui ogni operazione di aggiunta deve copiare tutti i caratteri di entrambe le stringhe in uno nuovo, quindi una serie di aggiunte finisce come O(n)in generale.
BeeOnRope,

93

La std::string.appendfunzione 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();

43

std::string è l'equivalente in C ++: è mutabile.


13

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 sprintfse vuoi rischiare di scrivere codice errato ;-)) in una matrice di caratteri e riconvertire in una stringa è l'unica opzione.


Non allo stesso modo di printf o .NET String.Format, vero?
Andy Shellam,

1
è un po 'disonesto dire che sono l'unico modo però
jk.

2
@jk: sono l'unico modo per confrontare la capacità di formattazione di StringBuilder di .NET, che è esattamente la domanda originale. Ho detto "Credo", quindi potrei sbagliarmi, ma puoi mostrarmi un modo per ottenere la funzionalità di StringBuilder in C ++ senza usare printf?
Andy Shellam,

aggiornata la mia risposta per includere alcune opzioni di formattazione alternative
jk.

6

Dato che std::stringin C ++ è mutabile, puoi usarlo. Ha una += operatore una appendfunzione.

Se è necessario aggiungere dati numerici, utilizzare le std::to_stringfunzioni.

Se si desidera una flessibilità ancora maggiore sotto forma di poter serializzare qualsiasi oggetto su una stringa, utilizzare la std::stringstreamclasse. Ma dovrai implementare le tue funzioni di operatore di streaming affinché funzioni con le tue classi personalizzate.


4

std :: string's + = non funziona con const char * (quali elementi come "string to add" sembrano essere), quindi sicuramente usare stringstream è il più vicino a ciò che è richiesto - basta usare << invece di +


3

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)


Questo è pulito. Non vedo perché std::stringstreamnon si comporti in questo modo.
einpoklum,

1

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.

0

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>):

  • Intestazione 1 byte (2 bit per indicare se i seguenti dati sono: stringa spostata, stringa o byte [])
  • 6 bit per indicare la lunghezza del byte []

per byte []

  • Memorizzo direttamente byte di stringhe brevi (per l'accesso sequenziale alla memoria)

per stringhe spostate (stringhe aggiunte con std::move)

  • Il puntatore a un std::stringoggetto (abbiamo la proprietà)
  • imposta un flag nella classe se ci sono byte riservati inutilizzati lì

per archi

  • Il puntatore a un std::stringoggetto (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::ostringstreamma ha alcuni aspetti negativi:

  • Ho assunto tipi di caratteri a lunghezza fissa (quindi 1,2 o 4 byte, non va bene per UTF8), non sto dicendo che non funzionerà per UTF8, ma non l'ho verificato per pigrizia.
  • Ho usato una cattiva pratica di codifica (buffer opaco, facile da rendere non portatile, credo che il mio sia portatile a proposito)
  • Manca tutte le funzionalità di ostringstream
  • Se una stringa di riferimento viene eliminata prima di unire tutte le stringhe: comportamento indefinito.

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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.