Существует ли в C ++ стандартная библиотека шаблонов классов, которая обеспечивает эффективную функциональность конкатенации строк, аналогичную Cring StringBuilder или Java StringBuffer ?
Существует ли в C ++ стандартная библиотека шаблонов классов, которая обеспечивает эффективную функциональность конкатенации строк, аналогичную Cring 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 :: format
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 # StringBuilder, я считаю snprintf(или sprintfесли вы хотите рискнуть написать ошибочный код ;-)) в массив символов и преобразовать обратно в строку - это единственный вариант.
Поскольку std::stringв C ++ изменчиво, вы можете использовать это. Это имеет += operatorиappend функция.
Если вам нужно добавить числовые данные, используйте std::to_string функции.
Если вам нужна еще большая гибкость в форме возможности сериализации любого объекта в строку, используйте std::stringstreamкласс. Но вам нужно будет реализовать свои собственные функции оператора потоковой передачи, чтобы он работал с вашими собственными классами.
Удобный конструктор строк для C ++
Как многие люди отвечали ранее, метод std :: stringstream является предпочтительным. Он работает хорошо и имеет много вариантов преобразования и форматирования. ИМО, однако, имеет один довольно неудобный недостаток: вы не можете использовать его как один вкладыш или как выражение. Вы всегда должны написать:
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
что довольно раздражает, особенно если вы хотите инициализировать строки в конструкторе.
Причина в том, что a) std :: stringstream не имеет оператора преобразования в std :: string и b) операторы << () строкового потока не возвращают ссылку на поток строки, а вместо этого ссылку на 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.