为啥 std::ends 会导致字符串比较失败?
Posted
技术标签:
【中文标题】为啥 std::ends 会导致字符串比较失败?【英文标题】:Why does std::ends cause string comparison to fail?为什么 std::ends 会导致字符串比较失败? 【发布时间】:2010-10-22 17:37:00 【问题描述】:我昨天花了大约 4 个小时试图在我的代码中解决这个问题。我将问题简化为下面的示例。
想法是将字符串存储在以 std::ends 结尾的字符串流中,然后稍后检索它并将其与原始字符串进行比较。
#include您可能已经猜到了,上面的代码在执行时不会打印任何内容。
虽然,如果打印出来,或者在调试器 (VS2005) 中查看,HELLO 和 hi 看起来相同,它们的 .length() 实际上相差 1。我猜这就是导致“==”运算符失败。
我的问题是为什么。我不明白为什么 std::ends 是添加到字符串 hi 的不可见字符,即使 hi 和 HELLO 具有相同的内容,它们的长度也会不同。此外,这个不可见的角色不会被增强修剪修剪。但是,如果您使用 strcmp 比较两个字符串的 .c_str(),则比较可以正常进行。
我首先使用 std::ends 的原因是因为我过去遇到过 stringstream 在流末尾保留垃圾数据的问题。 std::ends 为我解决了这个问题。
【问题讨论】:
好的,我了解这背后的机制,但我不喜欢语义。似乎我有两个选择:不使用 std::ends 并冒着产生垃圾数据的风险,或者使用它并添加自定义代码以消除额外的 NULL 字符。 您应该尝试设计您的代码,以便您知道字符串的期望是什么 - 例如,如果您从网络设备读取字符串,它们可能不是以空值结尾的,但这取决于您正在使用的 API,但如果您在应用程序内部传递字符串,那么它们可能是。不要陷入您不知道数据中有什么的情况。 你为什么还要使用末端?这仅在您从原始数据构建空终止的 C 样式字符串时使用。在这里,在您的示例中,它显然不合适。你已经有了一个 C++ 字符串。 【参考方案1】:std::ends
在流中插入一个空字符。以std::string
形式获取内容将保留该空字符并在相应位置创建一个带有该空字符的字符串。
所以确实 std::string 可以包含嵌入的空字符。以下 std::string 内容是不同的:
ABC
ABC\0
二进制零不是空格。但它也不能打印,所以你不会看到它(除非你的终端特别显示它)。
当您传递.c_str()
时,使用strcmp
进行比较会将std::string
的内容解释为C 字符串。它会说
嗯,第一个
\0
(终止空字符)之前的字符是ABC,所以我认为字符串是ABC
因此,它不会看到上述两者之间有任何区别。您可能遇到了这个问题:
std::stringstream s;
s << "hello";
s.seekp(0);
s << "b";
assert(s.str() == "b"); // will fail!
断言将失败,因为字符串流使用的序列仍然是包含“hello”的旧序列。你所做的只是覆盖第一个字符。你想这样做:
std::stringstream s;
s << "hello";
s.str(""); // reset the sequence
s << "b";
assert(s.str() == "b"); // will succeed!
另请阅读此答案:How to reuse an ostringstream
【讨论】:
【参考方案2】:std::ends
只是一个空字符。传统上,C 和 C++ 中的字符串以空(ascii 0)字符结尾,但事实证明 std::string
并不真正需要这个东西。无论如何,要逐点执行您的代码,我们会看到一些有趣的事情发生:
int main( int argc, char** argv )
字符串文字"hello"
是一个传统的以零结尾的字符串常量。我们将整个复制到std::string
HELLO。
const std::string HELLO( "hello" );
std::stringstream testStream;
我们现在将string
HELLO(包括尾随的0)放入stream
,然后是第二个空值,通过调用std::ends
将其放在那里。
testStream << HELLO << std::ends;
我们提取出我们放入stream
的内容的副本(文字字符串“hello”,加上两个空终止符)。
std::string hi = testStream.str();
然后我们使用std::string
类上的operator ==
比较两个字符串。此运算符(可能)比较 string
对象的长度 - 包括有多少尾随空字符。请注意,std::string
类不需要底层字符数组以尾随 null 结尾 - 换句话说,它允许字符串包含空字符,因此两个尾随空字符中的第一个被视为字符串 @987654337 的一部分@。
由于两个字符串的尾随空数不同,比较失败。
if( HELLO == hi )
std::cout << HELLO << "==" << hi << std::endl;
return 0;
虽然,如果打印出来,或者看过 在调试器(VS2005)中,HELLO 和 hi 看起来相同,它们的 .length() 在 事实相差 1。这就是我 猜测导致“==”运算符 失败。
原因是长度相差一个尾随空字符。
我的问题是为什么。我不 了解为什么 std::ends 是 添加到字符串的不可见字符 hi,让 hi 和 HELLO 不同 即使他们有长度 相同的内容。此外,这 隐形字符没有得到 用提升修剪修剪。然而,如果 你使用 strcmp 来比较 .c_str() 的 两个字符串,比较有效 正确。
strcmp
与 std::string
不同 - 它是在早期以 null 终止字符串时写入的 - 所以当它到达 hi
中的第一个尾随 null 时,它会停止查找。
我使用 std::ends 的原因 第一名是因为我有问题 过去与 stringstream 在结束时保留垃圾数据 流。 std::ends 解决了这个问题 我。
有时了解底层表示是个好主意。
【讨论】:
【参考方案3】:您正在使用 std::ends 向 HELLO 添加一个 NULL 字符。当您使用 str() 初始化 hi 时,您将删除 NULL 字符。琴弦不一样。 strcmp 不比较 std::strings,它比较 char*(它是一个 C 函数)。
【讨论】:
一定喜欢 *** - 在我写了 2 行答案的时候,有人写了战争与和平 :-) 看来我对 str() 的工作原理是错误的。回到我的绘图板上! steve-yegge.blogspot.com/2008/09/… ;) 这是一个很棒的博客!感谢您的链接! (尽管它主要是为 20 世纪男孩发布的):p【参考方案4】:std::ends 添加一个空终止符,(char)'\0'。您可以将它与已弃用的 strstream 类一起使用,以添加空终止符。
stringstream 不需要它,实际上它搞砸了,因为 null 终止符不是 stringstream 的“结束字符串的特殊 null 终止符”,对于 stringstream 它只是另一个字符,第零个字符. stringstream 只是添加它,这会将字符数(在您的情况下)增加到七个,并使与“hello”的比较失败。
【讨论】:
strstream
也不需要它。无论字符串是如何构建的,string::c_str()
总是正确地以 NUL 终止。【参考方案5】:
我认为有一个比较字符串的好方法是使用std::find
方法。不要将 C 方法和 std::string ones
混用!
【讨论】:
以上是关于为啥 std::ends 会导致字符串比较失败?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 TypeScript 中的方法链接会导致泛型类型推断失败?
为啥添加 @EnableAutoConfiguration 会导致 spring-boot 失败并显示“无法找到要扫描的 JPA 包”