STL源码之红黑树

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组;红黑树是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。

红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能;它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

红黑树的性质

  1. 每个节点非红即黑;
  2. 根节点是黑的;
  3. 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
  4. 如图所示,如果一个节点是红的,那么它的两儿子都是黑的;
  5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点。

_Rb_tree的结构图

_Rb_tree的结点结构

_Rb_tree_node的实现如下,几个实现也很简单,在_Rb_tree_node中定义了结点数据域,在基类_Rb_tree_node_base中分别定义了left、right、parent,另外还有一个表示颜色的标记常量。

另外在_Rb_tree_node_base实现了获取最大值与最小值的方法,根据红黑树的性质,获取最大值只需要在右子树上一直搜索,最小值在左子树上一直搜索。

  template<typename _Val>struct _Rb_tree_node : public _Rb_tree_node_base{typedef _Rb_tree_node<_Val>* _Link_type;#if __cplusplus < 201103L_Val _M_value_field;_Val*_M_valptr(){ return std::__addressof(_M_value_field); }const _Val*_M_valptr() const{ return std::__addressof(_M_value_field); }
#else__gnu_cxx::__aligned_buffer<_Val> _M_storage;_Val*_M_valptr(){ return _M_storage._M_ptr(); }const _Val*_M_valptr() const{ return _M_storage._M_ptr(); }
#endif};

_Rb_tree_node_base的实现如下:

 enum _Rb_tree_color { _S_red = false, _S_black = true };struct _Rb_tree_node_base{typedef _Rb_tree_node_base* _Base_ptr;typedef const _Rb_tree_node_base* _Const_Base_ptr;_Rb_tree_color  _M_color;_Base_ptr      _M_parent;_Base_ptr     _M_left;_Base_ptr       _M_right;static _Base_ptr_S_minimum(_Base_ptr __x) _GLIBCXX_NOEXCEPT{while (__x->_M_left != 0) __x = __x->_M_left;return __x;}static _Const_Base_ptr_S_minimum(_Const_Base_ptr __x) _GLIBCXX_NOEXCEPT{while (__x->_M_left != 0) __x = __x->_M_left;return __x;}static _Base_ptr_S_maximum(_Base_ptr __x) _GLIBCXX_NOEXCEPT{while (__x->_M_right != 0) __x = __x->_M_right;return __x;}static _Const_Base_ptr_S_maximum(_Const_Base_ptr __x) _GLIBCXX_NOEXCEPT{while (__x->_M_right != 0) __x = __x->_M_right;return __x;}};

_Rb_tree的迭代器

_Rb_tree的迭代器首先包含了实现萃取机制的一些typedef,当然实现了一些操作符重载,主要来看下++ 和 -- 的重载。

  template<typename _Tp>struct _Rb_tree_iterator{typedef _Tp  value_type;typedef _Tp& reference;typedef _Tp* pointer;typedef bidirectional_iterator_tag iterator_category;typedef ptrdiff_t                  difference_type;typedef _Rb_tree_iterator<_Tp>        _Self;typedef _Rb_tree_node_base::_Base_ptr _Base_ptr;typedef _Rb_tree_node<_Tp>*           _Link_type;_Rb_tree_iterator() _GLIBCXX_NOEXCEPT: _M_node() { }explicit_Rb_tree_iterator(_Link_type __x) _GLIBCXX_NOEXCEPT: _M_node(__x) { }referenceoperator*() const _GLIBCXX_NOEXCEPT{ return *static_cast<_Link_type>(_M_node)->_M_valptr(); }pointeroperator->() const _GLIBCXX_NOEXCEPT{ return static_cast<_Link_type> (_M_node)->_M_valptr(); }_Self&operator++() _GLIBCXX_NOEXCEPT{_M_node = _Rb_tree_increment(_M_node);return *this;}_Selfoperator++(int) _GLIBCXX_NOEXCEPT{_Self __tmp = *this;_M_node = _Rb_tree_increment(_M_node);return __tmp;}_Self&operator--() _GLIBCXX_NOEXCEPT{_M_node = _Rb_tree_decrement(_M_node);return *this;}_Selfoperator--(int) _GLIBCXX_NOEXCEPT{_Self __tmp = *this;_M_node = _Rb_tree_decrement(_M_node);return __tmp;}booloperator==(const _Self& __x) const _GLIBCXX_NOEXCEPT{ return _M_node == __x._M_node; }booloperator!=(const _Self& __x) const _GLIBCXX_NOEXCEPT{ return _M_node != __x._M_node; }_Base_ptr _M_node;};

前++的重载

_Self&
operator++() _GLIBCXX_NOEXCEPT
{_M_node = _Rb_tree_increment(_M_node);return *this;
}

前++的实现是通过_Rb_tree_increment(libstdc++-v3\src\c++98\tree.cc)来完成,其实现如下:

  _Rb_tree_node_base*_Rb_tree_increment(_Rb_tree_node_base* __x) throw (){return local_Rb_tree_increment(__x);}
 static _Rb_tree_node_base*local_Rb_tree_increment(_Rb_tree_node_base* __x) throw (){
/*
如果当前结点有右子节点,那么右子树的最左结点就是++所需的结点
*/if (__x->_M_right != 0) {__x = __x->_M_right;while (__x->_M_left != 0)__x = __x->_M_left;}else {
/*
1. 如果当前结点是左子树结点,那么++的结果就是当前结点的父结点
2. 如果当前结点是右子树结点,那么++的结果就是父结点的父结点
*/_Rb_tree_node_base* __y = __x->_M_parent;while (__x == __y->_M_right) {__x = __y;__y = __y->_M_parent;}if (__x->_M_right != __y)__x = __y;}return __x;}

前--的重载

_Self&
operator--() _GLIBCXX_NOEXCEPT
{_M_node = _Rb_tree_decrement(_M_node);return *this;
}

_Rb_tree_decrement的实现:

  _Rb_tree_node_base*_Rb_tree_decrement(_Rb_tree_node_base* __x) throw (){return local_Rb_tree_decrement(__x);}
static _Rb_tree_node_base*local_Rb_tree_decrement(_Rb_tree_node_base* __x) throw (){
/*
从上面的结构图,我们知道,header是红色,并且parent为root,而root的parent为header
*/if (__x->_M_color == _S_red && __x->_M_parent->_M_parent == __x)__x = __x->_M_right;else if (__x->_M_left != 0) {/*左子树的最大值*/_Rb_tree_node_base* __y = __x->_M_left;while (__y->_M_right != 0)__y = __y->_M_right;__x = __y;}else {_Rb_tree_node_base* __y = __x->_M_parent;while (__x == __y->_M_left) {__x = __y;__y = __y->_M_parent;}__x = __y;}return __x;}

_Rb_tree的成员构成

通过上述的类结构图可知,_Rb_tree 的具体实现是在 _Rb_tree_impl中,主要定义了三个成员变量:

  • _Key_compare        _M_key_compare;
  • _Rb_tree_node_base     _M_header;
  • size_type         _M_node_count;

_Rb_tree_impl还负责红黑树的初始化操作与内存管理。

template<typename _Key_compare, bool _Is_pod_comparator = __is_pod(_Key_compare)>struct _Rb_tree_impl : public _Node_allocator{_Key_compare       _M_key_compare;_Rb_tree_node_base   _M_header;size_type         _M_node_count; // Keeps track of size of tree._Rb_tree_impl(): _Node_allocator(), _M_key_compare(), _M_header(),_M_node_count(0){ _M_initialize(); }_Rb_tree_impl(const _Key_compare& __comp, const _Node_allocator& __a): _Node_allocator(__a), _M_key_compare(__comp), _M_header(),_M_node_count(0){ _M_initialize(); }#if __cplusplus >= 201103L_Rb_tree_impl(const _Key_compare& __comp, _Node_allocator&& __a): _Node_allocator(std::move(__a)), _M_key_compare(__comp),_M_header(), _M_node_count(0){ _M_initialize(); }
#endifprivate:void_M_initialize(){this->_M_header._M_color = _S_red;this->_M_header._M_parent = 0;this->_M_header._M_left = &this->_M_header;this->_M_header._M_right = &this->_M_header;}        };

_Rb_tree的内存管理

通过获取_M_impl的构造器来完成节点的创建和销毁,这里和我们之前看到的是一致的,内存分配和构造是分开的,在_M_create_node中,先通过构造器去分配,然后通过construct来构造。

  _Node_allocator&_M_get_Node_allocator() _GLIBCXX_NOEXCEPT{ return *static_cast<_Node_allocator*>(&this->_M_impl); }const _Node_allocator&_M_get_Node_allocator() const _GLIBCXX_NOEXCEPT{ return *static_cast<const _Node_allocator*>(&this->_M_impl); }allocator_typeget_allocator() const _GLIBCXX_NOEXCEPT{ return allocator_type(_M_get_Node_allocator()); }protected:_Link_type_M_get_node(){ return _Alloc_traits::allocate(_M_get_Node_allocator(), 1); }void_M_put_node(_Link_type __p) _GLIBCXX_NOEXCEPT{ _Alloc_traits::deallocate(_M_get_Node_allocator(), __p, 1); }#if __cplusplus < 201103L_Link_type_M_create_node(const value_type& __x){_Link_type __tmp = _M_get_node();__try{ get_allocator().construct(__tmp->_M_valptr(), __x); }__catch(...){_M_put_node(__tmp);__throw_exception_again;}return __tmp;}void_M_destroy_node(_Link_type __p){get_allocator().destroy(__p->_M_valptr());_M_put_node(__p);}
#elsetemplate<typename... _Args>_Link_type_M_create_node(_Args&&... __args){_Link_type __tmp = _M_get_node();__try{::new(__tmp) _Rb_tree_node<_Val>;_Alloc_traits::construct(_M_get_Node_allocator(),__tmp->_M_valptr(),std::forward<_Args>(__args)...);}__catch(...){_M_put_node(__tmp);__throw_exception_again;}return __tmp;}void_M_destroy_node(_Link_type __p) noexcept{_Alloc_traits::destroy(_M_get_Node_allocator(), __p->_M_valptr());__p->~_Rb_tree_node<_Val>();_M_put_node(__p);}
#endif_Link_type_M_clone_node(_Const_Link_type __x){_Link_type __tmp = _M_create_node(*__x->_M_valptr());__tmp->_M_color = __x->_M_color;__tmp->_M_left = 0;__tmp->_M_right = 0;return __tmp;}

_Rb_tree的元素操作

在看源码之前先看下以下几个接口:

      _Link_type_M_begin() _GLIBCXX_NOEXCEPT{ return static_cast<_Link_type>(this->_M_impl._M_header._M_parent); }_Const_Link_type_M_begin() const _GLIBCXX_NOEXCEPT{return static_cast<_Const_Link_type>(this->_M_impl._M_header._M_parent);}_Link_type_M_end() _GLIBCXX_NOEXCEPT{ return static_cast<_Link_type>(&this->_M_impl._M_header); }_Const_Link_type_M_end() const _GLIBCXX_NOEXCEPT{ return static_cast<_Const_Link_type>(&this->_M_impl._M_header); }

_M_insert_equal的元素插入操作

  template<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>
#if __cplusplus >= 201103Ltemplate<typename _Arg>
#endiftypename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L_M_insert_equal(_Arg&& __v)
#else_M_insert_equal(const _Val& __v)
#endif{pair<_Base_ptr, _Base_ptr> __res= _M_get_insert_equal_pos(_KeyOfValue()(__v));return _M_insert_(__res.first, __res.second, _GLIBCXX_FORWARD(_Arg, __v));}

上述代码中调用了_M_get_insert_equal_pos,其中的实现是从根节点开始,往下寻找适当的插入点:

  template<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,_Compare, _Alloc>::_Base_ptr,typename _Rb_tree<_Key, _Val, _KeyOfValue,_Compare, _Alloc>::_Base_ptr>_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_get_insert_equal_pos(const key_type& __k){typedef pair<_Base_ptr, _Base_ptr> _Res;_Link_type __x = _M_begin();_Link_type __y = _M_end();while (__x != 0)//寻找合适的插入点{__y = __x;__x = _M_impl._M_key_compare(__k, _S_key(__x)) ?_S_left(__x) : _S_right(__x);//遇大往左,遇小往右}return _Res(__x, __y);//x为插入点,y为插入点的父结点}

_M_insert_unique的元素插入操作

  template<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>
#if __cplusplus >= 201103Ltemplate<typename _Arg>
#endifpair<typename _Rb_tree<_Key, _Val, _KeyOfValue,_Compare, _Alloc>::iterator, bool>_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L_M_insert_unique(_Arg&& __v)
#else_M_insert_unique(const _Val& __v)
#endif{typedef pair<iterator, bool> _Res;pair<_Base_ptr, _Base_ptr> __res= _M_get_insert_unique_pos(_KeyOfValue()(__v));if (__res.second)return _Res(_M_insert_(__res.first, __res.second,_GLIBCXX_FORWARD(_Arg, __v)),true);return _Res(iterator(static_cast<_Link_type>(__res.first)), false);}

看下_M_get_insert_unique_pos的主要实现:

1. 插入新值,不允许重复,若重复插入无效
2. 返回值是个pair:第一个元素是rb-tree迭代器指向新增结点;第二个表示成功与否

 template<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,_Compare, _Alloc>::_Base_ptr,typename _Rb_tree<_Key, _Val, _KeyOfValue,_Compare, _Alloc>::_Base_ptr>_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_get_insert_unique_pos(const key_type& __k){typedef pair<_Base_ptr, _Base_ptr> _Res;_Link_type __x = _M_begin();_Link_type __y = _M_end();bool __comp = true;while (__x != 0)//从根节点开始,寻找合适的插入点{__y = __x;__comp = _M_impl._M_key_compare(__k, _S_key(__x));__x = __comp ? _S_left(__x) : _S_right(__x);}iterator __j = iterator(__y);//指向插入点的父结点if (__comp)//comp为true,说明插在左侧{if (__j == begin())//插入结点的父结点为最左侧结点return _Res(__x, __y);else--__j;}
//新键值不与既有结点重复,于是执行安插if (_M_impl._M_key_compare(_S_key(__j._M_node), __k))return _Res(__x, __y);return _Res(__j._M_node, 0);}

_M_insert_的插入操作

__x,__p,__v分别为插入点,插入点父结点,以及新值。

 template<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>
#if __cplusplus >= 201103Ltemplate<typename _Arg>
#endiftypename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L_M_insert_(_Base_ptr __x, _Base_ptr __p, _Arg&& __v)
#else_M_insert_(_Base_ptr __x, _Base_ptr __p, const _Val& __v)
#endif{bool __insert_left = (__x != 0 || __p == _M_end()|| _M_impl._M_key_compare(_KeyOfValue()(__v),_S_key(__p)));_Link_type __z = _M_create_node(_GLIBCXX_FORWARD(_Arg, __v));_Rb_tree_insert_and_rebalance(__insert_left, __z, __p,this->_M_impl._M_header);++_M_impl._M_node_count;return iterator(__z);}

_Rb_tree_insert_and_rebalance这个函数才是实现的重点,在这里会更新插入结点之后的leftmost、rightmost的更新;以及如果不满足红黑树的性质,就进行调整。

在这里需要结合:数据结构与算法——红黑树(Red Black Tree)中的几种插入情况。

  void _Rb_tree_insert_and_rebalance(const bool          __insert_left,_Rb_tree_node_base* __x,_Rb_tree_node_base* __p,_Rb_tree_node_base& __header) throw (){_Rb_tree_node_base *& __root = __header._M_parent;// Initialize fields in new node to insert.__x->_M_parent = __p;__x->_M_left = 0;__x->_M_right = 0;__x->_M_color = _S_red;// Insert.// Make new node child of parent and maintain root, leftmost and// rightmost nodes.// N.B. First node is always inserted left.if (__insert_left){__p->_M_left = __x; // also makes leftmost = __x when __p == &__headerif (__p == &__header){__header._M_parent = __x;__header._M_right = __x;}else if (__p == __header._M_left)__header._M_left = __x; // maintain leftmost pointing to min node}else{__p->_M_right = __x;if (__p == __header._M_right)__header._M_right = __x; // maintain rightmost pointing to max node}// Rebalance.while (__x != __root && __x->_M_parent->_M_color == _S_red) //父结点为红{_Rb_tree_node_base* const __xpp = __x->_M_parent->_M_parent;//获取祖父结点if (__x->_M_parent == __xpp->_M_left) //父结点为祖父结点的左子结点{_Rb_tree_node_base* const __y = __xpp->_M_right;if (__y && __y->_M_color == _S_red) //对应状况1{__x->_M_parent->_M_color = _S_black;__y->_M_color = _S_black;__xpp->_M_color = _S_red;__x = __xpp;}else {//无伯父结点if (__x == __x->_M_parent->_M_right) //新结点为父结点的右子结点,对应状况2{__x = __x->_M_parent;local_Rb_tree_rotate_left(__x, __root);}__x->_M_parent->_M_color = _S_black;//经过左旋之后,对应状况3,需要再做一次右旋__xpp->_M_color = _S_red;local_Rb_tree_rotate_right(__xpp, __root);}}else {_Rb_tree_node_base* const __y = __xpp->_M_left;if (__y && __y->_M_color == _S_red) //类似于状况1,只不过为祖父结点的右子结点{__x->_M_parent->_M_color = _S_black;__y->_M_color = _S_black;__xpp->_M_color = _S_red;__x = __xpp;}else {if (__x == __x->_M_parent->_M_left) //类似于2,3,只不过是做相反的旋转{__x = __x->_M_parent;local_Rb_tree_rotate_right(__x, __root);}__x->_M_parent->_M_color = _S_black;__xpp->_M_color = _S_red;local_Rb_tree_rotate_left(__xpp, __root);}}}__root->_M_color = _S_black;}

状况1:

此时将当前结点的父结点和叔叔节点涂黑,祖父结点涂红;并把当前结点指向祖父结点,从新的当前结点重新开始计算。

状况2:

当前结点的父结点作为新的当前结点,以新当前结点为支点进行左旋。

状况3:

此时将父结点变为黑色,祖父结点变为红色,祖父结点作为支点进行右旋。

右旋的实现local_Rb_tree_rotate_right:

  static void local_Rb_tree_rotate_right(_Rb_tree_node_base* const __x, _Rb_tree_node_base*& __root){_Rb_tree_node_base* const __y = __x->_M_left;__x->_M_left = __y->_M_right;if (__y->_M_right != 0)__y->_M_right->_M_parent = __x;__y->_M_parent = __x->_M_parent;if (__x == __root)__root = __y;else if (__x == __x->_M_parent->_M_right)__x->_M_parent->_M_right = __y;else__x->_M_parent->_M_left = __y;__y->_M_right = __x;__x->_M_parent = __y;}

左旋的实现local_Rb_tree_rotate_left:

  static void local_Rb_tree_rotate_left(_Rb_tree_node_base* const __x, _Rb_tree_node_base*& __root){_Rb_tree_node_base* const __y = __x->_M_right;__x->_M_right = __y->_M_left;if (__y->_M_left !=0)__y->_M_left->_M_parent = __x;__y->_M_parent = __x->_M_parent;if (__x == __root)__root = __y;else if (__x == __x->_M_parent->_M_left)__x->_M_parent->_M_left = __y;else__x->_M_parent->_M_right = __y;__y->_M_left = __x;__x->_M_parent = __y;}

以上参考:

  • STL源码剖析
  • https://www.cnblogs.com/newobjectcc/p/11293689.html
  • https://www.bilibili.com/video/BV1hE41147tP?from=search&seid=10787162310474444819

C++进阶——STL源码之红黑树(_Rb_tree)相关推荐

  1. STL源码剖析---红黑树原理详解下

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7760584       算法导论书上给出的红黑树的性质如下,跟STL源码 ...

  2. STL源码剖析---红黑树原理详解上

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7740956 一.红黑树概述 红黑树和我们以前学过的AVL树类似,都是在进 ...

  3. STL源码剖析---红黑树原理详解

    红黑树概述 红黑树都是在进行插入和删除时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能.红黑树追求的时局部平衡而不是AVL树中的非常严格的平衡. 所谓红黑树,不仅是一个二叉搜索树,而且必须满 ...

  4. HashMap、ConcurrentHashMap(1.7、1.8)源码分析 + 红黑树

    个人博客欢迎访问 总结不易,如果对你有帮助,请点赞关注支持一下 微信搜索程序dunk,关注公众号,获取博客源码 序号 内容 1 Java基础面试题 2 JVM面试题 3 Java并发编程面试 4 计算 ...

  5. STL源码剖析 关联式容器 红黑树

    概念 红黑树不仅仅是一个二叉树,必须满足如下条件 1,每个节点不是红色就是黑色 (深色底纹为黑色,浅色底纹为红色) 2,根节点是黑色的 3,如果节点为红,其子节点必须为黑色的 4,任一节点至NULL( ...

  6. STL的红与黑--rb_tree红黑树

    红黑树,作为一种广泛使用的数据结构,我想大家应该都不会陌生. 谈到红黑树的用途,最广为人知的应该就是红黑树在C++ STL中的应用了,在set, multiset, map, multimap等中,都 ...

  7. STL源码剖析 map

    所有元素会根据元素的键值自动被排序 元素的类型是pair,同时拥有键值和实值:map不允许两个元素出现相同的键值 pair 代码 template <class T1,class T2> ...

  8. 《STL源码剖析》相关面试题总结

    一.STL简介 STL提供六大组件,彼此可以组合套用: 容器 容器就是各种数据结构,我就不多说,看看下面这张图回忆一下就好了,从实现角度看,STL容器是一种class template. 算法 各种常 ...

  9. STL源码剖析(十三)关联式容器之rb_tree

    STL源码剖析(十三)关联式容器之rb_tree 文章目录 STL源码剖析(十三)关联式容器之rb_tree 一.rb_tree的数据结构 二.rb_tree的迭代器 三.rb_tree的操作 3.1 ...

最新文章

  1. VC++动态链接库(DLL)编程(四)――MFC扩展 DLL
  2. dart系列之:dart中的异步编程
  3. Python二叉树遍历
  4. sudo apt-get常用命令
  5. 无线(互联网)+有线(内网)上外网设置
  6. Clojure 学习入门(16)- 正则表达式
  7. python怎么替换主干网络_无法将关键字“model”解析到字段中。活塞主干网.js
  8. Linux chmod命令 修改文件权限被禁止(not permitted)的解决办法
  9. 正点原子STM32F4笔记
  10. 复旦新生计算机考试及格率,复旦大学本科新生《计算机办公自动化》课程入学考试考核大.doc...
  11. try-catch-finally中的4个大坑,老程序员也搞不定
  12. linux搭建stm32开发环境
  13. localhost:8080
  14. 性能之巅:常用性能分析方法
  15. NIOS II 内核使用 之 代码保存FLASH(EPCSX芯片)
  16. 应用层加密方_加密应用层数据之前要问的6个问题
  17. Python获取文件的行数和某一行的内容
  18. 软件系统复杂性灾难及解决方案探究
  19. QQ自动强制加好友代码html
  20. php购物车面试题,PHP 购物车 session(非框架)

热门文章

  1. lol国服服务器最新人口排名,2020lol大区人数排名,lol服务器人数
  2. 关于移动视频直播技术,关键干货都在这里了(三)编码和封装
  3. iOS Charts库绘制曲线
  4. android游戏和ios游戏哪个多,Android游戏类App占27.1% 与iOS差异显著
  5. Android 自定义注解处理器详解
  6. 常用的arm汇编指令(3) -学无止尽,积土成山,积水成渊
  7. AttributeError: module 'torch.nn.init' has no attribute 'zeros_'
  8. 常用的十种算法:二分查找,分治,动态规划,KMP
  9. 临时解决新款macbookpro m1pro刘海屏遮住部分菜单栏应用的问题
  10. Sketchup 程序自动化(二)Ruby 基础、单位转换