是否有C ++标准模板库类提供类似于C#的StringBuilder或Java的StringBuffer的有效字符串连接功能?
是否有C ++标准模板库类提供类似于C#的StringBuilder或Java的StringBuffer的有效字符串连接功能?
Answers:
注意此答案最近受到了一些关注。我不主张将其作为解决方案(这是我过去在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
存在的全部原因是为了掩盖Java不变的基本String类型的无效性。换句话说StringBuilder
就是拼凑而成,所以我们应该很高兴在C ++中不需要这样的类。
O(n)
通常会产生一系列追加。
您可以使用.append()来简单地串联字符串。
std::string s = "string1";
s.append("string2");
我认为您甚至可以做到:
std::string s = "string1";
s += "string2";
至于C#的格式化操作StringBuilder
,我相信snprintf
(或者sprintf
如果您想冒风险编写错误代码;-))放入字符数组并转换回字符串,则是唯一的选择。
一个方便的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
不这样做。
如果必须将字符串插入/删除目标字符串的随机位置或使用较长的char序列,则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
的 operator<<
效率,但是通过更多的尝试,我能够使StringBuilder在某些情况下更快。
每当我附加一个字符串时,我都会将对它的引用存储在某个地方,并增加总大小的计数器。
我最终实现它的真正方法(恐怖!)是使用不透明的缓冲区(std :: vector <char>):
对于字节[]
用于移动的字符串(附加了的字符串std::move
)
std::string
对象(我们拥有所有权)对于字符串
std::string
对象的指针(无所有权)还有一个小的优化,如果最后插入的字符串被移入,它将检查空闲的保留但未使用的字节并在其中存储更多字节,而不是使用不透明的缓冲区(这是为了节省一些内存,实际上会使它变慢一些,可能还取决于CPU,因此很少会看到带有额外预留空间的字符串)
最终std::ostringstream
,它的速度比之前略快,但缺点却很少:
ostringstream
结论?用
std::ostringstream
它已经解决了最大的瓶颈,而在实施矿井方面提高了几个百分点的速度是不值得的缺点。
std::ostringstream
。