C++14 - std::revers(ed) std::string是否在开头包含空字符?

10

如果我在 std::string 变量上使用 std::reverse 函数,我可以安全地假设空字符 '\0' 将被放置在字符串的开头吗?


4
如果你有一个以空字节结尾的字符串,那么是的。如果你没有在末尾加入一个空字节,就不要期望它在开头出现。 - dascandy
3
std::string在其内部表示中不一定必须包含\0,反转它也不会改变这一点。 - Maksim Solovjov
3
访问 *std::end(s) 在许多实现中可能有效,但标准并不保证其有效性,在其他实现上实际上会导致错误。这是因为大多数 std::string 的实现会在末尾保留一个空字符以简化 .data().c_str() 的实现。我知道确实会导致错误,在某些 MSVC 实现的调试版本中解引用 std::end(s) - Dietrich Epp
1
@KyleStrand 哦天啊,我的初步逻辑现在看起来好蠢啊。我以为当用户输入一个三位数时,第四位将被空字节占用。因此,当我在 cout 结果时,可以检查 \0 并省略千位。 - user5090588
3
我想作为程序员,这种感觉是不可或缺的。 :) 如果你不觉得你之前的尝试比现在差,那么你并没有真正进步... - user5090588
显示剩余10条评论
4个回答

15

不,它不会包含一个NUL字节(除非你自己添加)。前向迭代器也不会包含NUL字节,它只会迭代字符串本身中的字符。

然而,NUL字节保证在.c_str()或.data()的结尾之后。

示例程序:

#include <string>
#include <iostream>
#include <algorithm>

int main() {
    std::string s = "Hello";
    std::cout << "Forwards:\n";
    for (auto i = std::begin(s), e = std::end(s); i != e; ++i) {
        std::cout << *i << ' ' << static_cast<int>(*i) << '\n';
    }
    std::cout << "Backwards:\n";
    std::reverse(std::begin(s), std::end(s));
    for (auto i = std::begin(s), e = std::end(s); i != e; ++i) {
        std::cout << *i << ' ' << static_cast<int>(*i) << '\n';
    }
    return 0;
}

输出:

正向:
H 72
e 101
l 108
l 108
o 111
反向:
o 111
l 108
l 108
e 101
H 72

2
@zstewart 实际上它最后会有自己的空字节 - 这就是为什么 c_str() 是 O(1) 的原因。 - Barry
1
@Andy356:如果你想要一个NUL字节,那么你可以使用指针来安全地迭代.c_str().data()中的字符,包括NUL字节。你只是不能使用标准的std::string迭代器。 - Dietrich Epp
1
@KyleStrand 是的,你保证不需要重新分配内存就可以附加\0的方法是...一开始就附加\0。否则,它就不会是O(1)... - Barry
1
@DietrichEpp,我建议您阅读17.6.5.9 [res.on.data.races]和23.2.2 [container.requirements.dataraces]。当并发调用单个对象上的const函数时,标准库类型要求避免数据竞争,因此两个线程同时调用s.data()s.c_str()s[s.length()]或任何其他const函数必须是安全的,这使得实现在这些函数中执行任何写入操作非常困难(如果它们确实进行任何此类写入,则必须添加足够的同步以避免数据竞争)。 - Jonathan Wakely
2
无论如何,你会得到很多不同的答案,因为在 C++11 中,std::string 的规则已经发生了变化。在 C++11 之前,你不能保证有一个类似 C 的结束零字节。而自从 C++11 之后,你是有保障的,实际上 c_str()data() 是相同的。 - user4992621
显示剩余13条评论

2
我可以安全地假设空字符'\0'将被放置在字符串的开头吗?
不可以。只有在您将空字符作为原始字符串的最后一个字符放置时,才会出现这种情况。

2
与大多数容器不同,std::string实际上在end()迭代器下有数据( 标准虽然使解引用 end() 仍然是非法的,但实际上无法避免它)。
一个大小为.size()=Nstd::stringN+1个条目,最后一个条目是'\0'。前面的N个条目也可能包含'\0',但最后一个条目会自动放置。 begin()返回指向第一个条目的迭代器,而end()返回指向最后一个条目后一个位置的迭代器(有效地,指向终止的'\0',但根据标准是不允许检查的(这意味着调试迭代器可以捕获该错误,并告诉你你犯了错误))。
同时,.data().c_str()返回指向原始缓冲区的指针。使用.c_str()读取'\0' "超出结尾"是合法的。有趣的是,使用.data()读取'\0'结尾之外的内容是合法的,因为只有可以迭代到的元素才允许被读取。这在标准下是合法的,但不太可能,在第一次调用.c_str()之前'\0'处会有一个未初始化的字符(甚至是一个页面错误)。两者都不允许在C++11中分配。(我不对C++03或更早版本的C++做任何保证)。
因此,rbegin()rend()也返回字符串中的元素,不包括终止的'\0',并且reverse(begin(), end())再次操作字符串中的元素,不包括终止的'\0'
如果您在字符串中嵌入了'\0',则这些不会终止字符串。如果将.c_str()传递给const char* API,则它将假定字符串已结束,但由std::string管理的实际缓冲区将超出您注入的'\0'end()仍然将继续引用该字符串的"最后一个元素的后一个位置",而不是您注入的'\0'

@Andy356 这取决于您认为std::string 是什么? 您运行了 std::string x = "abcd\0"; 吗?还是运行了 std::string x = "abcd";?或者是这样运行的 char const buff = "abcd"; std::string x(std::begin(buff), std::end(buff));?这三者都可能被某些人称为 std::string 'abcd\0',但其中两个与第三个不同。如果您想知道代码做什么,请实际编写该代码并询问它做了什么。您还可以添加代码的解释以及您正在做事情的原因,但要询问伪代码的作用通常需要包含在伪代码中的细节。 - Yakk - Adam Nevraumont
我在谈论 std::string x = "abcd"; - user5090588
@Andy356 然后在执行 std::reverse(x.begin(), x.end()); 后,保证 x=="dcba" 为真。 - Yakk - Adam Nevraumont
但正如您所提到的,(N+1)字节始终包含'\0',因此理论上,如果我们直接查看内存中的字节,则在dcba之后会有一个\0,对吗?abcd\0在std::reverse之后变为dcba\0? - user5090588
1
@dyp aha,一个C++11的变化:"data()[i] == operator[](i)对于每个i在[0, size())上成立。 (直到C++11)" 和 "data() + i == &operator[](i)对于每个i在[0, size())上成立。 (自C++11起)"。这解释了cppreference文档中的模糊用语。有趣的是,data()强制[size()]返回[size()-1]之后的元素,即使operator[]没有这样做! - Yakk - Adam Nevraumont
显示剩余6条评论

0
一个std::string不会包含一个终止的空字符,你可能在想用作字符串的以空字符结尾的字符数组。

它不被视为字符串的元素之一,但它确实包含一个,在所有其他元素之后。如果您使用s.c_str()s.data()s[s.length()],则保证存在。 - Jonathan Wakely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接