0

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

据我所知,我知道创建字符串时,它会将一些字符数组 + '\0' 放入内存中。我也知道它们是不可变的。那么对于连接之类的事情,内存中会发生什么让您访问相同的字符串?

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

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

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

这会创建一个新字符串吗?一个指向一个新位置,上面写着“你好!”,而原始位置永远不会被引用?

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

4

3 回答 3

4

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

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)。——尾注]

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

有关详细信息,请参阅标准的相关部分。


C++03x 笔记

有人要求我为 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 ,然后使用成员函数附加给定的rhs(第一步的临时) 。append()为简洁起见,我省略了lhs临时的 const-reference 构造函数。这将我们带到......

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..5 basic_string& append(const basic_string& str, size_type pos, size_type 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 控制的字符串,从位置 pos 开始。

返回:*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所示

返回:*this

表 43 表示以下所需的效果。

data()- 指向数组的已分配副本的第一个元素,该数组的第一个元素由str.data()

size()- str.size()

capacity()- 至少和size()

我对此的评估是,实现可以做它想要达到的效果(在表 43 中;仍然需要此处显示的实现路径)。

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

于 2013-08-22T03:23:11.770 回答
2

正如评论中指出的那样,std::string它不是一成不变的。

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

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

于 2013-08-22T02:37:14.297 回答
0

在 C++11 之前,它是依赖于实现的。但是,与+=使用+. 最大的区别在于 C++11 现在指定(并强制执行)这些优化。

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

s+= "!" ;

代替您使用的示例代码。

原因是strings 不是语言原语。它们只是另一种“用户”类型(恰好与编译器一起提供,但这是另一回事)。当你写

s = s + "!" ;

调用+类的方法。string但是,它被迫创建一个新对象(可能与 共享一些存储空间s),因为您可以在其他上下文中使用它:

t = s + "!" ;

相反,该+=方法可以确定您想要追加到当前字符串,从而优化一点(例如:使用内部缓冲区中的可用空间)。

于 2013-08-22T02:35:53.910 回答