如何分配串联字符串

How are concatenated strings allocated?

本文关键字:字符串 分配 何分配      更新时间:2023-10-16

我想知道字符串和内存是如何协同工作的

据我所知,我知道当创建一个字符串时,它会将一些字符数组+"\0"放入内存。我也知道它们是不可变的。那么,对于串联之类的事情,内存中会发生什么,从而允许您访问相同的字符串

我不认为连接的字符串或字符会直接放在原始字符串的地址后面,因为这可能会与一些所需的内存重叠

在C#和其他语言中,您可以说:

string s = "Hello" ... s = s + '!'

这会创建一个新字符串吗?一个指向一个新位置,上面写着"你好!",让原来的位置永远不会被引用?

或者字符串使用的默认字符缓冲区是否允许在串联中留出一些空间?

您所质疑的表达式的行为是由标准定义的,并且是实现所必需的。该标准的相关章节如下:

C++11§21.4.8.1-11

template<class charT, class traits, class Allocator> 
    basic_string<charT,traits,Allocator>
operator+(const basic_string<charT,traits,Allocator>& lhs,
          const charT* rhs);

返回:lhs + basic_string<charT,traits,Allocator>(rhs)

这导致:

C++11§21.4.8.1-3

template<class charT, class traits, class Allocator>
    basic_string<charT,traits,Allocator>
operator+(const basic_string<charT,traits,Allocator>& lhs,
          basic_string<charT,traits,Allocator>&& rhs);

返回:std::move(rhs.insert(0, lhs))

最后。。。

C++11§21.4.2-22

basic_string<charT,traits,Allocator>&
  operator=(basic_string<charT,traits,Allocator>&& str) noexcept;

效果:如果*this和str不是同一个对象,则将*this修改为如表71所示。[注意:有效的实现是swap(str)。--end注]

换句话说,为+运算符的rhs创建一个临时值,然后使用rhs.insert(0,lhs)修改该右值引用,最后,将结果发送到赋值运算符的右值引用版本,该版本可以有效地执行移动操作。

有关更多信息,请参阅该标准的相关章节。


C++03x Notes

有人要求我为C++03x提供相同的演练。我对该标准的最后(官方)版本不确定,但为了参考,以下内容基于ISO/IEC 14882:2003(E)。自行使用。

C++03x也定义了类似的通道,如下所述,并适当注明了标准的相关章节。

C++03x§21.3.7.1-5

template<class charT, class traits, class Allocator>
             basic_string<charT,traits,Allocator>
operator+(const basic_string<charT,traits,Allocator>& lhs, const charT* rhs);

返回:lhs + basic_string<charT,traits,Allocator>(rhs)

因此,与C++11一样,临时表达式是由表达式的rhs构造的。从那里。。。

C++03x§21.3.7.1-1

template<class charT, class traits, class Allocator>
             basic_string<charT,traits,Allocator>
operator+(const basic_string<charT,traits,Allocator>& lhs, 
          const basic_string<charT,traits,Allocator>& rhs);

返回:basic_string(lhs).append(rhs)

这里我们与C++11不同。我们构造lhs的临时,然后使用append()成员函数附加给定的rhs[第一步中的临时]。为了简洁起见,我省略了lhs的临时const引用构造函数。这把我们带到…

C++03x§21.3.5.2-1

basic_string<charT,traits,Allocator>&
  append(const basic_string<charT,traits>& str);

返回:append(str, 0, npos)

这将调用转发到相关的成员函数,该函数接受要枚举的rhs的开始索引和停止索引。这将带我们去…

C++03x§21.3.5.2-2..5basic_ string&append(const basic_string&str,size_type pos,sizetype n);

需要:pos<=str.size()

抛出:如果pos>str.size(),则抛出out_of_range。

效果:将要附加的字符串的有效长度rlen确定为n和str.size()-pos中较小的一个。如果size()>=npos-rlen,则函数会抛出length_error。否则,函数将*this控制的字符串替换为长度为size()+rlen的字符串,该字符串的第一个size()元素是*this控制的原始字符串的副本,其余元素是从位置开始的str控制的字符串的初始元素的副本

返回:*this。

本质上,这会对位置参数进行一些健全性检查,然后用连接的内容执行替换。最后,现在已经完成了任务的rhs,我们可以对整个惨败的目标执行任务操作,这将带我们去…

C++03x§21.3.1-16

basic_string<charT,traits,Allocator>&
  operator=(const basic_string<charT,traits,Allocator>& str);

效果:如果*this和str不是同一个对象,则修改*this,如表-43 所示

返回:*此

表43表示以下所需效果。

data()-指向str.data() 指向其第一个元素的阵列的已分配副本的第一个元素

size()-str.size()

capacity()-至少与size() 一样大

我的评估是,实施可以随心所欲地实现这些效果(在表43中;这里所示的实施路径仍然是必需的)。

我太累了,没法开车进入C++98。我希望这已经足够了。

正如评论中所指出的,std::string并不是一成不变的。

在字符串中使用+运算符时,如s + '!'中所示,将创建一个新的临时字符串,其中包含结果。s = s + '!'将此临时字符串复制回原始s,替换原始文本。这就是不可变字符串在其他语言中的工作方式。

当您使用+=运算符或append函数时,会修改字符串,并将额外的字符添加到同一字符串对象中。但是,如果旧的内存缓冲区不够大,则可能会在内部分配新的内存缓冲。在重新分配时,通常会请求一些额外的空间,以允许在不重新分配的情况下进行小的未来追加(更高效)。您可以选择使用reserve函数来增加内部缓冲区的最小大小。如果您知道要追加多少数据,这会更有效。

在C++11之前,它依赖于实现。然而,与使用+相比,使用+=时库优化的机会要大得多。最大的区别是C++11现在指定(并强制)这些优化。

一般规则(包括过去、现在和未来的语言规范,甚至类似的语言)是:始终更喜欢

s+= "!" ;

代替您使用的示例代码。

原因是string不是语言原语。它们只是另一种"用户"类型(编译器附带了这种类型,但那是另一回事)。当你写

s = s + "!" ;

调用类CCD_ 25的CCD_。然而,它被迫创建一个新对象(可能与s共享一些存储),因为您本可以在其他上下文中使用它:

t = s + "!" ;

相反,+=方法可以确保您希望附加到当前字符串,从而优化一个位(例如:使用内部缓冲区中的可用空间)。