我最近很恼火地发现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
不一致,如果找不到该项,它将返回结束迭代器。
我知道标准人都非常聪明,所以我相信他们并不是偶然想出来的。它必须在其他地方给我一些优雅,我没有看到。这有什么好理由?
答案 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是更方便的默认参数。