Существует ли в 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
.