我想知道字符串和内存如何协同工作。
据我所知,我知道当创建一个字符串时,它会将一些字符数组+'\ 0'放入内存中。我也知道他们是不变的。那么对于像连接这样的东西,内存中会发生什么,允许你访问相同的字符串?
我不认为你连接的字符串或字符直接放在原始字符串的地址之后,因为这可能会重叠一些所需的内存。
在C#和其他语言中,您可以说:
string s = "Hello"
...
s = s + '!'
这会创建一个新字符串吗?一个指向一个新的位置,上面写着“你好!”,原来永远不会被引用?
或者是否存在字符串使用的默认字符串缓冲区,允许连接中的某些空间?
答案 0 :(得分: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)
修改rvalue-reference,最后将结果发送到rvalue-reference版本赋值运算符,可以有效地执行移动操作。
有关详细信息,请参阅标准的相关部分。
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 ,然后使用append()
成员函数追加给定的 rhs (第一步中的临时)。为简洁起见,我省略了 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&amp; str,size_type pos,size_type n);
需要:pos&lt; = str.size()
抛出:out_of_range如果pos&gt; str.size()。
效果:确定要附加的字符串的有效长度rlen,作为n和str.size() - pos中较小的一个。如果size()&gt; = npos - rlen,则函数会抛出length_error。否则,该函数将由* this控制的字符串替换为长度为size()+ rlen的字符串,其第一个size()元素是由* this控制的原始字符串的副本,其余元素是初始元素的副本由str控制的字符串从位置pos开始。
返回:* this。
基本上,这会对位置参数进行一些健全性检查,然后使用连接内容执行替换。最后,现在已完成赋值的完成后,我们可以对整个惨败的目标执行赋值操作,这将带我们去...
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。我希望这已经足够了。
答案 1 :(得分:2)
正如评论中指出的那样,std::string
不是一成不变的。
将+运算符与字符串一起使用时,如s + '!'
中所示,将创建一个包含结果的新临时字符串。 s = s + '!'
将此临时字符串复制回原始s
,替换原始文本。这就是不可变字符串在其他语言中的工作方式。
使用+ =运算符或追加函数时,将修改字符串,并将额外字符添加到同一字符串对象中。但是,如果旧的内存缓冲区不够大,可以在内部分配新的内存缓冲区。重新分配时,通常会要求一些额外的空间,以便在不重新分配的情况下允许小的未来追加(更高效)。您可以选择使用保留功能增加内部缓冲区的最小大小。如果您知道要追加多少数据,这会更有效。
答案 2 :(得分:0)
直到C ++ 11,它依赖于实现。但是,使用+=
时,图书馆使用+
的优化次数(并且仍然有)要比使用s+= "!" ;
时更好。最大的区别是C ++ 11现在指定(并强制要求)这些优化。
一般规则(包括语言的过去,现在和未来规范,甚至类似语言)是:总是喜欢
string
代替您使用的示例代码。
原因是s = s + "!" ;
s不是语言原语。它们只是另一种“用户”类型(恰好与编译器一起提供,但这是另一个故事)。当你写
+
调用类string
的{{1}}方法。但是,它被迫创建一个新对象(可能与s
共享一些存储空间),因为您可以在其他上下文中使用它:
t = s + "!" ;
相反,+=
方法可以确保您想要附加到当前字符串,从而优化一点(例如:使用内部缓冲区中的可用空间)。