项目实战:一个基于标准库的具备最值获取的万能容器实现

目录

写在前面

需求

分析

接口设计

项目实现

一些思考与总结

致谢


写在前面

刚刚介绍了变参模板和完美转发,现在换一换脑子做一个小的项目实战吧。博主最近学习的是标准库,总体来说,我认为标准库中的内容是很trivial的,重点还是在有需求的时候能够利用好编译器和cppreference。博主也不准备逐个总结各种标准库中数据结构的使用方法。因为是标准库,所以其实方法大同小异,更多是一个熟练掌握的过程。此外标准库的写法也是一种很好的规范,非常值得我们借鉴学习。今天带来的这个项目也采用了类似的规范,最终实现了一个通用的容器能够插入不同类型的对象并实现最大最小值的获取。在代码编写过程对标准库中晦涩难懂的类型进行了跳转最终找到了其原始的基本类型,这种操作对于标准库的理解十分有益,希望大家在进行相关练习时也要善于阅读源码。希望大家共同坚持共同进步~

需求:
  • 实现一个通用容器能够插入不同的类型和自定义结构体以及自定义类对象。(模板类)

  • 能够根据不同的比较规则从容器中获取最大值或最小值。(基于红黑树的排序容器,set, map, multi-map)

分析:
  • 通用容器,自己开发还是使用或继承stl标准库中的数据结构?

  • 支持多中数据存储,模板类。

  • 取最大最小值,使用函数对象或者使用有排列属性的数据结构(set/map)。

接口设计(sizeFilter)
  • 构造函数,析构函数,拷贝构造,拷贝赋值。

  • 插入和删除。

  • 查找最大最小值。

项目实现
  • 我们这一次采用类似于标准模板库中的编码规范来编写我们的容器,类内成员变量名称前面加一个下划线。

  • 便于方便我们把类接口的实现全部写在头文件中。

  • 首先我们的类是一个模板类,在我们的模板中会包含一个stl中的数据结构,默认是std::set<>。

  • 我们的类中应该包含一个容器进行数据的存储和排序,这个数据成员被设为保护权限,这个容器的类型在模板中声明。

  • 按照这个逻辑我们编写出我们类的定义。

  • /*
     * Created by herryao on 1/29/24.
     * Email: stevenyao@g.skku.edu
     * Sungkyunkwan Univ. Nano Particle Technology Lab(NPTL)
     * this is a project for achieving a container,
     * enabling inserting a variety type of members,
     * including the structs or classes defined by users,
     * having the function accessing the maximum or minimum members within,
     * corresponding with their regulation of comparison.
     *
     * this project is following the coding style of the data structure stack in the stl library
     */
    ​
    #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    protected:
        _Container _c;
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H
  • 分别定义默认构造,有参构造,拷贝构造,拷贝赋值以及一个默认的析构函数。

  • 在拷贝赋值的定义时发现返回类型 sizeFilter<_Ty, _Container>非常冗长,因此采用typedef对这类复杂的类型名称进行重命名来增加代码可读性。

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
        typedef sizeFilter<_Ty, _Container> Myt;
        //directly initialized with one empty constructor
        sizeFilter():_c(){}
        //destructor defined as default since no memory allocated from heap
        ~sizeFilter()=default;
        //copy constructor
        //sizeFilter<_Ty, _Container>& _Right is too long
        //sizeFilter(const sizeFilter<_Ty, _Container>& _Right):_c(_Right._c){}
        sizeFilter(const Myt& Right):_c(Right._c){}
    ​
        //constructor using a specified container
        explicit sizeFilter(const _Container& Cont):_c(Cont._c){}
    ​
        //copy assignment
        //using reference return type for A=B=C
        Myt& operator = (const Myt& Right){
            if(this != &Right){
                this->_c = Right._c;
            }
            return *this;
        }
    protected:
        _Container _c;
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H

  • 结束这些基本的类内重要函数方法的定义我们首先来实现一些简单的接口,首先是容器是否为空,这个很简单只需要调用类型中的方法并返回一个布尔值即可。

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
    //a function check if the current container is empty
        [[nodiscard]] bool empty()const{
            return (this->_c.empty());
        }
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H

  • 然后是获取当前容器内元素的个数,我们可以直接调用成员的size()方法,这个size()方法返回的是一个size_type类型。我们首先看一下这个类型在std::set中的定义。

    •    
       ///  Returns the size of the %set.
          size_type
          size() const _GLIBCXX_NOEXCEPT
          { return _M_t.size(); }

    • 跳进去可以看到其定义如下,实际上是一个重命名,大概可以看出是定义在另外一个类里面的一个成员类型。

    • public:
           ///@{
           ///  Iterator-related typedefs.
           // _GLIBCXX_RESOLVE_LIB_DEFECTS
           // DR 103. set::iterator is required to be modifiable,
           // but this allows modification of keys.
           typedef typename _Rep_type::size_type       size_type;

    • 我们继续跳入到_Rep_type这个类中看一下这个size_type到底是个什么东西。

    • private:
            typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
                     key_compare, _Key_alloc_type> _Rep_type;

    • 原来这个_Rep_type又是一个别名,别慌,我们继续跳进这个_Rb_tree中看一看:

    • template<typename _Key, typename _Val, typename _KeyOfValue,
             typename _Compare, typename _Alloc = allocator<_Val> >
      class _Rb_tree

    • 可以看到我们终于跳出了这个重命名进入了一个类,现在我们开始重新搜索一下这个size_type,定位到他的定义处。

    • public:
           typedef size_t                 size_type;

    • 终于找到了,原来这个size_type其实就是一个size_t的重命名,这种重命名的用法在标准库中被大量使用,因此也提升了标准库的阅读难度,但是实际上标准库也是基于cpp的基本语法,因此只要能捋清头绪最终都会找到cpp中的关键字的。

  • 现在我们了解了这个类型,但是我们还是不要破坏标准库中的书写习惯,既然是标准库中封装好了的命名,我们就直接使用也方便用户去跳转,但是我们也可以参照他们的方法对这个类型进行一个重写。

  • 顺便直接提及一下,除了这个size()返回的是size_type以外,insert(), erase()等方法也需要传入另外一个标准库中重命名的类型value_type大家可以自行跳转看一下这个类型是什么,博主这里就不过多赘述了。

  • 由于想要使用这两个返回类型,我们每次都要写typename _Container::size_type这么长的一个类型转换(注意:这里由于size_type本质是一个类型,而不是一个成员。此外_Container 是我们传入的一个模板的类,其本质也是一个模板。所以typename关键字在这里是必须的,因为_Container::size_type是一个依赖于模板参数的类型,所以需要typename来指明它是一个类型名称。)所以所有的类型重命名实现如下:

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
        typedef sizeFilter<_Ty, _Container> Myt;
        //if try to access the type name in a template class, the keyword typename is needed
        //otherwise the compiler cannot identify whether this is a typename or a
        // member static variable or something else.
        typedef typename _Container::size_type size_type;
        typedef typename _Container::value_type value_type;
    };

  • 现在我们可以用我们上面重命名的名称来简化我们的代码了,size()方法的实现如下,其余部分就略过了:

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
    //access the size of the container
        size_type size()const{
            return this->_c.size();
        }
    };

  • 现在我们来实现一个插入的接口,首先我们已经定义了传入参数类型的重命名value_type,其次我们知道set这个类的insert方法会返回一个std::pair<std::set::iterator, bool>的一个对象,前面表示插入位置的迭代器,后面表示插入是否成功,因此我们可以定义一个临时对象并同过其第二元素来判断插入操作是否成功(此时仅限于set,对于multiset会有一些区别,稍后会进行解释)

  • 下面是插入接口的声明和实现

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
        bool insert(const value_type& Val){
            //return type of the method insert is a pair<set<_Ty>::iterator, bool>,
            //where the first member is the iterator pointing to the inserted value if successful
            //while, second member indicated if the insert operation is successful or not
            //hereby once more, the _Container is a template class, as a result, as mentioned before,
            //the type name need to be explicitly declared using typename to inform compiler that the
            //keyword here is a type name rather than anything else
            
            std::pair<typename _Container::iterator, bool> ret = this->_c.insert(Val);
            if(ret.second){
                std::cout << "succeed in inserting operation on " << Val << std::endl;
            }else{
                std::cout << "failed in inserting operation" << std::endl;
            }
            return ret.second;
        }
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H

  • 然后是相应的erase()方法的实现,同样地要传入一个value_type类型的数据并调用容器成员的erase()方法对其进行删除,值得注意的是标准库这个方法会返回删除内容的个数,因此只要个数大于一就可以认为删除操作是成功的。我们对这个方法进行实现:

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
    //and delete
        bool erase(const value_type& Val){
            //there is one member function in the std::set erase()
            //which will return an integer indicating the number of keys deleted
            //if the returned value is larger than 1, indicating a successful operation
            if(this->_c.erase(Val) > 0){
                std::cout << "succeed in deleting operation" << std::endl;
                return true;
            }else{
                std::cout << "failed in deleting operation" << std::endl;
                return false;
            }
        }
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H

  • 接下来是clear()的接口实现,很简单直接调用即可:

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
        void clear(){
            this->_c.clear();
        }
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H

  • 然后就是我们的关键接口,获取最大值和最小值。首先我们思考一下,当容器中有元素,第一个数据就是最小值,最后一个数据就是最大值我们直接按照需求进行返回即可。但是如果这个容器是空的我们该如何返回呢?

  • 可以借助标准库中的思路返回一个std::pair的对象,将寻找的成功与否和寻找到的元素一并返回,于是我们可以实现:

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
        //access the minimum value in the container
        //how to achieve the return? if there is a value, return the value, else return false?
        std::pair<value_type, bool> getMin()const{
            std::pair<value_type, bool> ret;
            if(!this->_c.empty()){
                typename _Container::iterator pmin = _c.begin();
                ret.first = *pmin;
                ret.second = true;
            }else{
                ret.second = false;
            }
            return ret;
        }
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H

  • 接下来用类似的思路我们来实现一下getMax()这个接口,一样地返回一个pair对象。

  • 但是由于我们最大值在最后,而set不支持.back(),所以我们需要用.end()来偏移获取最后一个数据的迭代器,即最大值的迭代器。

  • 这时候便出现了一个问题那就是如何获取?简单的想法就是获取迭代器后-1,但是-1这种操作只适合于顺序容器如std::vector, std::deque, std::list等。但是 std::set, std::multiset, std::map, std::multimap等关联容器(基于树或者哈希表实现),不支持这种+1, -1的操作只支持++或者--。所以我们的getMax()的实现如下:

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    ​
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
        //access the maximum value in the container
        //how to achieve the return? following the definition in the getMin
        std::pair<value_type, bool> getMax()const{
            std::pair<value_type, bool> ret;
            if(!this->_c.empty()){
                //typename _Container::iterator pmax = _c.end()-1;
                //typename _Container::iterator pmax = _c.end()--;
                //to be noticed here, the iterator in the set is a bidirectional iterator
                // instead of a random access iterator, try not to use std::set<_Ty>::iterator it = _c.end() - 1;
                //besides _c.end() returns a temporary object, meaning that it is illegal to do -- operation directly on which,
                //in another word, the .end() function returns a right value instead of a left value.
                //right way is first store it in a variable and then perform the --operation
                typename _Container::iterator pmax = _c.end();
                ret.first = *(--pmax);
                ret.second = true;
            }else{
                ret.second = false;
            }
            return ret;
        }
    };
    ​
    ​
    #endif//PROJECT01_SIZEFILTER_H

  • 至此一个基于set的通用容器完成,现在我们开始做一些测试。

  • 首先测试一下最大最小值的获取以及清空操作

    • 创建一个容器对象,在容器内部添加五个元素,此时会调用insert方法并会有一些输出信息。

    • 然后获取一下最大值,如果有最大值就输出否则打印获取失败。

    • 然后获取一下最小值,如果有最小值就输出否则打印获取失败。

    • 清除掉容器中的元素。

    • 然后再插入一个元素。

    • 继续搜索最大最小值,此时应当均有输出且输出一样的结果。

  • #include <iostream>
    #include "sizeFilter.h"
    ​
    void test_4_max_min(){
        sizeFilter<int> sf;
        for(int i=0; i<5; ++i){
            sf.insert(5*i);
        }
        std::cout << "get the result here" <<std::endl;
    ​
        auto ret_max = sf.getMax();
        if(ret_max.second){
            std::cout << "find max val: " << ret_max.first << std::endl;
        }else{
            std::cout << "failed in find max" << std::endl;
        }
    ​
        auto ret_min = sf.getMin();
        if(ret_min.second){
            std::cout << "find min val: " << ret_min.first << std::endl;
        }else{
            std::cout << "failed in find min" << std::endl;
        }
    ​
        sf.clear();
        std::cout << "after clear" <<std::endl;
        sf.insert(5);
        auto ret_max_1 = sf.getMax();
        if(ret_max_1.second){
            std::cout << "find max val: " << ret_max_1.first << std::endl;
        }else{
            std::cout << "failed in find max" << std::endl;
        }
    ​
        auto ret_min_1 = sf.getMin();
        if(ret_min_1.second){
            std::cout << "find min val: " << ret_min_1.first << std::endl;
        }else{
            std::cout << "failed in find min" << std::endl;
        }
    }
    ​
    int main() {
        test_4_max_min();
    ​
        return 0;
    }
    ​

  • 测试结果如下,完全符合预期:

  • D:\ClionProject\project\cmake-build-debug\project.exe
    succeed in inserting operation on 0
    succeed in inserting operation on 5
    succeed in inserting operation on 10
    succeed in inserting operation on 15
    succeed in inserting operation on 20
    get the result here
    find max val: 20
    find min val: 0
    succeed in inserting operation on 5
    after clear
    find max val: 5
    find min val: 5
    ​
    Process finished with exit code 0
  • 然后再测试一下删除操作:

    • 类似上一个操作添加五个元素。

    • 删除一个存在的元素。

    • 删除一个不存在的元素。

  • #include <iostream>
    #include "sizeFilter.h"
    ​
    void test_4_erase(){
        sizeFilter<int> sf;
        for(int i=0; i<5; ++i){
            sf.insert(5*i);
        }
        std::cout << "get the result here" <<std::endl;
        std::cout << "erase a exist value: " << std::endl;
        sf.erase(5);
        std::cout << "erase one unknown value: " << std::endl;
        sf.erase(60);
    }
    ​
    int main() {
        test_4_erase();
    ​
        return 0;
    }

  • 相应的输出结果如下,存在和不存在的元素都得到了相应的处理:

  • D:\ClionProject\project\cmake-build-debug\project.exe
    succeed in inserting operation on 0
    succeed in inserting operation on 5
    succeed in inserting operation on 10
    succeed in inserting operation on 15
    succeed in inserting operation on 20
    get the result here
    erase a exist value:
    succeed in deleting operation
    erase one unknown value:
    failed in deleting operation
    ​
    Process finished with exit code 0
  • 接下来是重头戏了,我们来更换一下基本类型,使用一个std::multiset吧

  • void test_4_other_container(){
        sizeFilter<int, std::multiset<int>> sf;
        sf.insert(1);
        //return a pair while insert one value only
        std::set<int> st;
        auto ret_multiset = st.insert(1);
        std::multiset<int> ms;
        //the return type of multiset insert using one value is iterator only
        // while without a bool type and not a pair.
        auto ret_multiset = ms.insert(1);
    }

  • 当直接传入一个int的数据时,会报错一个类型不匹配,这是为什么呢?

  • ====================[ Build | project01 | Debug ]===============================
    /home/herryao/Software/clion-2023.2/bin/cmake/linux/x64/bin/cmake --build /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/cmake-build-debug --target project01 -- -j 10
    [ 50%] Building CXX object CMakeFiles/project01.dir/main.cpp.o
    In file included from /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/sizeFilter.hpp:7,
                     from /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/main.cpp:2:
    /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/sizeFilter.h: In member function ‘bool sizeFilter<_Ty, _Container>::insert(const value_type&)’:
    /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/sizeFilter.h:78:5: warning: no return statement in function returning non-void [•]8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wreturn-type•-Wreturn-type•]8;;•]
       78 |     }
          |     ^
    /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/main.cpp: In function ‘void test_4_other_container()’:
    /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/main.cpp:65:10: error: conflicting declaration ‘auto ret_multiset’
       65 |     auto ret_multiset = ms.insert(1);
          |          ^~~~~~~~~~~~
    /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week04/mon/project01/main.cpp:61:10: note: previous declaration as ‘std::pair<std::_Rb_tree_const_iterator<int>, bool> ret_multiset’
       61 |     auto ret_multiset = st.insert(1);
          |          ^~~~~~~~~~~~
    gmake[3]: *** [CMakeFiles/project01.dir/build.make:76: CMakeFiles/project01.dir/main.cpp.o] Error 1
    gmake[2]: *** [CMakeFiles/Makefile2:83: CMakeFiles/project01.dir/all] Error 2
    gmake[1]: *** [CMakeFiles/Makefile2:90: CMakeFiles/project01.dir/rule] Error 2
    gmake: *** [Makefile:124: project01] Error 2

  • 原来是因为multiset直接插入一个数据会返回一个迭代器,而set的insert会返回一个pair<set<_Ty>::iterator, bool>,这样我们写的方法就会出现匹配的问题。那么简单的解决方法就是寻找一个共性的方法,即相同的参数和相同的返回值,于是在cppreference 中我们可以看到有两个方法是完全一致的,但是这里我们选择传入一个左值的常引用来完成我们的一致性代码。

  • 现在只需要把这个地方变成在容器首地址插入即可(因为是树状结构,会自动排序因此在哪里插入是不影响的)修改后的插入方法接口实现如下:

  • #ifndef PROJECT01_SIZEFILTER_H
    #define PROJECT01_SIZEFILTER_H
    #include <iostream>
    #include <set>
    
    template<typename _Ty,
             class _Container = std::set<_Ty>>
    class sizeFilter {
    public:
    	//insert
    	bool insert(const value_type& Val){
        //return type of the method insert is a pair<set<_Ty>::iterator, bool>,
        //where the first member is the iterator pointing to the inserted value if successful
        //while, second member indicated if the insert operation is successful or not
        //hereby once more, the _Container is a template class, as a result, as mentioned before,
        //the type name need to be explicitly declared using typename to inform compiler that the
        //keyword here is a type name rather than anything else
        //previous method is commented, which is not suitable for std::multiset
        /*
        std::pair<typename _Container::iterator, bool> ret = this->_c.insert(Val);
        if(ret.second){
            std::cout << "succeed in inserting operation on " << Val << std::endl;
        }else{
            std::cout << "failed in inserting operation" << std::endl;
        }
        return ret.second;
         */
        	bool ret = false;
        	//for utilizing the multiset
        	typename _Container::iterator flag = _c.insert(_c.begin(), Val);
        	if(flag != this->_c.end()){
            	std::cout << "succeed in inserting operation on " << Val << std::endl;
            	ret = true;
        	}else{
            	std::cout << "failed in inserting operation" << std::endl;
        	}
        	return ret;
    	}
    };
    
    
    #endif//PROJECT01_SIZEFILTER_H
    

  • 重新运行之前的测试结果如下:

  • D:\ClionProject\project\cmake-build-debug\project.exe
    succeed in inserting operation on 1
    succeed in inserting operation on 5
    succeed in inserting operation on 7
    the size of the container is 3
    the maximum value is 7
    the minimum value is 1
    ​
    Process finished with exit code 0
  • 可以看出insert方法执行一切正常。

一些思考与总结
  • 此项目实战综合了标准库中的一些通用的方法如insert(), clear(), size()

  • 参照标准库中的写法对一些冗余的类型名称进行了重命名。

  • 对于模板类中自定义的一些类型的使用需要在前面加上typename关键字。

  • 关联类型容器的迭代器支支持++, -- 操作。

  • 通用方法的函数接口未必一样,应该尽可能考虑 不同类型的使用,因此在设计程序之前,阅读相关文档是非常必要的。

致谢
  • 感谢各位的支持,希望大家的cpp水平不断变强。

  • 感谢Martin老师的课程。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32实时时钟(RTC)的配置和使用方法详解

实时时钟&#xff08;RTC&#xff09;是STM32系列微控制器上的一个重要模块&#xff0c;用于提供准确的时间和日期信息。在本文中&#xff0c;我们将详细介绍STM32实时时钟的配置和使用方法。 ✅作者简介&#xff1a;热爱科研的嵌入式开发者&#xff0c;修心和技术同步精进 ❤欢…

【C++】默认成员函数

与普通成员函数差距较大&#xff0c;形式对于我们比较陌生&#xff0c;但这是语法&#xff0c;是我们是必须要掌握的。 目录 类的默认成员函数&#xff1a;构造函数&#xff1a;概念&#xff1a;语法&#xff1a;特性&#xff1a; 析构函数&#xff1a;概念&#xff1a;语法&a…

Django模型(七)

一、聚合与分组查询 1.1、准备数据 class Cook(models.Model):"""厨师"""name = models.CharField(max_length=32,verbose_name=厨师名)level = models.IntegerField(verbose_name=厨艺等级)age = models.IntegerField(verbose_name=年龄)sect …

消息中间件之RocketMQ源码分析(三)

RocketMQ中的Consumer启动流程 RocketMQ客户端中有两个独立的消费者实现类分别为DefaultMQPullConsumer和DefaultMQPushConsumer&#xff0c; DefaultMQPullConsumer DefaultMQPullConsumer,该消费者使用时需要用户主动从Broker中Pull消息和消费消息&#xff0c;提交消费位点…

Springboot-SpringCloud学习

文章目录 web项目开发历史 SpringBootSpring以及Springboot是什么微服务第一个Springboot项目配置如何编写 yaml自动装配原理集成 web开发&#xff08;业务核心&#xff09;集成 数据库 Druid分布式开发&#xff1a;Dubbo&#xff08;RPC&#xff09; zookeeperswagger&#x…

掌握使用 React 和 Ant Design 的个人博客艺术之美

文章目录 前言在React的海洋中起航安装 Create React App安装Ant Design 打造个性化的博客风格通过路由实现多页面美化与样式定制部署与分享总结 前言 在当今数字时代&#xff0c;个人博客成为表达观点、分享经验和展示技能的独特平台。在这个互联网浪潮中&#xff0c;选择使用…

新火种AI|脑洞照进现实!马斯克正式官宣,已将芯片连入大脑...

作者&#xff1a;小岩 编辑&#xff1a;彩云 2024年1月30日&#xff0c;马斯克在社交媒体上宣布&#xff0c;他的公司Neuralink已经完成了首例人类大脑芯片植入手术&#xff0c;并且目前手术恢复状况良好。这一突破性进展意味着人类距离实现大脑与电脑的直接连接更近了一步。…

【mysql】InnoDB引擎的索引

目录 1、B树索引 1.1 二叉树 1.1.1 二分查找&#xff08;对半查找&#xff09; 1.1.2 树&#xff08;Tree&#xff09; 1.1.2.1 树的定义 1.1.2.2 树的特点 1.1.2.3 二叉树 1.1.2.4 二叉查找&#xff08;搜索&#xff09;树 1.2 B树 1.2.1 聚簇索引&#xff08;clust…

AI-数学-高中-14-函数零点存在定理和运用

原作者视频&#xff1a;【函数综合】【考点精华】1零点存在性定理的运用&#xff08;基础&#xff09;_哔哩哔哩_bilibili 1.定义&#xff1a; 2.零点存在定义&#xff1a; 2.函数零点与图像焦点的转化 零点如果不好求&#xff0c;将函数化成两个函数再画图&#xff0c;看函数…

防抖和节流?有什么区别?如何实现?

#一、是什么 本质上是优化高频率执行代码的一种手段 如&#xff1a;浏览器的 resize、scroll、keypress、mousemove 等事件在触发时&#xff0c;会不断地调用绑定在事件上的回调函数&#xff0c;极大地浪费资源&#xff0c;降低前端性能 为了优化体验&#xff0c;需要对这类…

计算机网络-物理层传输介质(导向传输介质-双绞线 同轴电缆 光纤和非导向性传输介质-无线波 微波 红外线 激光)

文章目录 传输介质及分类导向传输介质-双绞线导向传输介质-同轴电缆导向传输介质-光纤非导向性传输介质小结 传输介质及分类 物理层规定电气特性&#xff1a;规定电气信号对应的数据 导向传输介质-双绞线 双绞线的主要作用是传输数据和语音信息。它通过将两根导线以特定的方…

python爬虫2

1.table 是表格&#xff0c;tr是行&#xff0c;td是列 ul li是无序列标签用的较多&#xff0c;ol li是有序列标签 最基本的结构 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title> Title </title>…

【JavaEE】UDP协议与TCP协议

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

CSS复合选择器

目录 1.什么是复合选择器 2.后代选择器 3.子选择器 4.并集选择器 5.伪类选择器 5.1链接伪类选择器 5.2 :focus 伪类选择器 6.总结 7.补充 7.1相邻兄弟选择器&#xff08;也叫加号选择器&#xff09; 7.2通用兄弟选择器&#xff08;也叫波浪号选择器&#xff09; 1. 什…

力扣hot100 划分字母区间 贪心 思维 满注释版

Problem: 763. 划分字母区间 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 代码随想录 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) Code class Solution {public List<Integer> partitionLabels(String s){// 创建哈希…

AI工具【OCR 01】Java可使用的OCR工具Tess4J使用举例(身份证信息识别核心代码及信息提取方法分享)

Java可使用的OCR工具Tess4J使用举例 1.简介1.1 简单介绍1.2 官方说明 2.使用举例2.1 依赖及语言数据包2.2 核心代码2.3 识别身份证信息2.3.1 核心代码2.3.2 截取指定字符2.3.3 去掉字符串里的非中文字符2.3.4 提取出生日期&#xff08;待优化&#xff09;2.3.5 实测 3.总结 1.简…

闭包的理解?闭包使用场景

说说你对闭包的理解&#xff1f;闭包使用场景 #一、是什么 一个函数和对其周围状态&#xff08;lexical environment&#xff0c;词法环境&#xff09;的引用捆绑在一起&#xff08;或者说函数被引用包围&#xff09;&#xff0c;这样的组合就是闭包&#xff08;closure&#…

FFmpeg和Monibuka拉取rtsp(大华摄像头)视频流时未进行URLCode编码导致提示404等报错

场景 Monibucav4(开源流媒体服务器)在Windows上搭建rtmp服务器并实现拉取rtsp视频流以及转换flv播放&#xff1a; Monibucav4(开源流媒体服务器)在Windows上搭建rtmp服务器并实现拉取rtsp视频流以及转换flv播放_monibuca 搭建流媒体服务-CSDN博客 Nginx搭建RTMP服务器FFmpeg…

Origin 2022下载安装教程,操作简单,小白也能轻松搞定,附安装包,带软件使用教程

前言 Origin是一个科学绘图、数据分析软件&#xff0c;支持各种各样的2D/3D图形&#xff0c;包括统计&#xff0c;信号处理&#xff0c;曲线拟合以及峰值分析&#xff0c;Origin具有强大的数据导入功能和多样的图形输出格式。 准备工作 1、Win7及以上系统 2、提前准备好 Or…

十分钟快速上手Spring Boot与微信小程序API接口的调用,快速开发小程序后端服务

1.1 微信小程序API接口介绍 微信小程序API接口是连接小程序前端与后端服务器的桥梁&#xff0c;它提供了丰富的功能接口&#xff0c;包括用户信息、支付、模板消息、数据存储等。这些API接口能够满足开发者在小程序中实现各种复杂业务逻辑的需求。 用户信息接口 用户信息接口…
最新文章