为什么s.find在失败时返回string :: npos而不是s.length()

时间:2013-11-26 20:46:15

标签: c++ string find

我最近很恼火地发现string::find在大海捞针中找不到针时返回string::npos。这使得以下看似优雅的代码编译但抛出超出范围的异常:

s.erase(s.find('#')); // erase everything after a # if one exists

如果find在失败时返回s.length(),则可以正常工作。相反,你必须做

auto pos = s.find('#');
if (pos != s.npos)
    s.erase(pos);

这也与std::find不一致,如果找不到该项,它将返回结束迭代器。

我知道标准人都非常聪明,所以我相信他们并不是偶然想出来的。它必须在其他地方给我一些优雅,我没有看到。这有什么好理由?

5 个答案:

答案 0 :(得分:9)

你的问题实际上是双重的:

  

1)为什么std::string拥有自己的find函数返回   std::size_t值而不是迭代器?

这主要是因为std::string是与标准库的其他部分分开开发的。只有在最近的标准中,它才被其他模板(例如iostream)所接受。因此,当它被添加到标准中时,它添加了一些函数,但它的原始功能几乎保持原样(例外是写入时复制的常见实现,这在C ++中被禁止) 11标准)。这样做主要是为了向后兼容。

根据你的问题,为什么它是这样开始的:原始string.h是一个非常薄的几个C字符串函数包装器。将strlen用作复制构造函数中使用的length()strcpy的返回值并不常见。没有强制使用这些函数的要求,因此最终实现者开始做一些有趣的事情(例如写时复制,非连续的内存块),但是它们保持接口相同以保留向后兼容性。在添加功能的同时,没有从界面中删除任何公共功能。因此,您可以跟踪设计决策,使用指针和函数参数的长度回到它只是C函数的包装器的日子。

  

2)如何在字符串上编写擦除序列而不必   检查返回值?

这可以通过使用find-erase惯用语来完成,但不能使用std::string的find函数:

s.erase(std::find(s.begin(), s.end(), '#'), s.end());

答案 1 :(得分:5)

使用std::string::npos使结果成为与std::string::length()不同的常量表达式。由于npos不适合作为迭代器,因此具有常量表达式是有价值的,例如,它可以用作采用std::string::size_type的参数的默认值。

另一个原因是在将STL添加到C ++标准库之前将std::basic_string的基本接口放在一起(至少,存在接口的一部分)。原始接口基本上是一个不可变的字符串,我认为它不支持字符串本身的任何变异。

答案 2 :(得分:2)

我不确定:原始的std :: sting(STL)没有要求连续存储数据。因此,在操作失败时返回size()将是开销(如果未存储大小)。在c ++ 11中,字符串是连续的,我同意你的批评。

答案 3 :(得分:0)

如果你喜欢std::find的行为,你应该使用它,因为std::string是一个容器:

s.erase( std::find( s.begin(), s.end(), '#' ), s.end() );

更改s.find()行为以返回s.length()可以使这个特殊情况更加优雅,但会产生其他问题。我认为更好的解决方案是让std :: string :: erase()接受std :: string :: npos作为第一个参数,什么都不做。

答案 4 :(得分:0)

问题是std :: basic_string的许多成员函数都使用默认参数。这使它们更容易使用。例如,考虑以下构造函数

basic_string(const basic_string& str, size_type pos, size_type n = npos,
const Allocator& a = Allocator());

您可以为第三个参数n指定什么默认参数? C ++标准不允许使用非静态成员作为默认参数:

  

同样,默认情况下不得使用非静态成员   参数,即使它没有被评估,除非它看起来像   类成员访问表达式(5.2.5)的id-expression或除非它   用于形成指向成员的指针

所以npos是更方便的默认参数。