C++ STL:容器 Container

news2025/1/16 3:03:29

文章目录

    • 1、序列容器
      • 1.1、容器共性
      • 1.2、vector
        • vector 结构
        • * vector 扩容原理
        • * vector 迭代器失效
      • 1.3、deque
        • deque 结构
        • deque 迭代器
        • deque 模拟连续空间
      • 1.4、list
        • list 特殊操作
        • list 结构
        • list 迭代器
    • 2、关联式容器
      • 2.1、容器共性
      • 2.2、容器特性
    • 3、无序关联式容器
      • 3.1、容器共性
      • 3.2、容器特性

模板类的集合,内部封装组织数据的方法,也就是数据结构

作用:存放数据

分类:

  • 序列式容器 :线性
  • 关联式容器:key-value 集合,红黑树实现
  • 无序关联容器:hash 实现

1、序列容器

序列式容器

  • array:固定大小的数组。支持随机访问迭代器。
  • vector:动态数组。支持随机访问迭代器。
  • deque:双端队列。支持随机访问迭代器。
  • list:双向链表。只支持双向访问迭代器。
  • forward_list:单链表。只支持前向访问迭代器

序列式容器使用总结,当下列操作频繁发生时

  • 在容器中间位置添加删除元素,list
  • 在容器头部位置添加删除元素,deque / list
  • 在容器尾部位置添加删除元素,vector / deque / list
  • 访问容器任意位置上的元素,vector

1.1、容器共性

初始化

// 1、默认构造函数,空容器
容器();
// 2、构造拥有 count 个有值 value 的元素的容器。
容器(size_type count,const T& value = T(),const Allocator& alloc = Allocator());
// 3、构造拥有范围 [first, last) 内容的容器。
容器(InputIt first, InputIt last, const Allocator& alloc = Allocator());

赋值操作

// 1、重载等号操作符
operator=
// 2、assign
void assign(InputIt first, InputIt last); 	  // 以范围 [first, last) 中元素的副本替换内容。
void assign(size_type count, const T& value); // 将 count 个 value 的副本替换内容。

元素访问

at		    // 访问指定元素,同时越界检查
operator[]	// 访问指定元素 
front 		// 访问第一个元素
back		// 访问最后一个元素

容量

// 检查容器是否无元素
bool empty() const; 
// 容器中元素数量
size_type size() const;

修改器

// 1. 删除容器中所有元素
void clear();

// 2. 插入元素
// 在 pos 前插入 value
iterator insert(const_iterator pos, T&& value);  
// 在 pos 前插入 value 的 count 个副本
iterator insert(const_iterator pos, size_type count, const T& value); 
// 原地构造元素,并将参数args转发给构造函数
iterator emplace(const_iterator pos, Args&&... args);

// 3. 容器末尾添加元素
void push_back(const T& value);
// 容器末尾就地构造元素
void emplace_back(Args&&... args);

// 4. 移除末尾元素
void pop_back();

// 5. 删除元素
// 移除位于 pos 的元素。
iterator erase(iterator pos);
// 移除范围 [first, last) 中的元素。
iterator erase(iterator first, iterator last);   

// 6. 改变容器中可存储元素的个数
void resize(size_type count);
void resize(size_type count, T value = T());

// 7. 交换容器内容
void swap(deque& other);

1.2、vector

  • 动态数组
  • 支持随机访问迭代器
  • 逻辑机构和物理结构一致,连续线性空间,空间不足时,自动扩容

vector 扩容

  • 申请新空间:两倍扩容
  • 移动数据
  • 释放原空间

vector 结构

vector 使用三个指针维护动态数组 start, finish, end_of_storage

在这里插入图片描述

源码如下:

template <class _Tp, class _Alloc> 
class _Vector_base {
protected:
  _Tp* _M_start;	      // 指向使用空间的第一个元素	
  _Tp* _M_finish;	      // 指向使用空间的最后一个元素的下一个位置
  _Tp* _M_end_of_storage; // 指向可用空间的最后一个位置的下一个位置
};

通过三个指针可以实现一些基本接口

iterator begin() { return _M_start; }
iterator end() { return _M_finish; }

size_type size() const { return size_type(end() - begin()); }
size_type capacity() const { return size_type(_M_end_of_storage - begin()); }
bool empty() const { return begin() == end(); }

reference operator[](size_type __n) { return *(begin() + __n); }

reference front() { return *begin(); }
reference back() { return *(end() - 1); }

* vector 扩容原理

vector 扩容

  • 申请新空间:两倍扩容。
  • 拷贝旧空间的元素到新空间:新空间前半段存放旧数据,后半段存放新插入的数据。
  • 释放原空间。

注意:初始化时,数组空间大小为 0,扩容后,新空间大小为 1。此后,都是两倍扩容操作。

添加元素时,先检测空间是否够用

  • size != capacity,在待插入位置构造新元素
  • size == capacity,则进行 vector 扩容
void push_back() {
    // 检测是否有可用空间
    // 空间足够
    if (_M_finish != _M_end_of_storage) {
        construct(_M_finish);
        ++_M_finish;
    }
    // 空间不够,申请新的空间
    else
        _M_insert_aux(end());
}

空间足够,在待插入位置构造新元素

template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
  new ((void*) __p) _T1(__value); // 定位new表达式:在 _p 位置上构造一个对象
}

template <class _T1, class _T2>
inline void construct(_T1* __p, const _T2& __value) {
  _Construct(__p, __value);
}

空间不够,扩容操作

template <class _Tp, class _Alloc>
void vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
  // 检测是否有可用空间,该函数会被其他函数调用,所以需要再次检查
  if (_M_finish != _M_end_of_storage) {
    construct(_M_finish, *(_M_finish - 1));
    ++_M_finish;
    _Tp __x_copy = __x;
    copy_backward(__position, _M_finish - 2, _M_finish - 1);
    *__position = __x_copy;
  }
  else {
    const size_type __old_size = size();
    // 1、申请新空间:若旧空间大小为 0(初始化),则新空间大小为 1;否则,新空间为旧空间大小的 2 倍
    const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
    iterator __new_start = _M_allocate(__len);
    iterator __new_finish = __new_start;
    // 2、将旧空间的元素复制到新空间
    // push_back 操作前半段用来存放旧数据,后半段用来存放新数据
    __STL_TRY {
      // 将旧数据拷贝到新空间
      __new_finish = uninitialized_copy(_M_start, __position, __new_start);
      // 为新元素设定初值
      construct(__new_finish, __x);
      // 调整水位
      ++__new_finish;
      // 拷贝插入点后的旧数据(insert操作也可能导致扩容)
      __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
    }
    // 3、释放旧空间
    __STL_UNWIND((destroy(__new_start,__new_finish), 
                  _M_deallocate(__new_start,__len)));
    destroy(begin(), end());
    _M_deallocate(_M_start, _M_end_of_storage - _M_start);
    _M_start = __new_start;
    _M_finish = __new_finish;
    _M_end_of_storage = __new_start + __len;
  }
}

* vector 迭代器失效

vector 支持随机访问迭代器,普通指针天生具备。

typedef _Tp value_type;
typedef value_type* iterator;

插入元素

  • 尾部插入:
    • size < capacity,尾迭代器失效
    • size == capacity,所有迭代器失效(动态数组扩容,重新分配空间)
  • 中间插入:
    • size <= capacity,插入位置后的所有迭代器失效
    • size == capacity,所有迭代器失效(动态数组扩容,重新分配空间)

例:在遍历容器元素的过程中,添加元素的操作很危险。一旦发生扩容操作,申请新的存储空间,而迭代器仍指向原空间,导致越界。

删除元素

  • 尾部删除:尾迭代器失效
  • 中间删除:删除位置后的所有迭代器失效(元素前移)

例:删除 vector 中值为 val 的所有元素。下面是容易导致错误的做法

// 错误写法:删除后,元素前移,当前删除位置后的所有迭代器失效
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
    if (val == *it) {
        numbers.erase(it);
    } 
}

错误的原因在于:

  • 删除连续相同元素,删除后元素前移,跳过第二个要删除的元素
  • 删除最后一个元素,删除后元素前移,迭代器指向 end() 的下一个位置,发生越界

正确的写法

for (auto it = vec.begin(); it != vec.end();) {  
    if (val == *it) { 
        // 删除元素后,不能执行 ++操作
        it = vec.erase(it); 
    } else {
        //未删除元素,执行++操作
        ++it; 
    } 
}

1.3、deque

  • 双端队列
  • 支持随机访问迭代器
  • 逻辑结构,连续空间,双端开口,首尾插入移除元素 O(1)
  • 物理离散,物理结构由多个片段构成,片段内部连续,片段间不连续,片段的控制由中控器完成。

修改器接口

// 容器头部插入元素
void push_front( const T& value );
// 容器头部就地构造元素
void emplace_front( Args&&... args );
// 移除容器头部元素
void pop_front();

deque 结构

物理结构由多个片段构成,片段内部连续,片段之间不连续,片段的控制由中控器完成。

deque 组成

  • 中控器:map 数组,其中的每个元素(node 节点)是一个指针,指向一块较大的连续空间(缓冲区)。
  • 缓冲区:buffer 缓冲区,实际存放数据的区域

在这里插入图片描述

源码如下:

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class deque : protected _Deque_base<_Tp, _Alloc> {
    
protected: 
  typedef pointer* _Map_pointer; // 二级指针
  static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }

protected:
  using _Base::_M_map;		// 连续空间,每个元素是一个指针,指向一个 buffer
  using _Base::_M_map_size;	// 容纳的指针数量
  using _Base::_M_start;	// 哨兵,指向 buffer 起始位置
  using _Base::_M_finish;	// 哨兵,指向 buffer 结束位置

缓冲区 buffer 大小默认 512

// 返回每个缓冲区可容纳元素数量
inline size_t __deque_buf_size(size_t __size) {
  return __size < 512 ? size_t(512 / __size) : size_t(1);
}

deque 迭代器

deque 逻辑连续,体现在 operator++ 和 operator–运算。deque 迭代器需要判断缓冲区位置,其次当迭代器处于缓冲区边缘,为了能够正确跳跃至上一个或下一个缓冲区,需要中控器的支持。

dueque 迭代器组成

  • cur:指向 buffer 的当前元素
  • fast:哨兵,指向 buffer 起始位置
  • last:哨兵,指向 buffer 结束位置
  • node:指向中控器

在这里插入图片描述

源码如下

template <class _Tp, class _Ref, class _Ptr>
struct _Deque_iterator {
  // 支持迭代器
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef size_t size_type;
  
  typedef ptrdiff_t difference_type;
  typedef _Tp** _Map_pointer;

  typedef _Deque_iterator _Self;
  typedef _Tp** _Map_pointer; // 二级指针,指向 buffer

  _Tp* _M_cur;   // 指向 buffer 的当前元素
  _Tp* _M_first; // 哨兵,指向 buffer 起始位置
  _Tp* _M_last;  // 哨兵,指向 buffer 结束位置
  _Map_pointer _M_node; // 中控器,二级指针
  ...
};

deque 插入元素,首先判断插入元素在 deque 的位置

  iterator insert(iterator position, const value_type& __x) {
    // 1、若插入位置是头部
    if (position._M_cur == _M_start._M_cur) {
      push_front(__x);
      return _M_start;
    }
    // 2、若插入位置是尾部
    else if (position._M_cur == _M_finish._M_cur) {
      push_back(__x);
      iterator __tmp = _M_finish;
      --__tmp;
      return __tmp;
    }
    // 3、若插入位置在中间
    else {
      return _M_insert_aux(position, __x);
    }
  }

在 deque 中间位置插入,需要移动元素,为了减少移动元素次数,这里有一个巧妙的设计,判断插入点前和插入点后的元素数量,移动元素数量少的一端。

template <class _Tp, class _Alloc>
typename deque<_Tp, _Alloc>::iterator
deque<_Tp,_Alloc>::_M_insert_aux(iterator __pos, const value_type& __x)
{
  // 插入点前元素个数
  difference_type __index = __pos - _M_start;
  value_type __x_copy = __x;
  // 比较插入点前的元素数量与插入点后的元素数量
  // 选择元素较少的一端移动元素
  // 1、若插入点前元素个数较少
  if (size_type(__index) < this->size() / 2) {
    // 头端插入与第一个元素值相同的元素
    push_front(front());
    iterator __front1 = _M_start;
    ++__front1;
    iterator __front2 = __front1;
    ++__front2;
    __pos = _M_start + __index;
    iterator __pos1 = __pos;
    ++__pos1;
    // 元素前移
    copy(__front2, __pos1, __front1);
  }
  // 2、若插入点后元素个数较少
  else {
    // 尾端插入与最后一个元素值相同的元素
    push_back(back());
    iterator __back1 = _M_finish;
    --__back1;
    iterator __back2 = __back1;
    --__back2;
    __pos = _M_start + __index;
    copy_backward(__pos, __back2, __back1);
  }
  // 插入元素
  *__pos = __x_copy;
  return __pos;
}

deque 模拟连续空间

deque 迭代器模拟连续空间

reference operator[](size_type __n) { return _M_start[difference_type(__n)]; }

reference front() { return *_M_start; }

reference back() {
    iterator __tmp = _M_finish;
    --__tmp;
    return *__tmp;
}

size_type size() const { return _M_finish - _M_start; }

bool empty() const { return _M_finish == _M_start; }

**operator ***

reference operator*() const { return *_M_cur; }

set_node

实现 node 节点 (buffer) 跳转

void _M_set_node(_Map_pointer __new_node) {
    _M_node = __new_node;
    _M_first = *__new_node;
    _M_last = _M_first + difference_type(_S_buffer_size());
}

difference_type operator-

两个迭代器的距离 = 两个迭代器间的 buffers 总长度 + 起始 buffer 长度 + 末尾(当前)buffer 长度

  • 两个迭代器间的 buffers 长度:bufferSize * 首尾 buffer 间的 buffers 数量,-1 是排除起始边界 buffer
  • 起始 buffer 的元素数量:__x._M_last - __x._M_cur
  • 末尾 buffer 的元素数量:_M_cur - _M_first
difference_type operator-(const _Self& __x) const {
    // 两个迭代器间的 buffers 总长度 + 起始 buffer 长度 + 末尾(当前)buffer 长度
    return difference_type(_S_buffer_size()) * (_M_node - __x._M_node - 1) +
        (_M_cur - _M_first) + (__x._M_last - __x._M_cur);
}

operator++

// 前置++
_Self& operator++() {
    ++_M_cur;
    // 判断是否到了 buffer 边界(终点)
    if (_M_cur == _M_last) {
        // 跳转至下一个node节点(buffer)的起点
        _M_set_node(_M_node + 1);
        _M_cur = _M_first;
    }
    return *this; 
}

// 后置++
_Self operator++(int)  {
    _Self __tmp = *this;
    // 前置++实现后置++
    ++*this;
    return __tmp;
}

operator–

// 前置--
_Self& operator--() {
    // 判断是否到了 buffer 边界(起点)
    if (_M_cur == _M_first) {
        // 跳转至前一个node节点(buffer)的终点
        _M_set_node(_M_node - 1);
        _M_cur = _M_last;
    }
    --_M_cur;
    return *this;
}

// 后置--
_Self operator--(int) {
    _Self __tmp = *this;
    // 前置--实现后置--
    --*this;
    return __tmp;
}

operator+=

_Self& operator+=(difference_type __n) {
    difference_type __offset = __n + (_M_cur - _M_first);
    // 目标位置在同一 buffer 内
    if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))
        _M_cur += __n;
    // 目标位置不在同一 buffer 内
    else {
        difference_type __node_offset =
            __offset > 0 ? __offset / difference_type(_S_buffer_size())
            : -difference_type((-__offset - 1) / _S_buffer_size()) - 1;
        // 切换到目标 node 节点(buffer)
        _M_set_node(_M_node + __node_offset);
        // 切换至正确元素
        _M_cur = _M_first + 
            (__offset - __node_offset * difference_type(_S_buffer_size()));
    }
    return *this;
  }

operator+

_Self operator+(difference_type __n) const {
    _Self __tmp = *this;
    // 内部调用 += 实现
    return __tmp += __n;
}

operator-=

内部调用 +=,偏移量 -n

// 内部调用 += -n
_Self& operator-=(difference_type __n) { return *this += -__n; }

operator-

内部调用 -=

_Self operator-(difference_type __n) const {
    _Self __tmp = *this;
    return __tmp -= __n;
}

1.4、list

  • list 双向链表
  • 支持双向访问迭代器
  • 物理结构:循环双向链表
  • 逻辑结构:双向链表

list 特殊操作

// 1、从一个链表转移元素到另一个链表
// 移动一个链表到另一个链表的某个指定位置
void splice(const_iterator pos, list& other);
void splice(const_iterator pos, list&& other)
// 移动一个链表中的某个元素到另一个链表的某个指定位置
void splice(const_iterator pos, list& other, const_iterator it)
void splice(const_iterator pos, list&& other, const_iterator it);
// 移动一对迭代器范围元素到另一个链表的某个指定位置
void splice(const_iterator pos, list& other, const_iterator first, const_iterator last)
void splice(const_iterator pos, list&& other,const_iterator first, const_iterator last)

// 2、对元素进行排序
void sort();
template< class Compare > void sort(Compare comp);
template <typename T>
struct Compare {
    bool operator()(const T &a, const T &b) const {
        return a < b;
    }
}; 

// 3、合并两个已排序的链表
void merge(list& other);

// 4、将该链表的所有元素的顺序反转
void reverse();

// 5、移除满足特定标准的元素
void remove(const T& value);
void remove_if(UnaryPredicate p)

// 6、删除连续的重复元素
void unique();

list 结构

list 通过 node 指针可以实现遍历循环链表,尾端留有空白节点,符合左闭右开,成为 last 迭代器。

在这里插入图片描述

list node

  • prev:指向前一个节点
  • next:指向下一个节点
  • data:存储数据

在这里插入图片描述

struct _List_node_base {
  _List_node_base* _M_next;
  _List_node_base* _M_prev;
};

template <class _Tp>
struct _List_node : public _List_node_base {
  _Tp _M_data;
};

list 迭代器

template<class _Tp, class _Ref, class _Ptr>
struct _List_iterator : public _List_iterator_base {
  // 1、typedef
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _List_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
  typedef _List_iterator<_Tp,_Ref,_Ptr>             _Self;

  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef _List_node<_Tp> _Node;
    
  // 2、运算符重载
  ...
};  

运算符重载

reference operator*() const { return ((_Node*) _M_node)->_M_data; }
 pointer operator->() const { return &(operator*()); }

void _M_incr() { _M_node = _M_node->_M_next; }
void _M_decr() { _M_node = _M_node->_M_prev; }

_Self& operator++() { 
    this->_M_incr();
    return *this;
}
_Self operator++(int) { 
    _Self __tmp = *this;	// 先调用重载=,在调用重载*,因此这里调用的是拷贝构造
    this->_M_incr();
    return __tmp;
}
_Self& operator--() { 
    this->_M_decr();
    return *this;
}
_Self operator--(int) { 
    _Self __tmp = *this;
    this->_M_decr();
    return __tmp;
}

2、关联式容器

2.1、容器共性

容器共性

  • 底层实现:红黑树,查找元素时间复杂度O(logN)
  • 默认情况下,按照关键字 key 升序排列
  • 不能修改关键字 key 的值
  • 支持双向访问迭代器

面试:红黑树的特征

  • 节点是红色或黑色
  • 根节点必须是黑色
  • 叶子节点都是黑色
  • 红色节点的孩子节点必须是黑色的。黑色节点的孩子节点可以是黑色的。
  • 从根节点到叶子节点的所有路径的黑色节点的个数相同

扩展:从根节点到叶子节点的路径上不存在连续的红色节点

  • 最短路径,节点都是黑色的;
  • 最长路径,红黑相间,2 * 黑色节点数 - 1,红色节点数 = 黑色节点数 - 1

2.2、容器特性

容器分类:set / map,key 唯一;multiset / multimap,key 不唯一。

set

  • 存放 key 的有序集合
  • 插入操作:insert,返回std::pair<iterator, bool>
  • 查找操作,count / find
  • 删除操作:erase

map

  • 存放键值对,pair<const key, value>。可以使用std::pairstd::make_pair存放元素。
  • 支持下标访问运算符,注意:查询时对应的 key 若不存在,会直接创建该 key 的记录

3、无序关联式容器

3.1、容器共性

  • 底层实现:哈希表(桶 + 单链表)
  • 存放的元素是无序的
  • 针对自定义类型:必须自定义 std::hashstd::equal_to

面试:hash table

在这里插入图片描述

  • 哈希冲突:不同的 key,散列到相同的地址,即 H(key1) == H(key2)
  • 哈希冲突解决方法:链地址法(STL)、开放定址法、再散列法
  • 装载因子 = 实际装载数据的长度 n / 哈希表长 m,当负载因子过大时,rehash

3.2、容器特性

  • unordered_set

  • unordered_map

  • unordered_multiset

  • unordered_multimap

例:自定义 point 类型,无法使用无序关联式容器。

定义std::hash

  • 自定义函数对象
  • 扩展std::hash的模板特化版本
  • 函数调用运算符设计成 const 版本
// 1、自定义哈希函数
// 1.1、扩展 std::hash 的模板特化版本
namespace std {
template <>
// 1.2、自定义函数对象
struct hash<Point> {
    // 1.3、函数调用运算符设计成 const 版本
	size_t operator()(const Point & rhs) const {
         // 自定义哈希函数
		return (rhs.getX() << 1) ^ (rhs.getY() << 1);
	}
};

定义 std::equal_to:重载函数调用运算符,或重载等号运算符,或使用模板特化

bool operator() (const T &lhs, const T &rhs) const {
	return lhs == rhs;
}

bool operator== (const T &lhs, const T &rhs) {
	return lhs == rhs;
}

namespace std {
template <>
struct equal_to<Point> {
	bool operator()(const Point &lhs, const Point &rhs) const {
		return (lhs.getX() == rhs.getX()) && (lhs.getY() == rhs.
	}
};
}//end of namespace std

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/379109.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【剑指offer】JZ5 替换空格、JZ6 从尾到头打印链表

目录 JZ5 替换空格 思路&#xff1a; 解题步骤&#xff1a; JZ6 从尾到头打印链表 思路&#xff1a; 解题步骤 JZ5 替换空格 描述&#xff1a; 请实现一个函数&#xff0c;将一个字符串s中的每个空格替换成“%20”。 例如&#xff0c;当字符串为We Are Happy.则经过替换之…

LibAFL的安装及基本使用

本教程安装LibAFL使用的是Ubuntu 22.04 操作系统 1. 安装 1.1 Rust 安装 Rust的安装&#xff0c;参照Rust官网&#xff1a;https://www.rust-lang.org/tools/install curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh1.2 LLVM安装 直接apt安装&#xff0c;安…

Day903.自增主键不能保证连续递增 -MySQL实战

自增主键不能保证连续递增 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于自增主键不能保证连续递增的内容。 MySql保证了主键是自增&#xff0c;但不相对连续&#xff1b;帮助开发人员快速识别每个行的唯一性&#xff0c;并提高查询效率。 自增主键可以让主键索引…

2023上半年北京/上海/广州/深圳NPDP产品经理认证报名

产品经理国际资格认证NPDP是国际公认的唯一的新产品开发专业认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年…

【GUI】Robo 3T(Studio 3T Free) for Mongodb安装与使用教程

下载 robo 3T现已更名为studio 3T free&#xff0c;官网即可下载 studio 3T free下载地址 安装 mac电脑下载的是dmg安装包&#xff0c;直接正常安装即可&#xff0c;windows电脑也是一样的&#xff0c;不需要配置环境&#xff0c;安装即可使用。&#xff08;前提是你已经安装…

【C++】二叉搜索树的模拟实现

一、概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值它的左右子树也分别…

Python---time模块

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;Python在学&#xff0c;希望能够得到各位的支持&#xff01;&#xff01;&#xff01; time模块前言时间戳time.time()将时间戳转换成字符串time.ctime()将时间戳转换为元组time.localtime(时间戳)将元…

大四、非计算机专业,Python该怎么学?

我是非计算机专业&#xff0c;大四时开始学Python&#xff0c;用了大概一个半月时间&#xff0c;现在从事数据挖掘工作&#xff0c;算是有点发言权。虽然之前学了点C&#xff0c;但仅仅是皮毛&#xff0c;为了应付考试&#xff0c;所以我基本是零基础开始学Python的。 总结学习…

Stress压力工具的部署及使用

Stress压力工具的部署及使用 下载地址&#xff1a;wget https://fossies.org/linux/privat/old/stress-1.0.5.tar.gz 1.部署 进入目录执行./autogen.sh [rootiZ2ze1pj93eyq389c2ppi5Z stress-1.0.5]# ./autogen.sh ps&#xff1a;如果执行过程中缺包&#xff0c;安装对应的…

运维自动化——Ansible

一&#xff1a;ansible命令执行过程 1. 加载自己的配置文件 默认/etc/ansible/ansible.cfg 2. 加载自己对应的模块文件&#xff0c;如command 3. 通过ansible将模块或命令生成对应的临时py文件&#xff0c; 并将该文件传输至远程服务器的对应执行用户 $HOME…

leetcode 51~60 学习经历

leetcode 51~60 学习经历51. N 皇后52. N 皇后 II53. 最大子数组和54. 螺旋矩阵55. 跳跃游戏56. 合并区间57. 插入区间58. 最后一个单词的长度59. 螺旋矩阵 II60. 排列序列小结51. N 皇后 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子…

【MobileNet】MobileNet V1

MobileNet V11、简介2、Depthwise Separable Convolution1&#xff09;Depthwise Separable Convolution 的优点2&#xff09;Depthwise Separable Convolution 网络结构3&#xff09;pytorch 函数 实现 depth-wise convolution2、Mobile 网络结构pytorch实现 Mobile 网络结构&…

hiveSQL开窗函数详解

hive开窗函数 文章目录hive开窗函数1. 开窗函数概述1.1 窗口函数分类1.2 窗口函数和普通聚合函数的区别2. 窗口函数的基本用法2.1 基本用法2.2 设置窗口的方法2.2.1 window_name2.2.2 partition by2.2.3 order by 子句2.2.4 rows指定窗口大小窗口框架2.3 开窗函数中加 order by…

Linux下使用Makefile实现条件编译

在Linux系统下Makefile和C/C语言都有提供条件选择编译的语法&#xff0c;就是在编译源码的时候&#xff0c;可以选择性地编译指定的代码。这种条件选择编译的使用场合有好多&#xff0c;例如我们开发一个兼容标准版本与定制版本兼容的项目&#xff0c;那么&#xff0c;一些与需…

[数据结构]:07-二叉树(无头结点)(C语言实现)

目录 前言 已完成内容 二叉树实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-QueueFunction.cpp 04-TreeFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代码。使用C引用主要是为了…

金三银四,助力你的大厂梦,2023年软件测试经典面试真题(3)(共3篇)

前言 金三银四即将到来&#xff0c;相信很多小伙伴要面临面试&#xff0c;一直想着说分享一些软件测试的面试题&#xff0c;这段时间做了一些收集和整理&#xff0c;下面共有三篇经典面试题&#xff0c;大家可以试着做一下&#xff0c;答案附在后面&#xff0c;希望能帮助到大…

【软件测试】从0到1的突破,appium自动化测试你真的会吗?自动化测试思路总结......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 总结一下APP UI自动…

超店有数分享:tiktok数据分析工具推荐,助你成功出海!

现阶段的跨境电商人都纷纷入局tiktok&#xff0c;这是风口也是发展趋势。Tiktok的下载量已经超过了35亿&#xff0c;每月都有10亿用户活跃&#xff0c;在154国家/地区使用。Tiktok用户每天在平均花1小时左右进行浏览&#xff0c;打开率也很高。如今&#xff0c;tiktok也越来越成…

酷雷曼VR丨十大“高含金量”荣誉,一起见证!

VR全景领域 十大“高含金量”荣誉 高光时刻 一同见证 01、双高新技术企业 同时获得国家高新技术企业、中关村高新技术企业双认证&#xff0c;是对酷雷曼企业研究开发组织管理水平、科技成果转化能力、自主知识产权数量、销售与总资产成长性等多维度实力的综合体现。 双高…

【MinIO】文件断点续传和分块合并

【MinIO】文件断点续传和分块合并 文章目录【MinIO】文件断点续传和分块合并0. 准备工作1. 检查文件是否存在1.1 定义接口1.2 编写实现方法2. 检查分块文件是否存在2.1 定义接口2.2 编写实现方法3. 上传分块文件接口3.1 定义接口3.2 编写实现方法4. 合并分块文件接口4.1 定义接…