为什么std::string.find(text,std::string:npos)不返回npos?

3

我正在对一个字符串进行一系列搜索,但在搜索过程中会有一个字符串被漏掉,导致我的搜索失败。

我原本以为一旦位置达到std::string::npos就会停止搜索,但实际上并不是这样。将std::string::npos传递给std::string.find似乎会重新从头开始搜索。

std::string str("frederick");
std::string::size_type pos = str.find("der",std::string::npos);
TS_ASSERT_EQUALS(pos, std::string::npos); // FAIL, 3 is returned

为什么它没有被认为是字符串的结尾?

更新: 意图是按顺序搜索一系列字符串,并在末尾检查结果。

pos = str.find(string1, pos)
pos = str.find(string2, pos)
pos = str.find(string3, pos)
if (pos != std:string::npos)
{ // All strings found

1
将-1(npos)作为开始查找子字符串的位置传入有什么意义? - Gishu
@Gishu 我已经添加了一些示例代码。 - David Sykes
2
我用g++尝试了你的代码,发现find确实返回了npos。我认为Charles Bailey是正确的。 - mweerden
1
MSVC2005 返回 npos,Xcode 3.1.2 和 2.4.1 则没有返回。 - David Sykes
7个回答

10

看规格说明,我认为你的实现可能有一个 bug。

basic_string::find 应该返回最低位置 xpos,使得 pos <= xposxpos + str.size() <= size() 并且 at(xpos + I) == str.at(I) 对于 str 控制的所有元素 I 均成立。

basic_string::npos 是 -1 转换为无符号类型后的值,因此必须是该无符号类型可表示的最大数。鉴于没有其他位置 xpos 可以满足甚至第一部分 npos <= xpos,并且在失败时 find 必须返回 npos,据我所见,当传递 npos 作为第二个参数调用 basic_string::find 时,npos 是唯一有效的返回值。


4
比较string::find()和string::copy()。(在N2798中,这是21.3.7.2和21.3.6.7,页面686/687)两者都需要一个位置参数。但只有string::copy有一个"Requires: pos <= size()"条款。因此,string::find不需要pos <= size()。
从那时起,Charles Bailey就有了正确的逻辑。看一下有效返回值的范围,很明显,只有与要求匹配的唯一返回值是string::npos。任何其他返回值都小于string::npos,未通过21.3.7.2/1测试。
从N2798=08-0308开始,版权ISO/IEC:
21.3.7.2 basic_string::find [string::find]
size_type find(const basic_string<charT,traits,Allocator>& str, size_type pos = 0) const;
1 Effects: 确定最低位置xpos(如果可能),使得满足以下两个条件: — pos <= xpos,且 xpos + str.size() <= size(); — traits::eq(at(xpos+I), str.at(I))对于由str控制的字符串的所有元素I。 2 Returns: 如果函数可以确定这样的值,则返回xpos。否则,返回npos。 3 Remarks: 使用traits::eq()。

3
你可能会发现,对于这种情况,免费函数std::search更容易使用。例如:
std::string::const_iterator iter = str.begin();

iter = std::search( iter, str.end(), string1.begin(), string1.end() );
iter = std::search( iter, str.end(), string2.begin(), string2.end() );
iter = std::search( iter, str.end(), string3.begin(), string3.end() );

3

std::string::npos不是std::string::find的有效参数。

标准中find的定义只提到了npos作为可能的返回值,而不是起始位置。


1
标准在哪里规定了这一点?我只发现 npos 的类型是 basic_string::size_type,并且我找不到对于此重载的 find 函数第二个参数允许值范围的任何限制。 - CB Bailey
@Charles:在这里http://www.cplusplus.com/reference/string/string/find/中写道:“要考虑可能匹配的字符串中第一个字符的位置。值为0表示整个字符串都被考虑在内。”根据这份文档,我甚至不会尝试传递npos值,因为行为可能会受到实现的影响。 - Daniel Daranas
1
但标准对于find应该做什么更加清晰,并且对于第二个参数的任何值都没有限制其允许的行为。 - CB Bailey
1
同意Charles的观点。cplusplus.com并没有定义C++库。 - MSalters
1
@Charles:好的,所以标准允许该参数的任何值。那么你是正确的,David正在使用的实现在这里有一个错误。我保留我的上面的评论,以便讨论完整性,尽管我不再持有它 :) - Daniel Daranas

1

如果传递npos,则行为是未定义的:

[更新]
STL文档(我能找到的两个版本)仅将string :: npos 提及为可能的返回值,而不是pos 的有效值。后者是搜索开始的索引。

但是,请参见下面的评论(我对ISO标准不是专家,我根据拥有的文档来限制我的期望)。

STL实现通常会使用明显超出范围的值(例如((size_type)-1)。如何处理此参数未清楚说明,因此我不依赖于该行为。 [/更新]

因此,您需要从0开始,并在每次调用find之后检查pos!= npos

 pos = str.find(string1, 0)
 if (pos != std:string::npos)
   pos = str.find(string2, pos)
 if (pos != std:string::npos)
   pos = str.find(string3, pos)

 if (pos != std:string::npos)
 { 
   // All strings found
 }

2
当您传递npos时,行为为什么是未定义的? - CB Bailey
2
但标准不必明确提及当传递 string::npos 时会发生什么。string::npos 是 string::size_type 的有效值,且输入范围没有明确的限制。据我所见,对于第二个参数的所有值,string::find 的行为描述仍然得到了很好的规定。 - CB Bailey
1
请注意,STL文档(唯一的ISO文档)实际上确实说明了哪些参数值是有效的,如果这是一个子集。请参阅前一页的basic_string :: copy()。//此外,任何STL实现都不能使用负值作为npos,因为它的类型是无符号的(以及某种整数类型)。 - MSalters
好观点,MSalters。我已经删除了上面的相应句子。不过,如果起始索引不在字符串限制内,我不会期望返回npos的返回值 - 除非在查找文档中明确说明,或者在文档约定中隐含说明。 - peterchen
1
如果元素之间没有相等,那么返回 npos 是符合自然的 "Effects:" 部分的。这个函数返回的是通过 .at() 指定的元素,而不是更自然的 operator[],因此很明显超出范围的索引不会触发未定义行为。 - MSalters

0
将std::string :: npos作为find的第二个参数传递意味着“从字符串中std::string :: npos位置开始查找”。
显然,这不是您想要的。
编辑:
这可能会实现您最初想要的内容:
string s;
string::size_type pos;

if ((pos = s.find(s1)) != string::npos && (pos = s.find(s2, pos)) != npos && 
    (pos = s.find(s3,pos)) != string::npos)
{
    // okay
}

我还没有测试过,但应该可以工作,你可能更喜欢 peterchen 的风格,因为它更易读。


0

你应该使用字符串的长度作为起始位置。


我想使用上一个查找的返回值,而不必每次都检查它。 - David Sykes

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