关联容器中“相等”与“等价”的区别及注意事项

本文探讨了STL中关联容器如set/map的insert和find操作,这两个操作基于operator<实现有序性。insert通过比较key大小找到插入位置,而find同样依赖于key的比较。同时,介绍了std::find算法使用operator==来判断元素相等。自定义类型作为关联容器key时,需提供比较操作,并注意处理指针变量的情况,确保正确比较。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 == 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值