Є клас C ++ Standard Template Library , яка забезпечує ефективну струнную функціональність конкатенації, схожу на С # StringBuilder або в Java StringBuffer ?
Є клас C ++ Standard Template Library , яка забезпечує ефективну струнную функціональність конкатенації, схожу на С # StringBuilder або в Java StringBuffer ?
Відповіді:
ПРИМІТКА. Ця відповідь останнім часом привернула деяку увагу. Я не виступаю за це як рішення (це рішення, яке я бачив у минулому, перед STL). Це цікавий підхід і повинен застосовуватися тільки по std::string
або std::stringstream
якщо після профілювання коду ви виявите , що робить поліпшення.
Я зазвичай використовую std::string
або std::stringstream
. У мене ніколи не було проблем із цим. Я, як правило, спочатку зарезервую трохи приміщення, якщо заздалегідь знаю шорсткий розмір струни.
Я бачив, як інші люди роблять власні оптимізовані будівельники струн у далекому минулому.
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;
}
};
Він використовує два рядки, один для більшості рядків, а інший як область подряпин для об'єднання коротких рядків. Він оптимізує додавання, додаючи операції з коротким додаванням в одну невелику рядок, потім додаючи її до головної рядки, таким чином зменшуючи кількість перерозподілів, необхідних для основної рядки, коли вона збільшується.
Я не вимагав цього трюку з std::string
або std::stringstream
. Я думаю, що він використовувався із сторонньою бібліотекою рядків до std :: string, це було давно. Якщо ви скористаєтеся такою стратегією, спочатку подайте заявку.
scratch
струна насправді нічого тут не виконує. Кількість перерозподілів основного рядка значною мірою буде функцією його остаточного розміру, а не кількості операцій додавання, якщо string
реалізація дійсно погана (тобто не використовує експоненціальне зростання). Тож "збір" вгору append
не допомагає, оскільки коли основна string
величина, вона зростатиме лише зрідка в будь-якому випадку. Крім того, він додає купу надмірних операцій копіювання, і може зробити більше перерозподілів (отже, дзвінки в new
/ delete
), оскільки ви додаєте до короткого рядка.
str.reserve(1024);
що буде швидше, ніж ця річ
Способом C ++ було б використання std :: stringstream або просто об'єднання рядків. Структури C ++ є змінними, тому міркування щодо ефективності конкатенації не викликають особливих проблем.
що стосується форматування, ви можете зробити все те саме форматування в потоці, але по-іншому, аналогічноcout
. або ви можете використовувати сильно набраний функтор, який інкапсулює це і надає інтерфейс типу String.Format, наприклад, boost :: формат
StringBuilder
полягає в тому, щоб покрити неефективність незмінного базового типу String Java . Іншими словами, StringBuilder
це печворк, тому ми повинні бути раді, що нам не потрібен такий клас на C ++.
O(n)
взагалі.
std::string.append
Функція не є хорошим варіантом , оскільки він не приймає багато форм даних. Більш корисною альтернативою є використання std::stringstream
; так:
#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();
Ви можете використовувати .append () для просто об'єднання рядків.
std::string s = "string1";
s.append("string2");
Я думаю, ви навіть зможете зробити:
std::string s = "string1";
s += "string2";
Що стосується операцій з форматування C # 's StringBuilder
, я вважаю, snprintf
(або sprintf
якщо ви хочете ризикувати написанням баггі-коду ;-)) в масив символів і перетворення назад у рядок - це єдиний варіант.
Оскільки std::string
в C ++ є змінним, ви можете використовувати це. Він має += operator
і append
функцію.
Якщо вам потрібно додати числові дані, скористайтеся std::to_string
функціями.
Якщо ви хочете ще більше гнучкості у вигляді можливості серіалізувати будь-який об’єкт до рядка, тоді використовуйте std::stringstream
клас. Але вам потрібно буде реалізувати власні функції оператора потокового передавання, щоб він працював із власними спеціальними класами.
Зручний конструктор струн для c ++
Як і багато людей, які раніше відповідали, std :: stringstream є методом вибору. Він працює добре і має багато варіантів перетворення та форматування. IMO у нього є один досить незручний недолік: Ви не можете використовувати його як один вкладиш або як вираз. Ви завжди повинні написати:
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
що дуже дратує, особливо коли ви хочете ініціалізувати рядки в конструкторі.
Причина полягає в тому, що a) std :: stringstream не має оператора перетворення в std :: string та b) оператора << () поточного рядка не повертає посилання stringstream, а замість std :: ostream - який не можна додатково обчислити як потоковий рядок.
Рішення полягає в тому, щоб переосмислити std :: stringstream та забезпечити його кращі відповідність операторам:
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;
}
За допомогою цього ви можете писати такі речі
std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )
навіть у конструкторі.
Я мушу зізнатися, що я не вимірював продуктивність, оскільки я ще не використовував її в середовищі, яке ще сильно використовує побудову рядків, але я припускаю, що це буде не гірше, ніж std :: stringstream, оскільки все зроблено через посилання (крім перетворення в рядок, але це операція копіювання в std :: stringstream також)
std::stringstream
так не поводиться.
Rope контейнер може бути варто , якщо потрібно вставити / видалити рядок в випадкове місце рядка призначення або для довгих послідовностей гольців. Ось приклад реалізації 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.
Я хотів додати щось нове через наступне:
При першій спробі мене не вдалося побити
std::ostringstream
's operator<<
ефективність, але за допомогою більшої кількості спроб я зміг зробити StringBuilder, який у деяких випадках швидший.
Щоразу, коли я додаю рядок, я просто зберігаю посилання на нього десь і збільшую лічильник загального розміру.
Реальний спосіб, коли я його остаточно реалізував (жах!) - це використовувати непрозорий буфер (std :: vector <char>):
для байтів []
для переміщених рядків (рядки додаються std::move
)
std::string
об’єкт (у нас є право власності)для струн
std::string
об’єкт (без власності)Існує також одна невелика оптимізація, якщо останній вставлений рядок був переміщений, він перевіряє наявність вільних зарезервованих, але невикористаних байтів, і зберігає подальші байти туди замість використання непрозорого буфера (це для економії деякої пам’яті, це фактично робить це трохи повільніше , може залежати також від процесора, і рідко можна зустріти рядки з додатковим зарезервованим простором)
Це, нарешті, трохи швидше, std::ostringstream
але у нього є кілька недоліків:
ostringstream
висновок? використання
std::ostringstream
Це вже фіксує найбільше вузьке місце, тоді як набирати декілька відсотків у швидкості при впровадженні міни не варто недоліків.
std::ostringstream
.