關聯容器中“相等”與“等價”的區別及注意事項

STL對於set/map這類關聯容器,涉及到插入和查找操作。由於set/map是有序的,插入(insert)操作就需要根據key的大小進行排序,實現中使用了operator < 用於比較key大小; 其成員函數find操作也是基於operator <來找到給定的元素。我們稱之爲“等價”。

而<alogrithm> 中的find函數則不然:因爲find算法針對的是普通的前向迭代器,並不要求元素有序,其基於operator == 來判斷元素是否相等。我們稱之爲"相等"

set容器的insert和find源碼分析

  • 插入(insert)操作的邏輯
    根據需要插入元素的key,在關聯容器中找到插入位置(基於operator操作比較元素大小)
    藉助clion IDE的跳轉功能,我們來看下函數調用棧
// set插入元素示例
std::set<std::string> test_set;
test_set.insert("hello");

以下爲std::set insert內部實現

pair<iterator,bool> insert(const value_type& __v)
{return __tree_.__insert_unique(__v);}
pair<iterator, bool> __insert_unique(const __container_value_type& __v) {
    return __emplace_unique_key_args(_NodeTypes::__get_key(__v), __v);
}
#ifndef _LIBCPP_CXX03_LANG
template <class _Tp, class _Compare, class _Allocator>
template <class _Key, class... _Args>
pair<typename __tree<_Tp, _Compare, _Allocator>::iterator, bool>
__tree<_Tp, _Compare, _Allocator>::__emplace_unique_key_args(_Key const& __k, _Args&&... __args)
#else
template <class _Tp, class _Compare, class _Allocator>
template <class _Key, class _Args>
pair<typename __tree<_Tp, _Compare, _Allocator>::iterator, bool>
__tree<_Tp, _Compare, _Allocator>::__emplace_unique_key_args(_Key const& __k, _Args& __args)
#endif
{
    __parent_pointer __parent;
    __node_base_pointer& __child = __find_equal(__parent, __k);
    __node_pointer __r = static_cast<__node_pointer>(__child);
    bool __inserted = false;
    if (__child == nullptr)
    {
#ifndef _LIBCPP_CXX03_LANG
        __node_holder __h = __construct_node(_VSTD::forward<_Args>(__args)...);
#else
        __node_holder __h = __construct_node(__args);
#endif
        __insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__h.get()));
        __r = __h.release();
        __inserted = true;
    }
    return pair<iterator, bool>(iterator(__r), __inserted);
}
// Find place to insert if __v doesn't exist
// Set __parent to parent of null leaf
// Return reference to null leaf
// If __v exists, set parent to node of __v and return reference to node of __v
template <class _Tp, class _Compare, class _Allocator>
template <class _Key>
typename __tree<_Tp, _Compare, _Allocator>::__node_base_pointer&
__tree<_Tp, _Compare, _Allocator>::__find_equal(__parent_pointer& __parent,
                                                const _Key& __v)
{
    __node_pointer __nd = __root();
    __node_base_pointer* __nd_ptr = __root_ptr();
    if (__nd != nullptr)
    {
        while (true)
        {
            if (value_comp()(__v, __nd->__value_))
            {
                if (__nd->__left_ != nullptr) {
                    __nd_ptr = _VSTD::addressof(__nd->__left_);
                    __nd = static_cast<__node_pointer>(__nd->__left_);
                } else {
                    __parent = static_cast<__parent_pointer>(__nd);
                    return __parent->__left_;
                }
            }
            else if (value_comp()(__nd->__value_, __v))
            {
                if (__nd->__right_ != nullptr) {
                    __nd_ptr = _VSTD::addressof(__nd->__right_);
                    __nd = static_cast<__node_pointer>(__nd->__right_);
                } else {
                    __parent = static_cast<__parent_pointer>(__nd);
                    return __nd->__right_;
                }
            }
            else
            {
                __parent = static_cast<__parent_pointer>(__nd);
                return *__nd_ptr;
            }
        }
    }
    __parent = static_cast<__parent_pointer>(__end_node());
    return __parent->__left_;
}

從這個調用棧可知,set容器插入元素時,是基於operator < 來比較key的大小,當 key1 < key2 爲false,且key2 < key1 爲false時,則認爲元素在容器中已經存在,這時insert操作將返回“等價”元素的位置和inserted爲false的一個flag。

  • 查找(find)操作邏輯
const_iterator find(const key_type& __k) const {return __tree_.find(__k);}
template <class _Tp, class _Compare, class _Allocator>
template <class _Key>
typename __tree<_Tp, _Compare, _Allocator>::const_iterator
__tree<_Tp, _Compare, _Allocator>::find(const _Key& __v) const
{
    const_iterator __p = __lower_bound(__v, __root(), __end_node());
    if (__p != end() && !value_comp()(__v, *__p))
        return __p;
    return end();
}

可以看到,set成員函數find也是基於operator <語義的。

std::find源碼

template <class _InputIterator, class _Tp>
inline _LIBCPP_INLINE_VISIBILITY
_InputIterator
find(_InputIterator __first, _InputIterator __last, const _Tp& __value_)
{
    for (; __first != __last; ++__first)
        if (*__first == __value_)
            break;
    return __first;
}

可以看到std::find是基於operator == 語義的。

自定義類型作爲關聯容器的key注意事項

  1. 需要實現一個函數對象,定義自定義類型key的比較操作,具體可以參考std::less

    struct _LIBCPP_TEMPLATE_VIS less : binary_function<_Tp, _Tp, 	bool>
    {
    	bool operator()(const _Tp& __x, const _Tp& __y) const
        	{return __x < __y;}
    };
    

    然後把這個作爲關聯容器的key_comp函數。

  2. 如果自定義類型含有指針變量的話,需要注意指針變量其實是一個字長整型數,可能需要取指針指向內容的值來做比較操作。

  3. 如果需要find元素的話,優先使用關聯容器內置的find操作,或者使用binary_search,如果使用std::find的話,記得重載operator == 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章