C++ C++;std::字符串附加vs push_back()
这真的是一个仅出于我自身利益的问题,我无法通过文档确定 我从中看出,附加具有复杂性: 未指定,但在新字符串长度中通常为线性 而push_back()具有复杂性: 未指定;通常为摊销常数,但在新字符串长度中为线性 作为一个玩具示例,假设我想将字符“foo”附加到字符串中。会C++ C++;std::字符串附加vs push_back(),c++,string,append,push-back,C++,String,Append,Push Back,这真的是一个仅出于我自身利益的问题,我无法通过文档确定 我从中看出,附加具有复杂性: 未指定,但在新字符串长度中通常为线性 而push_back()具有复杂性: 未指定;通常为摊销常数,但在新字符串长度中为线性 作为一个玩具示例,假设我想将字符“foo”附加到字符串中。会 myString.push_back('f'); myString.push_back('o'); myString.push_back('o'); 及 完全一样的东西?还是有什么区别?您可能会认为append会更有效,因为
myString.push_back('f');
myString.push_back('o');
myString.push_back('o');
及
完全一样的东西?还是有什么区别?您可能会认为append会更有效,因为编译器会知道需要多少内存才能将字符串扩展到指定的字符数,而push_-back可能需要确保每次调用的内存?是的,出于您给出的原因,我还希望
append()
执行得更好,在需要追加字符串的情况下,使用append()
(或operator+=
)当然更可取(尤其是因为代码可读性更高)
但标准规定的是操作的复杂性。即使对于append()
,这通常也是线性的,因为最终被追加的字符串的每个字符(以及可能的所有字符,如果发生重新分配)都需要被复制(即使使用了memcpy
或类似的内容,这也是正确的)。在C++03中(大多数“cplusplus.com”的文档都是为其编写的),由于允许库实现者对字符串进行写时复制或“rope样式”的内部表示,因此未指定复杂性。例如,如果一个字符被修改并且共享正在进行,COW实现可能需要复制整个字符串
在C++11中,禁止使用COW和rope实现。您应该期望每个添加的字符有固定的摊销时间,或者在字符串末尾添加的字符数中有线性摊销时间。实现者可能仍然会对字符串做一些相对疯狂的事情(比如说,与std::vector相比),但大多数实现都会局限于“小字符串优化”之类的事情
在比较
push_back
和append
时,push_back
剥夺了底层实现可能用于预分配空间的潜在有用长度信息。另一方面,append
要求实现对输入进行两次遍历,以找到该长度,因此性能增益或损失将取决于许多未知因素,例如尝试追加之前字符串的长度。也就是说,差别可能非常小。为此,请使用append
,它更具可读性。在此处再添加一个意见
我个人认为,在从另一个字符串中逐个添加字符时,最好使用<代码> PurthBook()/代码>。例如:
string FilterAlpha(const string& s) {
string new_s;
for (auto& it: s) {
if (isalpha(it)) new_s.push_back(it);
}
return new_s;
}
如果在这里使用
append()
,我会用append(1,it)
替换push_-back(it)
,这对我来说不是那么容易理解。我也有同样的疑问,所以我做了一个小测试来检查这一点(Linux上的g++4.8.5和C++11配置文件,Intel,VmWare Fusion下的64位)
结果很有趣:
push :19
append :21
++++ :34
推送:19
附加:21
++++ :34
这可能是因为字符串长度(大),但是运算符+与push_-back和append相比非常昂贵
另外有趣的是,当操作符只接收一个字符(不是字符串)时,它的行为与push_back非常相似
为了不依赖于预先分配的变量,每个周期都在不同的范围内定义
注意:vCounter只是使用gettimeofday来比较差异
TimeCounter vCounter;
{
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++) {
vTest.push_back('a');
vTest.push_back('b');
vTest.push_back('c');
}
vCounter.stop();
cout << "push :" << vCounter.elapsed() << endl;
}
{
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++) {
vTest.append("abc");
}
vCounter.stop();
cout << "append :" << vCounter.elapsed() << endl;
}
{
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++) {
vTest += 'a';
vTest += 'b';
vTest += 'c';
}
vCounter.stop();
cout << "++++ :" << vCounter.elapsed() << endl;
}
时间计数器vCounter;
{
字符串vTest;
vCounter.start();
对于(int-vIdx=0;vIdxBesides-the-besides-the-besides-the-besides-the-besides-the-besides-the-besides-besides-the-besides-the-besides-besides-the-besides-the-besides-besides-the-besides-besides-besides-besides-besides-besides-besides-the-besi。虽然不幸的是,他们没有说明追加
的典型情况,但只有最坏的情况。@JoachimPileborg:推回
必须按固定时间摊销,因此我所知道的每个实现三次分配的可能性为零。大多数从合理的大小开始(大于1或2)并以几何方式增长基础缓冲区(按1.5倍或每个大小增加2倍)。需要复制附加字符串的每个字符。如果不需要调整基础内存块的大小,则不需要复制字符串的现有内容。但是,如果只想附加1个字符,则没有附加(字符)
重载。在这种情况下,可以使用push_back()
。@rustyx当然可以,但那将是另一个问题:)你知道C++11/14/17标准是否对字符串的push_back
/append
/insert
的复杂性要求进行了严格的限制?如果没有,您确定大多数现有的实现都是如此友好吗?(我隐约记得过去在这些方面存在问题,但这可能只是一些CoW实现的结果。)@尼莫:我不确定你能要求什么样的“紧缩”。据我所知,它们一直是按固定时间摊销的。目前它是N4606 23.2.3[sequence.reqmts]/16@BillyONeal:我的意思是相对于C++03,在C++03中,push_back
等。字符串基本上没有复杂度保证(不像vector)。我看到这是针对C++11中字符串上的push_back
进行的更正,谢谢您的参考。但据我所知,该标准没有对字符串的insert(end(),…)
或append()
施加任何复杂性要求。因此从“严格的
TimeCounter vCounter;
{
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++) {
vTest.push_back('a');
vTest.push_back('b');
vTest.push_back('c');
}
vCounter.stop();
cout << "push :" << vCounter.elapsed() << endl;
}
{
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++) {
vTest.append("abc");
}
vCounter.stop();
cout << "append :" << vCounter.elapsed() << endl;
}
{
string vTest;
vCounter.start();
for (int vIdx=0;vIdx<1000000;vIdx++) {
vTest += 'a';
vTest += 'b';
vTest += 'c';
}
vCounter.stop();
cout << "++++ :" << vCounter.elapsed() << endl;
}