Existe uma classe C ++ Standard Template Library que fornece funcionalidade eficiente de concatenação de cadeias, semelhante ao StringBuilder do C # ou ao StringBuffer do Java ?
Existe uma classe C ++ Standard Template Library que fornece funcionalidade eficiente de concatenação de cadeias, semelhante ao StringBuilder do C # ou ao StringBuffer do Java ?
Respostas:
NOTA esta resposta recebeu alguma atenção recentemente. Não estou defendendo isso como uma solução (é uma solução que eu já vi no passado, antes do STL). É uma abordagem interessante e só deve ser aplicada sobre std::string
ou std::stringstream
se, após criar um perfil do seu código, você descobrir que isso melhora.
Eu normalmente uso um std::string
ou std::stringstream
. Eu nunca tive problemas com isso. Normalmente, eu reservaria um espaço primeiro se soubesse o tamanho aproximado da corda com antecedência.
Vi outras pessoas criarem seu próprio construtor de strings otimizado no passado distante.
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;
}
};
Ele usa duas strings, uma para a maioria das strings e a outra como uma área de rascunho para concatenar strings curtas. Ele otimiza os anexos agrupando em lotes as operações de acréscimo curto em uma cadeia pequena e, em seguida, anexando-a à cadeia principal, reduzindo assim o número de realocações necessárias na cadeia principal à medida que ela aumenta.
Eu não exigi esse truque com std::string
ou std::stringstream
. Eu acho que foi usado com uma biblioteca de strings de terceiros antes de std :: string, foi há muito tempo. Se você adotar uma estratégia como esse perfil, seu aplicativo primeiro.
scratch
corda realmente realiza alguma coisa aqui. O número de realocações da cadeia principal será em grande parte uma função de seu tamanho final, não o número de operações de acréscimo, a menos que a string
implementação seja realmente ruim (ou seja, não use crescimento exponencial). Portanto, "agrupar" o append
pacote não ajuda, porque, uma vez que o subjacente string
é grande, ele só cresce ocasionalmente de qualquer maneira. Além disso, ele adiciona várias operações de cópia redundante e pode ter mais realocações (daí chamadas para new
/ delete
), pois você está anexando uma sequência curta.
str.reserve(1024);
seria mais rápido do que essa coisa
A maneira C ++ seria usar std :: stringstream ou concatenações de strings simples. As seqüências de caracteres C ++ são mutáveis; portanto, as considerações de desempenho da concatenação são menos preocupantes.
no que diz respeito à formatação, você pode fazer a mesma formatação em um fluxo, mas de uma maneira diferente, semelhante acout
. ou você pode usar um functor fortemente tipado que encapsula isso e fornece uma String.Format como interface, por exemplo, boost :: format
StringBuilder
existe é para cobrir a ineficiência do tipo String imutável básico do Java . Em outras palavras, StringBuilder
é uma colcha de retalhos; portanto, devemos estar felizes por não precisarmos dessa classe em C ++.
O(n)
em geral.
A std::string.append
função não é uma boa opção porque não aceita muitas formas de dados. Uma alternativa mais útil é usar std::stringstream
; igual a:
#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();
Você pode usar .append () para simplesmente concatenar seqüências de caracteres.
std::string s = "string1";
s.append("string2");
Eu acho que você pode até fazer:
std::string s = "string1";
s += "string2";
Quanto às operações de formatação dos C # StringBuilder
, acredito snprintf
(ou sprintf
se você quiser arriscar escrever código de buggy ;-)) em uma matriz de caracteres e converter novamente em uma string é a única opção.
Como std::string
no C ++ é mutável, você pode usá-lo. Tem += operator
uma append
função e.
Se você precisar acrescentar dados numéricos, use as std::to_string
funções.
Se você deseja ainda mais flexibilidade na forma de poder serializar qualquer objeto em uma string, use a std::stringstream
classe Mas você precisará implementar suas próprias funções de operador de streaming para que ele funcione com suas próprias classes personalizadas.
Um construtor de strings conveniente para c ++
Como muitas pessoas responderam antes, std :: stringstream é o método de escolha. Funciona bem e possui muitas opções de conversão e formatação. IMO, porém, tem uma falha bastante inconveniente: você não pode usá-lo como um liner ou como uma expressão. Você sempre tem que escrever:
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
o que é bastante irritante, especialmente quando você deseja inicializar seqüências de caracteres no construtor.
O motivo é que a) std :: stringstream não tem operador de conversão para std :: string eb) os operadores << () do stringstream não retornam uma referência stringstream, mas uma referência std :: ostream - que não pode mais ser computado como um fluxo de strings.
A solução é substituir o std :: stringstream e oferecer melhores operadores correspondentes:
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;
}
Com isso, você pode escrever coisas como
std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )
mesmo no construtor.
Tenho que confessar que não medi o desempenho, pois ainda não o usei em um ambiente que faça uso pesado da construção de strings, mas presumo que não será muito pior do que std :: stringstream, pois tudo está feito via referências (exceto a conversão em string, mas isso também é uma operação de cópia em std :: stringstream)
std::stringstream
que não se comporta dessa maneira.
O contêiner Rope pode valer a pena se for necessário inserir / excluir uma string no local aleatório da string de destino ou por longas sequências de caracteres. Aqui está um exemplo da implementação da 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.
Eu queria adicionar algo novo por causa do seguinte:
Numa primeira tentativa, não consegui vencer
std::ostringstream
é operator<<
eficiência, mas com mais tentativas, consegui criar um StringBuilder que é mais rápido em alguns casos.
Sempre que anexo uma string, apenas guardo uma referência a ela em algum lugar e aumento o contador do tamanho total.
A maneira real de finalmente implementá-lo (Horror!) É usar um buffer opaco (std :: vector <char>):
para byte []
para cadeias movidas (cadeias anexadas com std::move
)
std::string
objeto (temos propriedade)para cordas
std::string
objeto (sem propriedade)Há também uma pequena otimização: se a última string inserida foi movida, ela verifica se há bytes reservados reservados, mas não utilizados, livres e armazena mais bytes lá em vez de usar o buffer opaco (isso é para economizar memória, na verdade, fica um pouco mais lento , talvez dependa também da CPU, e é raro ver sequências com espaço reservado extra de qualquer maneira)
Finalmente, isso foi um pouco mais rápido do que, std::ostringstream
mas tem poucas desvantagens:
ostringstream
conclusão? usar
std::ostringstream
Ele já corrige o maior gargalo, enquanto ganhar alguns pontos percentuais em velocidade com a implementação da mina não vale a pena.
std::ostringstream
.