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注意事项
-
需要实现一个函数对象,定义自定义类型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函数。
-
如果自定义类型含有指针变量的话,需要注意指针变量其实是一个字长整型数,可能需要取指针指向内容的值来做比较操作。
-
如果需要find元素的话,优先使用关联容器内置的find操作,或者使用binary_search,如果使用std::find的话,记得重载operator == 。