一些容器没有找到功能

时间:2014-09-02 07:02:31

标签: c++ stl containers

某些STL容器(例如std::liststd::vector)没有find()方法作为成员函数。这是为什么?我知道可以使用std::find中的<algorithm>替代方案,但这种用法仍然不是100%自然。

7 个答案:

答案 0 :(得分:42)

一般设计原则是尽可能使用std::find,并在效率更高时实施find成员函数。

的容器具有find成员的容器具有更有效的元素查找机制,然后在std::find中执行线性搜索。例如,二元搜索树,例如std::setstd::map,或哈希表,例如unordered对应树。

答案 1 :(得分:13)

findlower_boundupper_bound成员函数仅在 更高效 时提供,而不是使用非成员等效项,或者当 非成员无法运行 时给定容器的公共API

请特别注意std::string有一个find函数,它提供std::find() - 就像字符搜索的线性搜索工具和std::search()一样 - 用于子字符串搜索的工具:虽然非成员版本可能具有相同的大O效率,但鉴于专用机器代码指令通常可用于“字符串”搜索,它们可能效率较低。还有历史,便利和易于移植的因素。

除了效率问题之外,值得注意的还有一些容器:

  • 本身就是排序的(multi - setmap)或未排序的(unordered_mapunordered_set),通常是未排序的(例如{ {1}}),或轻松(std::string

  • 公开支持前向迭代和/或随机访问

  • 可能私下支持二元搜索

  • 有一个专门用于元素访问的公共API,因此算法的潜在重用相对有限(例如std::vector / unordered_map::bucket等)

使用大量算法在::begin(n)中搜索也很有意思:

  • vector进行强力线性O(n)搜索,首先“找到”低级索引元素,

  • std::find需要一个已排序的向量,但会跳转以达到O(log2n)复杂度。

  • 其他选项如外推搜索和散列可能适用

您如何选择实施和添加成员?似乎有点武断。尽管如此,使用哪种选择在性能方面也很重要:对于一百万个元素,std::binary_search在匹配之前平均进行50万个元素比较,而在元素不存在时平均为百万个元素,而{{1通常需要进行~20次比较。

包含find的容器通常不具备此类灵活性,而他们提供的binary_search和/或find / find可视为非{ - 成员等价物,可能是搜索容器的唯一合理方式。

答案 2 :(得分:9)

因为来自std::findalgorithm功能适用于他们。

通常,std::vectorstd::list等容器具有线性搜索时间复杂度。因此附加到成员find功能是一种冗余,因为它已经std::find。对于其他容器(例如,std::setstd::map等),实现搜索的方式更好(即,比线性复杂度更快)。因此,实施者将这些更快的搜索算法实现为成员函数。

答案 3 :(得分:3)

具有按键搜索功能的容器将集成find方法(例如map,内部使用可以高效查找的二叉树实现)。

其他人,就像你引用的那些,允许使用std::find进行范围搜索,但不具备特色查找功能,因为它比std :: find具有没有算法优势(排序/特殊情况除外)

答案 4 :(得分:2)

对各种容器使用相同的功能可以获得更清晰的API,您不必了解每个容器的特性,只需要如何应用一个用于所有容器的功能。

它也用于代码可重用性 - 您使用的算法从任何提供它们的容器中获取迭代器,因此算法不必依赖于容器std::vectorstd::list等。

答案 5 :(得分:2)

std::vectorstd::liststd::forward_list和其他一些容器是连续容器。没有比顺序搜索更好的方法可以应用于这些容器了。因此,如果所有这些容器都相同,则无需重写每个顺序容器的顺序搜索。

异常是类std::basic_string,它最初模拟已经具有strchr,strstr等特殊搜索功能的C字符串。

答案 6 :(得分:0)

正如其他评论中所提到的,设计原理是vector::find()可以像非成员函数std::find()一样有效地实现。使用后者的好处是它将数据结构和操作符分离到数据结构上,从而提高了可维护性(这对于库的开发人员来说是有利的。)

然而,前者的好处是它会使所有容器之间的API保持一致,并使客户端代码更简洁。这将增加可学习性和可读性(这对于库的用户是有利的)。此外,一致的API将允许编写通用代码。考虑一下:

template <typename Container, typename T>
void foo(const Container& c, const T& x) {
    if (std::find(c.begin(), c.end(), x) != c.end()) {
        // ...
    }
}

Containerstd::mapstd::set时,上述效率低下。为了提高效率,我们需要这样做:

template <typename Container, typename T>
void foo(const Container& c, const T& x) {
    if (c.find(x) != c.end()) {
        // ...
    }
}

但是它不会为std::vectorstd::list编译。这会给库的用户带来负担,为他们想要支持的每种类型编写自己专用/重载的通用函数:

template <typename T>
bool contains(const std::vector<T>& c, const T& x) {
    return std::find(c.begin(), c.end(), x) != c.end();
}

template <typename T>
bool contains(const std::set<T>& c, const T& x) {
    return c.find(x) != c.end();
}

template <typename Container, typename T>
void foo(const Container& c, const T& x) {
    if (contains(c, x)) {
        // ...
    }
}

我承认做出这些类型的设计决策很难,但我认为STL的设计师在这里犯了一个错误。非常小的可维护性负担似乎非常值得用户提供更好的API和一致性。简而言之,由于find必须是某些容器的成员函数(性能),因此find应该是所有容器的成员函数(为了一致性)。请注意,我完全可以使用其他算法作为非成员函数。

(我的意思是,来吧,一个容器根据定义是包含东西的东西。用户编写一个通用且高效的“包含”函数应该是微不足道的。事实上,我认为它应该是添加到Container概念,但我离题了。)

相关问题