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

目录

写在前面

需求

分析

接口设计

项目实现

一些思考与总结

致谢


写在前面

刚刚介绍了变参模板和完美转发,现在换一换脑子做一个小的项目实战吧。博主最近学习的是标准库,总体来说,我认为标准库中的内容是很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 constructorsizeFilter():_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 containerexplicit sizeFilter(const _Container& Cont):_c(Cont._c){}
    ​//copy assignment//using reference return type for A=B=CMyt& 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_typesize() 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 containersize_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 elsestd::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 deletebool 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 operationif(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 getMinstd::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 --operationtypename _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 onlystd::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://insertbool 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 multisettypename _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.mzph.cn/news/658732.shtml

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

相关文章

04 避免 Latch 的产生

Latch 是什么 latch 即锁存器&#xff0c;是一种对电平敏感的存储单元电路&#xff0c;和寄存器一样都是基本存储单元&#xff0c;但是寄存器是边沿触发的存储器&#xff0c;锁存器是电平触发的存储器。 组合逻辑电路和时序逻辑电路 在数字电路中将逻辑电路分成两大类&#…

Java学习笔记(十一)——常用类

一、包装类 &#xff08;一&#xff09;包装类和基本数据类型的转换 ​编辑 &#xff08;二&#xff09;包装类型和String类型的相互转换 &#xff08;三&#xff09;Integer类和Character类的常用方法 二、String &#xff08;一&#xff09;创建String对象的两种方式 …

【爬虫专区】批量下载PDF (无反爬)

天命:只要没反爬,一切都简单 这次爬取的是绿盟的威胁情报的PDF 先看一下结构,很明显就是一个for循环渲染 burp抓包会发现第二次接口请求 接口请求一次就能获取到了所有的数据 然后一个循环批量下载数据即可,其实没啥难度的 import requests,osres = requests.get("…

C++ 数论相关题目 台阶-Nim游戏

现在&#xff0c;有一个 n 级台阶的楼梯&#xff0c;每级台阶上都有若干个石子&#xff0c;其中第 i 级台阶上有 ai 个石子(i≥1 )。 两位玩家轮流操作&#xff0c;每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中&#xff08;不能不拿&#xff09;。 已经拿到地面…

鸿蒙会取代Android吗?听风就是雨

现在说取代还谈不上&#xff0c;毕竟这需要时间。安卓作为全球第一的手机操作系统&#xff0c;短时间内还无法取代。持平iOS甚至超过iOS有很大可能&#xff0c;最终会呈现“三足鼎立”有望超过安卓基数。 作为全新的鸿蒙操作系统&#xff0c;其现在已经是全栈自研底座。按照鸿…

linux --中断管理 -- irq的自动探测机制

irq自动探测机制 如果一个设备的驱动程序无法确定它说管理的设备的软件中断号irq&#xff0c;此时设备驱动程序可以使用irq的自动探测机制来获取其正在使用的irq。 使用自动探测机制的条件 内核与驱动&#xff0c;必须共同努力才能完成只限于非共享中断的情况 探测前&#…

vue3前端开发框架的安全特性,非常适合现在的市场需求

vue3前端开发框架的安全特性,非常适合现在的市场需求&#xff01;现在几乎所有的前端开发&#xff0c;都是使用的vue3做了开发。下面给大家展示一下。为什么说vue3框架自带安全特性呢。 如图&#xff0c;这个是我们在浏览器内看见的&#xff0c;渲染后的数据页面信息。很齐全。…

React中文官网已经搬迁了,原网址内容将不再更新

注意1&#xff1a;React中文官网已经搬迁至-React 官方中文文档&#xff0c;原网址内容将不再更新 注意2&#xff1a;React官网已经将React的定义由“用于构建用户界面的 JavaScript 库”更改为“用于构建 Web 和原生交互界面的库”。

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例5-5 Canvas 绘制三角形

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>Canvas 绘制三角形</title> </head><body><canvas id"cavsElem">您的浏览器不支持Canvas&#xff0c;请升级浏览器</canvas…

最快最便捷的pytest使用allure测试报告

一、前言 最近通过群友了解到了allure这个报告&#xff0c;开始还不以为然&#xff0c;但还是逃不过真香定律。 经过试用之后&#xff0c;发现这个报告真的很好&#xff0c;很适合自动化测试结果的展示。下面说说我的探索历程吧。 选用的项目为Selenium自动化测试Pytest框架…

k8s的operator基石:controller-runtime源码解析

写在之前 今天开始开更controller-runtime的源码阅读&#xff0c;笔者建议大家在阅读前了解以下知识&#xff0c;可能会帮助大家更好的理解源码逻辑。 1.client-go的基础使用 2. 使用kubebuilder搭建一个简单的controller-runtime环境 3.informer的基本思想 1.源码环境搭建 参…

代理模式(静态代理、JDK 动态代理、CGLIB 动态代理)

代理模式(静态代理、JDK 动态代理、CGLIB 动态代理) 一、代理模式概述1. 生活中的代理案例2. 为什么要使用代理3. 代理模式在 Java 中的应用4. 概述5. 生活中代理图示二、代理的实现方式1. Java 中代理图示2. 静态代理2.1 案例2.2 实现案例2.3 静态代理存在的问题三、动态代理…

SpringBoot的默认组件扫描

本篇博客主要探究&#xff1a;为什么SpringBoot项目中我们没有配置组件扫描的包&#xff0c;为什么它会默认扫描启动类所在的包&#xff1f; 一、访问与启动类所在同一包下的接口 我们先来看一个简单的接口&#xff1a; 我们可以观察到&#xff0c;HelloController这个类处在…

福布斯财富增长榜前十富豪身价暴增3.5万亿!他们致富的秘诀究竟是?

按照《福布斯》最新的数据显示&#xff0c;今年全球前十位财富增长最多的富豪的身家总共增加了4900亿美元&#xff08;约3.5万人民币&#xff09;&#xff0c;大家可能对于3.5万亿没什么概念&#xff0c;但是换算一下&#xff0c;中国一共才14亿人&#xff0c;如果把这3.5万亿平…

jenkins部署(docker)

docker部署&#xff0c;避免安装tomcat 1.拉镜像 docker pull jenkins/jenkins2.宿主机创建文件夹 mkdir -p /lzp/jenkins_home chmod 777 /lzp/jenkins_home/3.启动容器 docker run -d -p 49001:8080 -p 49000:50000 --privilegedtrue -v /lzp/jenkins_home:/var/jenkins_…

BUUCTF-Real-[PHP]XXE

目录 1、原理 2、XXE漏洞产生的原因 3、开始复现 paylaod 复现 4、flag 1、原理 XML数据在传输过程中&#xff0c;攻击者强制XML解析器去访问攻击者指定的资源内容&#xff08;本地/远程&#xff09;&#xff0c;外部实体声明关键字SYSTEM会令XML解析器读取数据&#xf…

【Axure教程0基础入门】00Axure9汉化版下载、安装、汉化、注册+01制作线框图

写在前面&#xff1a;在哔哩哔哩上面找到的Axure自学教程0基础入门课程&#xff0c;播放量最高&#xff0c;5个多小时。课程主要分为4个部分&#xff0c;快速入门、动态面板、常用动效、项目设计。UP主账号【Song老师产品经理课堂】。做个有素质的白嫖er&#xff0c;一键三连必…

【C/C++】深入理解--函数重载(什么是函数重载?为什么要有函数重载?)

目录 一、前言 二、 函数重载 &#x1f34e;什么是函数重载 &#x1f350;函数重载的条件 &#x1f347;函数重载的注意点 &#x1f349;为什么要有函数重载 &#x1f353;为何C语言不支持函数重载&#xff0c;反倒C可以&#xff1f; &#x1f4a6; Linux环境下演示函数重…

Cocos creator 动作系统

动作系统简介 是用于控制物体运动的一套系统&#xff0c;完全依赖代码进行实现&#xff0c;动态调节节点的移动。 移动 cc.moveTo 移动到某个坐标&#xff08;x,y&#xff09; //1秒时间内&#xff0c;移动到0,0let action1 cc.moveTo(1,0,0)this.node.runAction(action1)c…

基于单片机的烟草干燥温度控制系统设计

摘 要&#xff1a;烟草干燥研究一直备受国内外烟草工作者的重视&#xff0c;在烟草干燥的方法中热风管处理法是利用热空气对流使烟草达到干燥的效果&#xff0c;这样可以控制烟草干燥时的温度&#xff0c;使烟草能够更好更快地干燥&#xff0c;因此温度的检测和控制是很重要的。…