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

news2024/12/23 15:35:14

目录

写在前面

需求

分析

接口设计

项目实现

一些思考与总结

致谢


写在前面

刚刚介绍了变参模板和完美转发,现在换一换脑子做一个小的项目实战吧。博主最近学习的是标准库,总体来说,我认为标准库中的内容是很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.coloradmin.cn/o/1422774.html

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

相关文章

蓝桥杯 第 1 场 小白入门赛

目录 1.蘑菇炸弹 2.构造数字 3.小蓝的金牌梦 4.合并石子加强版 5.简单的LIS问题 6.期望次数 1.蘑菇炸弹 我们直接依照题目 在中间位置的数进行模拟即可 void solve(){cin>>n;vector<int> a(n1);for(int i1;i<n;i) cin>>a[i];int ans0;for(int i2;i…

uniapp底部栏设置未读红点或角标

pages.json {... // 省略"tabBar": {"color": "#333333","selectedColor": "#3296fa","backgroundColor": "#ffffff","borderStyle": "white","list": [{"pagePat…

【Go】Viper读取配置文件

go get github.com/spf13/viper 1. 设置配置文件的信息 etcd:ip: "192.168.6.106"port: 2379dialTimeout: 3redis:ip: "192.168.6.107"port: 6379password: "root1028"2. 读取配置文件的信息 2.1 通过kv的方式 package mainimport ("fm…

【Tomcat与网络5】再论Tomcat的工作过程与两种经典的设计模式

前面两篇&#xff0c;我们重点分析了Tomcat的容器和连接器的基本设计&#xff0c;今天我们来看一下两个机构如何在service的调度下进行协同工作的。 目录 1.模板模式与Tomcat的重用性设计 2.观察者模式与Tomcat可扩展性设计 1.模板模式与Tomcat的重用性设计 首先&#xff0…

电脑护眼模式怎么设置?4个有效方法保护眼睛!

“我感觉每天使用电脑的时间久了&#xff0c;眼睛总是不太舒服。电脑护眼模式怎么设置呢&#xff1f;有什么比较好用的方法可以推荐吗&#xff1f;” 如果长时间使用电脑&#xff0c;或许会让我们感到用眼疲劳。电脑护眼模式是现代人常用的电脑设置之一&#xff0c;它能有效地减…

通俗易懂三大范式

通俗易懂三大范式 第一范式说的是每个字段不可再分 第二范式说的是不能存在部分依赖&#xff08;不能由联合主键的部分就可以推出其他字段&#xff0c;必须整个联合主键才能推出其他字段&#xff09; 第三范式说的是不能存在间接依赖(A&#xff08;主键&#xff09;→B,B→C…

wordcloud库和jieba库的使用

文章目录 wordcloud库的简单示范使用wordcloud库报错记录anaconda安装第三方jieba库jieba库的简单示范任务 1&#xff1a;三国演义中的常见词汇分布在“三国"这两个隶书字上&#xff0c;出现频率高的词字体大任务 2&#xff1a;三国演义中出现频率前十的人名。必须是以下这…

【日常总结】windows11 设置文件默认打开方式

一、场景 二、实战 Stage 1&#xff1a;打开设置 Stage 2&#xff1a;应用 > 默认应用 > 搜索 .txt Stage 3&#xff1a;修改成notepad &#xff0c;设置默认值即可 一、场景 windows 11 .txt 默认记事本打开 需求&#xff1a;如何使用notepad打开呢 &#xff1f;…

App Inventor 2 低功耗蓝牙(BLE) 硬件接入、数据通信及IO控制

低功耗蓝牙(BLE)以低功耗、低成本、开发简便逐渐被广泛应用&#xff0c;本文主要介绍一款较为通用、价格低廉的BLE设备从零开始如何利用App Inventor 2开发一款自己专属的手机蓝牙App应用。 BLE与经典蓝牙的区别可参考&#xff1a;《低功耗蓝牙(BLE) 和 经典蓝牙(SPP) 的区别》…

一张序列图搞懂resilience4j的工作原理

本文主要回答以下几个问题&#xff1a; Spring Cloud与Resilience4j如何集成的&#xff08;spring-cliud-circuitbreaker-resilience4j模块的 Resilience4JAutoConfiguration类实现了主要组件的注入&#xff0c;特别是在Resilience4jBulkheadConfiguration中定义如何自定义fac…

Qt简易的五子棋

五子棋是个简单的小游戏&#xff0c;尝试使用Qt将他做出来&#xff0c;学习时的练习demo。 成果展示 需求分析 五子棋&#xff1a;在棋盘上&#xff0c;黑棋先行&#xff0c;交替下棋&#xff0c;五子练成直线获取胜利。 实现过程 1.棋盘绘制&#xff1a;下棋的第一步肯定是绘制…

ubuntu gedit主题更改

ubuntu16.04 gedit 编辑器又有首选项如何设置主题 这里下载主题 将主题XML复制到 /usr/share/gtksourceview-3.0/styles 文件夹内&#xff1b; 使用gsettings 命令设置喜欢的配色方案&#xff0c;使用方式如下&#xff1a;(实测不带.xml后缀哦) gsettings set org.gnome.gedi…

本地配置Joplin Server用于Joplin笔记同步并实现公网远程访问

文章目录 1. 安装Docker2. 自建Joplin服务器3. 搭建Joplin Sever4. 安装cpolar内网穿透5. 创建远程连接的固定公网地址 Joplin 是一个开源的笔记工具&#xff0c;拥有 Windows/macOS/Linux/iOS/Android/Terminal 版本的客户端。多端同步功能是笔记工具最重要的功能&#xff0c;…

小程序定制开发:解析定制化移动应用的未来

引言 在当今数字化时代&#xff0c;移动应用已经成为人们生活不可或缺的一部分。随着智能手机的普及&#xff0c;移动应用的需求呈现出爆发式增长&#xff0c;企业们也纷纷投身于这场数字化浪潮。然而&#xff0c;众多企业在竞争激烈的市场中&#xff0c;如何突显个性、提高用…

uniapp本地存储日志

uniapp本地存储日志 背景实现代码实现使用查看生成log读取 注意事项尾巴 背景 我们的APP开发完成之后&#xff0c;在我们测试环境或者自测的时候都好好的&#xff0c;但是发布到生产环境客户使用总会出现一些奇奇怪怪的问题。这时候因为没在开发环境&#xff0c;我们无法查看到…

【云原生】docker安全与https加密的超文本传输协议CA证书生成

目录 一、docker安全 二、http与https的区别 三、为什么要使用 SSL 证书&#xff1f; 四、https证书认证的过程 https单向认证的访问流程 https双向认证的访问流程 五、如何获取证书&#xff1f; 六、实操获取证书并验证 1、通过阿里云获取证书 2、通过mkcert获取证书…

Flutter的安装与环境配置

一、下载安装Futter&#xff1a; 1、Flutter中文文档&#xff1a; 安装和环境配置 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 2、下载 Futter SDK&#xff1a; Flutter中文文档 里面有&#xff0c;下载完成之后找个文件夹解压出来&#xff0c;最好不要将 Flu…

GoLang和GoLand的安装和配置

1. GoLang 1.1 特点介绍 Go 语言保证了既能达到静态编译语言的安全和性能&#xff0c;又达到了动态语言开发维护的高效率&#xff0c;使用一个表达式来形容 Go 语言&#xff1a;Go C Python , 说明 Go 语言既有 C 静态语言程序的运行速度&#xff0c;又能达到 Python 动态语…

编程实战实例分享,棋牌室计时计费管理系统软件教程

编程实战实例分享&#xff0c;棋牌室计时计费管理系统软件教程 一、前言 以下编程实例以 佳易王棋牌计时计费软件V17.8为例说明 1、开始计时和等待中&#xff0c;图片自动识别&#xff0c;自动匹配 2、开始计时后&#xff0c;系统记录开始时间&#xff0c;并直观显示所用的时…

开源项目MessageNest打造个性化消息推送平台多种通知方式

今天介绍一个开源项目&#xff0c;Message Nest - 可以打造个性化消息推送平台&#xff0c;整合邮件、钉钉、企业微信等多种通知方式。定制你的消息&#xff0c;让通知方式更灵活多样。 开源地址&#xff1a; https://github.com/engigu/Message-Push-Nest 测试平台 系统&am…