StringBuffer / StringBuilder C ++ eşdeğeri?


184

C # 'ın benzer verimli dize birleştirme işlevsellik sağlayan bir C ++ Standart Şablon Kütüphanesi sınıfı var mıdır StringBuilder veya Java'nın StringBuffer ?


3
kısa cevap: Evet, STL bunun için bir sınıfa sahiptir ve öyle std::ostringstream.
CoffeDeveloper

Hey @andrew. Kabul edilen cevabı değiştirebilir misiniz? Açık bir kazanan cevap var ve şu an kabul edilen cevap değil.
null

Yanıtlar:


53

NOT Bu cevap son zamanlarda biraz ilgi gördü. Bunu bir çözüm olarak savunmuyorum (geçmişte STL'den önce gördüğüm bir çözüm). Bu ilginç bir yaklaşımdır ve sadece üzerine uygulanmalıdır std::stringya std::stringstreamkodunuzu profilleme sonra keşfettiğiniz bu bir iyileşme sağlar.

Normalde kullanmak ya std::stringveya std::stringstream. Bunlarla hiç problem yaşamadım. Önceden dizenin kaba boyutunu biliyorsanız normalde ilk önce bazı oda rezervasyonu.

Diğer insanların uzak geçmişte kendi optimize dize yapıcılarını yaptıklarını gördüm.

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;
    }
};

Biri dizenin çoğunluğu için diğeri kısa dizeleri birleştirmek için bir çizik alanı olarak iki dizge kullanır. Kısa ekleme işlemlerini küçük bir dizede topluyor ve ardından bunu ana dizeye ekleyerek, böylece ana dizede daha fazla büyüdükçe gerekli yeniden tahsis sayısını azaltarak optimizasyon yapar.

Bu numarayı std::stringveya ile istemedim std::stringstream. Ben std :: string önce bir üçüncü taraf dize kütüphanesi ile kullanıldığını düşünüyorum, bu uzun zaman önceydi. Bu profil gibi bir strateji benimserseniz, öncelikle başvurunuz.


13
Tekerleği yeniden icat etmek. std :: stringstream doğru cevaptır. Aşağıdaki iyi yanıtlara bakın.
Kobor42

13
@ Kobor42 Cevabımın ilk ve son satırına işaret ederken sana katılıyorum.
iain

1
Sanmıyorum scratchdize gerçekten burada bir şey yapar. Ana dize yeniden tahsis sayısı, stringuygulama gerçekten zayıf olmadığı sürece (yani üstel büyüme kullanmazsa) büyük ölçüde son boyutunun bir fonksiyonu olacaktır, ekleme işlemlerinin sayısı değil. Yani "yığınlama" appendyardımcı olmaz çünkü altta yatan stringbüyük bir zaman sadece her iki şekilde büyüyecektir. Bunun yanı sıra, bir sürü yedek kopya işlemi ekler ve kısa bir dizeye eklediğiniz için daha fazla yeniden konumlandırma (dolayısıyla new/ çağrıları delete) olabilir.
BeeOnRope

@BeeOnRope Sana katılıyorum.
Iain

eminim str.reserve(1024);bu şeyden daha hızlı olurdu
hanshenrik

160

C ++ yolu std :: stringstream veya sadece düz dize birleşimlerini kullanmak olacaktır. C ++ dizeleri değiştirilebilir, böylece birleştirme performans değerlendirmeleri daha az endişe kaynağıdır.

biçimlendirmeyle ilgili olarak, bir akışta aynı biçimlendirmeyi ancak benzer şekilde farklı bir şekilde yapabilirsinizcout . ya da bunu içine alan ve bir String.Format benzeri bir arayüz sağlayan güçlü bir şekilde yazılmış bir functor kullanabilirsiniz örneğin boost :: format


59
C ++ dizeleri değiştirilebilir : tam olarak. Tek nedeni StringBuildervar etmektir Java'nın değişmez temel String türü verimsizlik kapsayacak . Başka bir deyişle StringBuilderpatchwork, bu yüzden C ++ 'da böyle bir sınıfa ihtiyacımız olmadığına sevinmeliyiz.
bobobobo

57
@bobobobo değişmez dizeleri olsa da, diğer dersler için atları
jk.

8
Düz dize birleştirmeleri yeni bir nesne oluşturmaz, bu yüzden Java'daki değişmezlikle aynı sorunla karşılaşır mı? Tüm değişkenlerin aşağıdaki örnekte dize olduğunu düşünün: a = b + c + d + e + f; B ve c üzerinde operatör +, sonuç ve d üzerinde operatör + çağırmayacak mı?
Serge Rogatch

9
Bir dakika bekle, standart string sınıfı kendini nasıl değiştireceğini biliyor ama bu verimsizliğin olmadığı anlamına gelmiyor. Bildiğim kadarıyla std :: string sadece dahili char * boyutunu genişletemezsiniz. Bu, daha fazla karakter gerektiren bir şekilde mutasyona uğratılması anlamına gelir, yeniden tahsis ve kopyalama gerekir. Bir karakter vektöründen farklı değildir ve bu durumda ihtiyacınız olan alanı ayırmak kesinlikle daha iyidir.
Trygve Skogsholm

7
@TrygveSkogsholm - karakterlerin bir vektöründen farklı değildir, ancak elbette dizenin "kapasitesi" boyutundan daha büyük olabilir, bu nedenle tüm eklerin yeniden tahsis edilmesi gerekmez. Genelde ipler üstel bir büyüme stratejisi kullanacak, böylece ekleme hala doğrusal maliyet işlemine amorti olmaktadır. Bu, her ekleme işleminin her iki Dizgideki tüm karakterleri yenisine kopyalaması gereken Java'nın değişmez Dizelerinden farklıdır, bu nedenle O(n)genel olarak bir dizi ekleme sona erer .
BeeOnRope

93

std::string.appendO verinin birçok formları kabul etmez çünkü işlev iyi bir seçenek değildir. Daha kullanışlı bir alternatif kullanmaktır std::stringstream; şöyle:

#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 olduğu C ++ eşdeğeri: Bu değişken var.


13

Dizeleri birleştirmek için .append () yöntemini kullanabilirsiniz.

std::string s = "string1";
s.append("string2");

Hatta yapabileceğinizi düşünüyorum:

std::string s = "string1";
s += "string2";

C # 's biçimlendirme işlemleri gelince StringBuilder, snprintf(veya sprintfbuggy kodu ;-) yazma riskini istiyorsanız) bir karakter dizisine inanıyorum ve bir dize geri dönüştürmek tek seçenek hakkında.


Yine de printf veya .NET'in String.Format ile aynı şekilde değil mi?
Andy Shellam

1
onun tek yolu olsa söylemek biraz biraz müstehcen
jk.

2
@jk - orijinal sorunun özel olarak sorduğu .NET'in StringBuilder'in biçimlendirme yeteneğini karşılaştırmanın tek yolu bunlar. Ben yanlış olabilir bu yüzden "İnanıyorum" dedim, ama bana printf kullanmadan C ++ StringBuilder işlevselliği almak için bir yol gösterebilir?
Andy Shellam

cevabımı bazı alternatif formatlama seçeneklerini içerecek şekilde güncelledi
jk.

6

Yana std::stringdeğişebilirdir C ++ bunu kullanabilir. Bir += operatorve bir appendişlevi vardır.

Sayısal veri eklemeniz gerekiyorsa std::to_stringişlevleri kullanın .

Herhangi bir nesneyi dizeye serileştirme şeklinde daha fazla esneklik istiyorsanız, std::stringstreamsınıfı kullanın . Ancak, kendi özel sınıflarınızla çalışması için kendi akış operatörü işlevlerinizi uygulamanız gerekir.


4

std :: string's + = const char * ile çalışmaz ("eklemek için string" gibi şeyler görünür), bu yüzden kesinlikle stringstream kullanmak gerekli olana en yakın olanıdır - sadece + yerine << kullanırsınız


3

C ++ için uygun bir dize oluşturucu

Daha önce birçok kişi gibi, std :: stringstream de seçim yöntemidir. İyi çalışıyor ve çok sayıda dönüştürme ve biçimlendirme seçeneği var. IMO yine de oldukça rahatsız edici bir kusuru var: Tek bir astar veya bir ifade olarak kullanamazsınız. Her zaman şunu yazmalısınız:

std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );

özellikle yapıcıdaki dizeleri başlatmak istediğinizde oldukça can sıkıcıdır.

Bunun nedeni, a) std :: stringstream öğesinin std :: string'e dönüştürme operatörü olmaması ve b) stringstream operatörünün << () 'leri bir stringstream referansı döndürmez, bunun yerine std :: ostream referansını döndürür - bir dize akışı olarak daha fazla hesaplanamaz.

Çözüm std :: stringstream'i geçersiz kılmak ve daha iyi eşleşen işleçler vermektir:

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;
}

Bununla, böyle şeyler yazabilirsiniz

std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )

yapıcıda bile.

İtiraf etmeliyim ki, henüz dize yapmanın ağır kullanımını kullanan bir ortamda kullanmadığım için performansı ölçmedim, ama her şeyin yapıldığı için std :: stringstream'den çok daha kötü olmayacağını varsayıyorum. referanslar aracılığıyla (dizeye dönüştürme hariç, ancak std :: stringstream öğesinde de bir kopya işlemi)


Bu temiz. Neden std::stringstreamböyle davranmadığını anlamıyorum .
einpoklum

1

Halat hedef dizesinin rastgele yerine veya uzun karakter dizileri için / silme dize eklemek için varsa konteyner değerinde olabilir. SGI'nın uygulanmasından bir örnek:

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

Aşağıdakiler nedeniyle yeni bir şey eklemek istedim:

İlk denemede yenemedim

std::ostringstream 'ler operator<<

verimlilik, ancak daha fazla girişimi ile bazı durumlarda daha hızlı bir StringBuilder yapabildim.

Her zaman bir dize eklemek Ben sadece bir yerde bir referans saklamak ve toplam boyut sayacı artırmak.

Nihayetinde uyguladığım gerçek yol (Korku!) Opak bir tampon (std :: vector <char>) kullanmaktır:

  • 1 bayt başlığı (aşağıdaki verilerin olup olmadığını anlamak için 2 bit: taşınan dize, dize veya bayt [])
  • Bayt uzunluğunu bildirmek için 6 bit []

bayt için []

  • Doğrudan bayt kısa dizeleri saklıyorum (sıralı bellek erişimi için)

taşınan dizeler için (eklenen dizeler std::move)

  • Bir std::stringnesnenin işaretçisi (sahipliğimiz var)
  • orada kullanılmayan ayrılmış baytlar varsa sınıfta bir bayrak ayarlama

teller için

  • Bir std::stringnesneye işaretçi (sahiplik yok)

Ayrıca, en son eklenen dize taşınırsa, küçük bir optimizasyon da vardır, opak tamponu kullanmak yerine ücretsiz ayrılmış ancak kullanılmayan baytları kontrol eder ve daha fazla bayt depolar (bu, biraz bellek tasarrufu sağlar, aslında biraz daha yavaş hale getirir) , belki de CPU'ya bağlıdır ve zaten fazladan ayrılmış alana sahip dizeleri görmek nadirdir)

Bu nihayet biraz daha hızlıydı std::ostringstreamama birkaç dezavantajı var:

  • Sabit uzunluk karakter türlerini varsaydım (yani 1,2 veya 4 bayt, UTF8 için iyi değil), UTF8 için işe yaramayacağını söylemiyorum, Sadece tembellik için kontrol etmiyorum.
  • Kötü kodlama uygulaması kullandım (opak tampon, taşınabilir değil yapmak kolay, bu arada benimki taşınabilir olduğuna inanıyorum)
  • Tüm özelliklerini içermiyor ostringstream
  • Başvurulan bir dize tüm dizeleri birleştirmeden önce silinirse: tanımsız davranış.

Sonuç? kullanım std::ostringstream

Maden uygulamasında% birkaç hızda ganing yaparken en büyük darboğazı düzeltir, dezavantajlara değmez.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.