C++ STL容器(三) —— 迭代器底层剖析

news2024/11/14 22:20:37

本篇聚焦于STL中的迭代器,同样基于MSVC源码。


文章目录

  • 迭代器模式
    • 应用场景
    • 实现方式
    • 优缺点
  • UML类图
  • 代码解析
    • list 迭代器
      • const 迭代器
      • 非 const 迭代器
    • vector 迭代器
      • const 迭代器
      • 非const迭代器
    • 反向迭代器
  • 迭代器失效
  • 参考资料


迭代器模式

首先迭代器模式是设计模式中的一种,属于行为型设计模式,简单的迭代器模式结构如下。

应用场景

迭代器模式适合的应用场景有:

  1. 当集合背后为复杂的数据结构,且你希望对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),可以使用迭代器模式。
    迭代器封装了与复杂数据结构进行交互的细节,为客户端提供多个访问集合元素的简单方法,即具体迭代器内部实现对用户是透明的,用户不用去了解针对每个容器要怎么迭代,只需要用迭代器的简单方法就能通杀。这种方式不仅对客户端来说非常方便,而且能避免客户端在直接与集合交互时执行错误或有害的操作,从而起到保护集合的作用。
  2. 使用该模式可以减少程序中重复的遍历代码。
    重要迭代算法的代码往往体积非常庞大。当这些代码被放置在程序业务逻辑中时,它会让原始代码的职责模糊不清,降低其可维护性。因此,将遍历代码移到迭代器中可使程序代码更加精炼和简洁。
  3. 如果你希望代码能够遍历不同的甚至时无法预知的数据结构,可以使用迭代器模式。
    该模式为集合和迭代器提供了一些通用接口。如果你在代码中使用了这些接口,那么将其他实现了这些接口的集合和迭代器传递给它时,它仍将可以正常运行。

实现方式

  1. 声明迭代器接口。该接口必须提供至少一个方法来获取集合中的下个元素。但为了使用方便,可以添加一些其他方法,例如获取前一个元素、记录当前位置和判断迭代是否结束。(后面也可以看到MSVC中的实现提供了不止一个方法)
  2. 声明集合接口并描述一个获取迭代器的方法,其返回值必须是迭代器接口。如果可能有多组不同的迭代器,可以声明多个类似的方法(比如STL中的容器可以有 begincbegin()rbegin() 等多种获取不同迭代器的方法)。
  3. 为希望使用迭代器进行遍历的集合实现具体迭代器类。迭代器对象必须与单个集合实体链接。链接关系通常通过迭代器的构造函数建立。如STL中的不同容器,有各自不同的迭代器,后面就能看到。
  4. 在你的集合类中实现集合接口。其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。这也好理解,迭代器既然想迭代,肯定要持有对应的容器/集合,那么在什么时候给这个迭代器,最好当然是在构造的时候就给迭代器了。
  5. 检查客户端代码,使用迭代器替代所有集合遍历代码。每当客户端需要遍历集合元素时都会获取一个迭代器。

优缺点

优点:

  • 单一职责原则:通过将体积庞大的遍历算法代码抽取为独立的类,可对客户端代码和集合进行整理。
  • 开闭原则:可实现新型的集合和迭代器并将其传给现有代码,无需修改现有代码。
  • 可以并行遍历同一集合,因为每个迭代器对象都包含其自身的遍历状态(读操作并行,写操作还是要同步)。
  • 可以暂停遍历,并在需要时继续。

缺点:

  • 如果你的程序只与简单的集合进行交互,应用该模式可能会事倍功半。
  • 对于某些特殊集合,使用迭代器可能比直接遍历的效率低。

UML类图

下面整理了MSVC中迭代器实现的相关类图,这里暂时先考虑 listvector 两个容器的迭代器。

  1. 可以看到 list 的迭代器和 vector 的迭代器,都有一个公共的基类 _Iterator_base
  2. 这里 list 有 unchecked 和 checked 两种迭代器,checked 迭代器用于 Debug 版本下的运行时错误排查,比如访问越界等。这里包括后面暂时不考虑这块代码,这块具体可以看官方文档的解释。
  3. reverse_iterator 反向迭代器是通过持有正向迭代器,然后重载相关的一些方法来实现逆向迭代的。
  4. const迭代器是非const迭代器的父类,这个也好理解,毕竟只是一个是只读,一个是可读写的,当然在功能上就是包含关系。

代码解析

下面主要分析下 listvector 这两个容器的迭代器以及反向迭代器的实现。

list 迭代器

const 迭代器

  1. 构造函数,第一部分有提到,我们需要把容器和迭代器建立链接,这块就是放在构造函数中做,这里 _Adopt 就是用在 Debug 下的错误检查,在 Release 下是一个空操作。
    _List_unchecked_const_iterator() noexcept : _Ptr() {}

    _List_unchecked_const_iterator(_Nodeptr _Pnode, const _Mylist* _Plist) noexcept : _Ptr(_Pnode) {
        this->_Adopt(_Plist);
    }
  1. 获取指向元素,重载 operator* 因为是链表,返回就是保存在每个节点中的值的引用,这里因为是 const 迭代器,其 reference 等于 const value_type&,如果保存的值是一个结构体/类,也可以用 -> 的方式访问其成员,这里 **this 第一个 * 获取到迭代器本身,第二个 * 调用了 operator* 获取到 _Ptr->_Myval,而 pointer_to 相当于是一个取址,我们就得到一个指向 _Ptr->_Myval 的指针。
    _NODISCARD reference operator*() const noexcept {
        return _Ptr->_Myval;
    }
	
	    _NODISCARD pointer operator->() const noexcept {
        return pointer_traits<pointer>::pointer_to(**this);
    }
  1. 向后迭代,这里有前置 ++ 和后置 ++ 前置的话就是返回自己的引用,后置的是返回迭代前的副本。
    _List_unchecked_const_iterator& operator++() noexcept {
        _Ptr = _Ptr->_Next;
        return *this;
    }

    _List_unchecked_const_iterator operator++(int) noexcept {
        _List_unchecked_const_iterator _Tmp = *this;
        _Ptr                                = _Ptr->_Next;
        return _Tmp;
    }
  1. 向前迭代list 的迭代器是一个双向的迭代器,所以也支持向前迭代,那么其实就和向后迭代实现差不多了。
    _List_unchecked_const_iterator& operator--() noexcept {
        _Ptr = _Ptr->_Prev;
        return *this;
    }

    _List_unchecked_const_iterator operator--(int) noexcept {
        _List_unchecked_const_iterator _Tmp = *this;
        _Ptr                                = _Ptr->_Prev;
        return _Tmp;
    }
  1. 迭代器判等,我们在用迭代器遍历容器的过程经常需要判断当前迭代器是否和 end 迭代器相等,来判断遍历是否结束,因此需要在迭代器中实现判断与另一个迭代器是否相等的方法,对于 list 容器就是简单看迭代器持有的指针是否相等。
    _NODISCARD bool operator==(const _List_unchecked_const_iterator& _Right) const noexcept {
        return _Ptr == _Right._Ptr;
    }

非 const 迭代器

非 const 迭代器,其实和 const 迭代器的实现没啥区别,里面也调用了 const 迭代器的方法,除了我们获取内部元素时候,const 迭代器返回的是一个常量引用/常量指针,而非 const 当然应该返回非 const,具体内部也是通过一个 const_cast 来做。

    _NODISCARD reference operator*() const noexcept {
        return const_cast<reference>(_Mybase::operator*());
    }

    _NODISCARD pointer operator->() const noexcept {
        return pointer_traits<pointer>::pointer_to(**this);
    }

    _List_unchecked_iterator& operator++() noexcept {
        _Mybase::operator++();
        return *this;
    }

    _List_unchecked_iterator operator++(int) noexcept {
        _List_unchecked_iterator _Tmp = *this;
        _Mybase::operator++();
        return _Tmp;
    }

    _List_unchecked_iterator& operator--() noexcept {
        _Mybase::operator--();
        return *this;
    }

    _List_unchecked_iterator operator--(int) noexcept {
        _List_unchecked_iterator _Tmp = *this;
        _Mybase::operator--();
        return _Tmp;
    }

vector 迭代器

const 迭代器

首先对于构造函数、取元素、前向遍历和后向遍历都和 list 的迭代器差不多(因为随机迭代器的功能肯定包含双向迭代器),除了内部实现有点差别(因为 vector 容器是一块连续的内存,所以用一个指针而不是节点就能找到/遍历这个容器,而指针本身就支持 ++ --)。

    _CONSTEXPR20 _Vector_const_iterator() noexcept : _Ptr() {}

    _CONSTEXPR20 _Vector_const_iterator(_Tptr _Parg, const _Container_base* _Pvector) noexcept : _Ptr(_Parg) {
        this->_Adopt(_Pvector);
    }

    _NODISCARD _CONSTEXPR20 reference operator*() const noexcept {
        return *_Ptr;
    }

    _NODISCARD _CONSTEXPR20 pointer operator->() const noexcept {
        return _Ptr;
    }

    _CONSTEXPR20 _Vector_const_iterator& operator++() noexcept {
        ++_Ptr;
        return *this;
    }

    _CONSTEXPR20 _Vector_const_iterator operator++(int) noexcept {
        _Vector_const_iterator _Tmp = *this;
        ++*this;
        return _Tmp;
    }

    _CONSTEXPR20 _Vector_const_iterator& operator--() noexcept {
        --_Ptr;
        return *this;
    }

    _CONSTEXPR20 _Vector_const_iterator operator--(int) noexcept {
        _Vector_const_iterator _Tmp = *this;
        --*this;
        return _Tmp;
    }
	
	    _NODISCARD _CONSTEXPR20 bool operator==(const _Vector_const_iterator& _Right) const noexcept {
        _Compat(_Right);
        return _Ptr == _Right._Ptr;
    }

而随机迭代器,相较于双向迭代器,提供额外的一些功能以支持随机访问。

  1. + += - -= 操作用于一定的随机访问,而对于 vector 容器来说,指针本身就指针这样的操作,所以实现起来也很简单。
    _CONSTEXPR20 _Vector_const_iterator& operator+=(const difference_type _Off) noexcept {
        _Ptr += _Off;
        return *this;
    }

    _NODISCARD _CONSTEXPR20 _Vector_const_iterator operator+(const difference_type _Off) const noexcept {
        _Vector_const_iterator _Tmp = *this;
        _Tmp += _Off;
        return _Tmp;
    }

    _NODISCARD_FRIEND _CONSTEXPR20 _Vector_const_iterator operator+(
        const difference_type _Off, _Vector_const_iterator _Next) noexcept {
        _Next += _Off;
        return _Next;
    }

    _CONSTEXPR20 _Vector_const_iterator& operator-=(const difference_type _Off) noexcept {
        return *this += -_Off;
    }

    _NODISCARD _CONSTEXPR20 _Vector_const_iterator operator-(const difference_type _Off) const noexcept {
        _Vector_const_iterator _Tmp = *this;
        _Tmp -= _Off;
        return _Tmp;
    }

    _NODISCARD _CONSTEXPR20 difference_type operator-(const _Vector_const_iterator& _Right) const noexcept {
        return static_cast<difference_type>(_Ptr - _Right._Ptr);
    }
  1. 迭代器的比较,这里不仅支持了相等的判断,而支持了 < <= > >= 的判断,实现也就是指针的大小判断。
    _NODISCARD bool operator<(const _Vector_const_iterator& _Right) const noexcept {
        return _Ptr < _Right._Ptr;
    }

    _NODISCARD bool operator>(const _Vector_const_iterator& _Right) const noexcept {
        return _Right < *this;
    }

    _NODISCARD bool operator<=(const _Vector_const_iterator& _Right) const noexcept {
        return !(_Right < *this);
    }

    _NODISCARD bool operator>=(const _Vector_const_iterator& _Right) const noexcept {
        return !(*this < _Right);
    }
  1. 基于索引的随机访问,作为随机迭代器,当然应该支持基于索引的随机访问
    _NODISCARD _CONSTEXPR20 reference operator[](const difference_type _Off) const noexcept {
        return *(*this + _Off);
    }

非const迭代器

和 const 迭代器差不多,也就是返回的是一个非常量引用/非常量指针。

    _NODISCARD _CONSTEXPR20 reference operator*() const noexcept {
        return const_cast<reference>(_Mybase::operator*());
    }

    _NODISCARD _CONSTEXPR20 pointer operator->() const noexcept {
        return this->_Ptr;
    }

    _CONSTEXPR20 _Vector_iterator& operator++() noexcept {
        _Mybase::operator++();
        return *this;
    }

    _CONSTEXPR20 _Vector_iterator operator++(int) noexcept {
        _Vector_iterator _Tmp = *this;
        _Mybase::operator++();
        return _Tmp;
    }

    _CONSTEXPR20 _Vector_iterator& operator--() noexcept {
        _Mybase::operator--();
        return *this;
    }

    _CONSTEXPR20 _Vector_iterator operator--(int) noexcept {
        _Vector_iterator _Tmp = *this;
        _Mybase::operator--();
        return _Tmp;
    }

    _CONSTEXPR20 _Vector_iterator& operator+=(const difference_type _Off) noexcept {
        _Mybase::operator+=(_Off);
        return *this;
    }

    _NODISCARD _CONSTEXPR20 _Vector_iterator operator+(const difference_type _Off) const noexcept {
        _Vector_iterator _Tmp = *this;
        _Tmp += _Off;
        return _Tmp;
    }

    _NODISCARD_FRIEND _CONSTEXPR20 _Vector_iterator operator+(
        const difference_type _Off, _Vector_iterator _Next) noexcept {
        _Next += _Off;
        return _Next;
    }

    _CONSTEXPR20 _Vector_iterator& operator-=(const difference_type _Off) noexcept {
        _Mybase::operator-=(_Off);
        return *this;
    }

    _NODISCARD _CONSTEXPR20 _Vector_iterator operator-(const difference_type _Off) const noexcept {
        _Vector_iterator _Tmp = *this;
        _Tmp -= _Off;
        return _Tmp;
    }

    _NODISCARD _CONSTEXPR20 reference operator[](const difference_type _Off) const noexcept {
        return const_cast<reference>(_Mybase::operator[](_Off));
    }

反向迭代器

反向迭代器 reverse_iterator 持有正向迭代器,通过实现新的上面的迭代方法,实现方向迭代。

  1. 构造函数,会有一个 current 成员来保存正向迭代器,那么构造函数中就需要传入这个正向迭代器。当然相应的可以通过一个 base 方法获取到其持有的反向迭代器。
    _CONSTEXPR17 reverse_iterator() = default;

    _CONSTEXPR17 explicit reverse_iterator(_BidIt _Right) noexcept(
        is_nothrow_move_constructible_v<_BidIt>) // strengthened
        : current(_STD move(_Right)) {}

    template <class _Other>
    _CONSTEXPR17 reverse_iterator(const reverse_iterator<_Other>& _Right) noexcept(
        is_nothrow_constructible_v<_BidIt, const _Other&>) // strengthened
        : current(_Right.current) {
    }

    template <class _Other>
    _CONSTEXPR17 reverse_iterator& operator=(const reverse_iterator<_Other>& _Right) noexcept(
        is_nothrow_assignable_v<_BidIt&, const _Other&>) /* strengthened */ {
        current = _Right.current;
        return *this;
    }

	_NODISCARD _CONSTEXPR17 _BidIt base() const noexcept(is_nothrow_copy_constructible_v<_BidIt>) /* strengthened */ {
        return current;
    }
  1. 获取元素,下面看到 operator* 实现的内容和我们正向迭代器不太一样,首先获取到一个正向迭代器的副本,然后先前置 -- 然后解引用,可以想象对于一个 list 我们传入的一个正向迭代器,然后对其相应的反向迭代器解引用,得到的是前一个节点元素的引用。比如在 list 容器中,提供的 rbegin 得到的是一个 reverse_iterator(end()) 那么解引用后就是链表的最后一个节点。对于 operator-> 就是其实也是一样的,这里用到了条件编译,如果传入的本身就是个指针,那么直接返回指针,如果是个迭代器返回 _Tmp.operator->() 得到的指针。
    _NODISCARD _CONSTEXPR17 reference operator*() const noexcept(is_nothrow_copy_constructible_v<_BidIt> //
            && noexcept(*--(_STD declval<_BidIt&>()))) /* strengthened */ {
        _BidIt _Tmp = current;
        return *--_Tmp;
    }
	
	    _NODISCARD _CONSTEXPR17 pointer operator->() const
        noexcept(is_nothrow_copy_constructible_v<_BidIt> //
                     && noexcept(--(_STD declval<_BidIt&>()))
                 && _Has_nothrow_operator_arrow<_BidIt&, pointer>) /* strengthened */
    {
        _BidIt _Tmp = current;
        --_Tmp;
        if constexpr (is_pointer_v<_BidIt>) {
            return _Tmp;
        } else {
            return _Tmp.operator->();
        }
    }
  1. 正向/反向迭代,逆向迭代器的正向迭代,其实就相当于对其持有的正向迭代器进行反向迭代,即 --current,而逆向迭代器的反向迭代,当然也就对应着其持有正向迭代器的正向迭代。
    _CONSTEXPR17 reverse_iterator& operator++() noexcept(noexcept(--current)) /* strengthened */ {
        --current;
        return *this;
    }

    _CONSTEXPR17 reverse_iterator operator++(int) noexcept(is_nothrow_copy_constructible_v<_BidIt> //
            && noexcept(--current)) /* strengthened */ {
        reverse_iterator _Tmp = *this;
        --current;
        return _Tmp;
    }

    _CONSTEXPR17 reverse_iterator& operator--() noexcept(noexcept(++current)) /* strengthened */ {
        ++current;
        return *this;
    }

    _CONSTEXPR17 reverse_iterator operator--(int) noexcept(is_nothrow_copy_constructible_v<_BidIt> //
            && noexcept(++current)) /* strengthened */ {
        reverse_iterator _Tmp = *this;
        ++current;
        return _Tmp;
    }
  1. 随机迭代,也是和正向/反向迭代一样,逆向的迭代器 + 的大小,对应其持有正向迭代器 - 的大小,而对于按索引的随机访问,比如索引是 Off 对应正向迭代器中的索引就是 -_Off-1
    _NODISCARD _CONSTEXPR17 reverse_iterator operator+(const difference_type _Off) const
        noexcept(noexcept(reverse_iterator(current - _Off))) /* strengthened */ {
        return reverse_iterator(current - _Off);
    }

    _CONSTEXPR17 reverse_iterator& operator+=(const difference_type _Off) noexcept(
        noexcept(current -= _Off)) /* strengthened */ {
        current -= _Off;
        return *this;
    }

    _NODISCARD _CONSTEXPR17 reverse_iterator operator-(const difference_type _Off) const
        noexcept(noexcept(reverse_iterator(current + _Off))) /* strengthened */ {
        return reverse_iterator(current + _Off);
    }

    _CONSTEXPR17 reverse_iterator& operator-=(const difference_type _Off) noexcept(
        noexcept(current += _Off)) /* strengthened */ {
        current += _Off;
        return *this;
    }

    _NODISCARD _CONSTEXPR17 reference operator[](const difference_type _Off) const
        noexcept(noexcept(_STD _Fake_copy_init<reference>(current[_Off]))) /* strengthened */ {
        return current[static_cast<difference_type>(-_Off - 1)];
    }

迭代器失效

最后再看下迭代器失效的问题,比如对于一个 vector 容器,我们再用迭代器遍历它的过程中,加入/删除元素就会有迭代器失效的问题,对于加入元素,因为 vector 容器本身是有扩容机制的,那么当 size>capacity 就会触发扩容,那么会申请新的一块内存,然后把元素移动到这个新内存区域。那么会导致我们容器给出的几个指针都发生了改变,但我们的迭代器里还保存着这个旧的指针,但旧的指针指向的已经是一块非法的内存区域了,这就是 vector 插入引发的迭代器失效问题。

vector 如果是删除元素,那么需要考虑到删除元素后,会把后面的元素向前移动,而当前迭代器持有的指针指向的元素可能会发生改变,那么就可能产生出乎你意料的结果。

而对于 list 来说,插入一般不会改变当前迭代器,但删除会导致当前迭代器失效(如果删除了当前迭代器指向的节点,那么当前迭代器持有的指针就是不合法的,迭代器失效)。


参考资料

Checked Iterators
迭代器模式

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

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

相关文章

YOLOv8——测量高速公路上汽车的速度

引言 在人工神经网络和计算机视觉领域&#xff0c;目标识别和跟踪是非常重要的技术&#xff0c;它们可以应用于无数的项目中&#xff0c;其中许多可能不是很明显&#xff0c;比如使用这些算法来测量距离或对象的速度。 测量汽车速度基本步骤如下&#xff1a; 视频采集&#x…

江协科技STM32学习- P18 实验-PWM输入捕获测频率PWMI输入捕获模式测频率和占空比

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

分布式光伏的发电监控

国拥有丰富的清洁可再生能源资源储量&#xff0c;积极开发利用可再生能源&#xff0c;为解决当前化石能源短缺与环境污染严重的燃眉之急提供了有效途径[1]。但是可再生能源的利用和开发&#xff0c;可再生能源技术的发展和推广以及可再生能源资源对环境保护的正向影响&#xff…

Qt窗口——QMenuBar

文章目录 QMenuBar示例演示给菜单栏设置快捷键给菜单项设置快捷键添加子菜单添加分割线添加图标 QMenuBar Qt中采用QMenuBar来创建菜单栏&#xff0c;一个主窗口&#xff0c;只允许有一个菜单栏&#xff0c;位于主窗口的顶部、主窗口标题栏下面&#xff1b;一个菜单栏里面有多…

计算机毕业设计之:基于微信小程序的电费缴费系统(源码+文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

鸿蒙OpenHarmony【小型系统基础内核(进程管理调度器)】子系统开发

调度器 基本概念 OpenHarmony LiteOS-A内核采用了高优先级优先 同优先级时间片轮转的抢占式调度机制&#xff0c;系统从启动开始基于real time的时间轴向前运行&#xff0c;使得该调度算法具有很好的实时性。 OpenHarmony 的调度算法将 tickless 机制天然嵌入到调度算法中&…

gRPC介绍

gRPC 是一个由谷歌开发的现代开源高性能 RPC 远程过程调用&#xff08; Remote Procedure Calls&#xff09;框架&#xff0c;具备良好的兼容性&#xff0c;可在多个开发环境下运行。 相较于目前主流的 HTTP API 接口&#xff0c;gRPC 接口采用了领先的 HTTP/2 底层架构设计作…

input文本框随其中内容而变化长

<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><title>input文本框随其中内容而变化长</title><style>.input-length {border: 1px solid #ccc;padding: 5px;min-width: 10px;width: auto;}.in…

cobbler自动批量安装多版本操作系统

本次虚拟化环境为VMware Workstation Pro&#xff0c;cobbler服务端为CentOS7.9&#xff0c;需要自动安装的版本为CentOS7.9和CentOS8.1 目录 一、安装cobbler服务端1、修改YUM源2、关闭防火墙3、安装软件包4、cobbler环境配置5、解决语法问题6、启动服务7、导入镜像8、自定义…

828华为云征文|华为云Flexus X实例:极速搭建个人代码仓库GitLab平台

目录 前言 一、Flexus云服务器X介绍 1.1 Flexus云服务器X实例简介 1.2 Flexus云服务器X实例特点 1.3 Flexus云服务器X实例使用场景 二、Flexus云服务器X购买 2.1 Flexus X实例购买 2.2 重置密码 2.3 登录服务器 三、Flexus X 实例安装GitLab 3.1 GitLab镜像下载 3.2 GitLab部署…

yolov8模型在猫脸关键点检测识别中的应用【代码+数据集+python环境+GUI系统】

yolov8模型在猫脸关键点检测识别中的应用【代码数据集python环境GUI系统】 yolov8模型在猫脸关键点检测识别中的应用【代码数据集python环境GUI系统】 背景意义 猫脸关键点检测是计算机视觉领域的一个重要研究方向&#xff0c;它基于深度学习、机器学习等技术&#xff0c;通过…

手机文件压缩与解压:节省流量的实用技巧

首先&#xff0c;节省存储空间是手机文件压缩的一大优势。随着我们拍摄照片、录制视频、下载文件等&#xff0c;手机的存储空间很容易被占满。 通过压缩文件&#xff0c;可以减小文件的大小&#xff0c;从而释放更多的存储空间。例如&#xff0c;一些大型的文档、图片和视频文…

MySQL:进阶巩固-存储过程

目录 一、存储过程的概述二、存储过程的基本使用2.1 创建存储过程2.2 使用存储过程2.3 查询指定数据库的存储过程以及状态信息2.4 查看某个存储过程的定义2.5 删除存储过程2.6 案例 三、存储过程的变量设置3.1 系统变量3.2 用户自定义变量3.3 局部变量 四、IF判断五、参数六、C…

自动化学习3:日志记录及测试报告的生成--自动化框架搭建

一.日志记录 1.配置文件pytest.ini&#xff1a;将日志写入文件方便日后查询或查看执行信息。 需要将文件处理器&#xff08;文件存放位置/时间/格式等等&#xff09;添加到配置文件中的【日志记录器】 # pytest.ini [pytest] # ---------------日志文件&#xff0c;需要配合…

虚拟机使用FileZilla软件实现文件互传

软件版本&#xff1a;FizeZilla 3.63.2 VirtualBox7.0.20 1.设置桥接模式(网卡) 2.查看ip 在控制台输入ifconfig 3.在终端打开控制台安装FTP服务 sudo apt-get install vsftpd 等待软件自动安装&#xff0c;安装完成以后使用 VI命令打开 /etc/vsftpd.conf&#xff0c;命令…

8086的指令系统

今天上午综测答辩结束&#xff0c;感觉就很一般&#xff0c;但是我昨晚也操心到觉都没睡好&#xff0c;今天中午舍友玩P5吵得我也没睡着&#xff0c;感觉脑袋昏昏沉沉&#xff0c;汇编上课没认真听讲&#xff0c;晚上来补一补。还是采用GPT来讲解&#xff08;水文字&#xff09…

Unity开发绘画板——02.创建项目

1.创建Unity工程 我们创建一个名为 DrawingBoard 的工程&#xff0c;然后先把必要的工程目录都创建一下&#xff1a; 主要包含了一下几个文件夹&#xff1a; Scripts &#xff1a;存放我们的代码文件 Scenes &#xff1a;工程默认会创建的&#xff0c;存放场景文件 Shaders &…

9.22日常记录

1.memccpy函数 memccpy是一个用于内存复制的函数&#xff0c;它的原型通常在<cstring>&#xff0c;memccpy函数的作用是从源内存区域复制字节到目标内存区域&#xff0c;直到遇到特定的字符或者复制了指定数量的字节为止。 返回值: 如果在复制过程中找到了指定的字符&am…

科大讯飞智能体Python SDK接入流程

第一步&#xff1a;注册账号​ 进入https://passport.xfyun.cn/login&#xff0c;根据提示注册或登陆账号。 ​ 第二步&#xff1a;创建智能体 进入这个网页创建智能体&#xff0c;填好信息&#xff1a; https://xinghuo.xfyun.cn/botcenter/createbot?createtrue&qu…

lkhgjfjghkbhjk

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…