网站服务器崩了怎么办,西地那非片功效与作用主要会有哪些,星火教育培训机构,开关网站建设目录 写在前面
需求
分析
接口设计
项目实现
一些思考与总结
致谢 写在前面
刚刚介绍了变参模板和完美转发#xff0c;现在换一换脑子做一个小的项目实战吧。博主最近学习的是标准库#xff0c;总体来说#xff0c;我认为标准库中的内容是很trivial的#xff0c;重点…目录 写在前面
需求
分析
接口设计
项目实现
一些思考与总结
致谢 写在前面
刚刚介绍了变参模板和完美转发现在换一换脑子做一个小的项目实战吧。博主最近学习的是标准库总体来说我认为标准库中的内容是很trivial的重点还是在有需求的时候能够利用好编译器和cppreference。博主也不准备逐个总结各种标准库中数据结构的使用方法。因为是标准库所以其实方法大同小异更多是一个熟练掌握的过程。此外标准库的写法也是一种很好的规范非常值得我们借鉴学习。今天带来的这个项目也采用了类似的规范最终实现了一个通用的容器能够插入不同类型的对象并实现最大最小值的获取。在代码编写过程对标准库中晦涩难懂的类型进行了跳转最终找到了其原始的基本类型这种操作对于标准库的理解十分有益希望大家在进行相关练习时也要善于阅读源码。希望大家共同坚持共同进步~
需求 实现一个通用容器能够插入不同的类型和自定义结构体以及自定义类对象。模板类 能够根据不同的比较规则从容器中获取最大值或最小值。基于红黑树的排序容器set map multi-map
分析 通用容器自己开发还是使用或继承stl标准库中的数据结构 支持多中数据存储模板类。 取最大最小值使用函数对象或者使用有排列属性的数据结构set/map。
接口设计sizeFilter 构造函数析构函数拷贝构造拷贝赋值。 插入和删除。 查找最大最小值。
项目实现 我们这一次采用类似于标准模板库中的编码规范来编写我们的容器类内成员变量名称前面加一个下划线。 便于方便我们把类接口的实现全部写在头文件中。 首先我们的类是一个模板类在我们的模板中会包含一个stl中的数据结构默认是std::set。 我们的类中应该包含一个容器进行数据的存储和排序这个数据成员被设为保护权限这个容器的类型在模板中声明。 按照这个逻辑我们编写出我们类的定义。 /** Created by herryao on 1/29/24.* Email: stevenyaog.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
templatetypename _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
templatetypename _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 ABCMyt 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
templatetypename _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_treekey_type, value_type, _Identityvalue_type,key_compare, _Key_alloc_type _Rep_type; 原来这个_Rep_type又是一个别名别慌我们继续跳进这个_Rb_tree中看一看 templatetypename _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
templatetypename _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
templatetypename _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::pairstd::set::iterator, bool的一个对象前面表示插入位置的迭代器后面表示插入是否成功因此我们可以定义一个临时对象并同过其第二元素来判断插入操作是否成功此时仅限于set对于multiset会有一些区别稍后会进行解释 下面是插入接口的声明和实现 #ifndef PROJECT01_SIZEFILTER_H
#define PROJECT01_SIZEFILTER_H
#include iostream
#include set
templatetypename _Ty,class _Container std::set_Ty
class sizeFilter {
public:bool insert(const value_type Val){//return type of the method insert is a pairset_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::pairtypename _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
templatetypename _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
templatetypename _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
templatetypename _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::pairvalue_type, bool getMin()const{std::pairvalue_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
templatetypename _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::pairvalue_type, bool getMax()const{std::pairvalue_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(){sizeFilterint sf;for(int i0; i5; 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(){sizeFilterint sf;for(int i0; i5; 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(){sizeFilterint, std::multisetint sf;sf.insert(1);//return a pair while insert one value onlystd::setint st;auto ret_multiset st.insert(1);std::multisetint 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::pairstd::_Rb_tree_const_iteratorint, 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会返回一个pairset_Ty::iterator, bool,这样我们写的方法就会出现匹配的问题。那么简单的解决方法就是寻找一个共性的方法即相同的参数和相同的返回值于是在cppreference 中我们可以看到有两个方法是完全一致的但是这里我们选择传入一个左值的常引用来完成我们的一致性代码。 现在只需要把这个地方变成在容器首地址插入即可(因为是树状结构会自动排序因此在哪里插入是不影响的修改后的插入方法接口实现如下 #ifndef PROJECT01_SIZEFILTER_H
#define PROJECT01_SIZEFILTER_H
#include iostream
#include settemplatetypename _Ty,class _Container std::set_Ty
class sizeFilter {
public://insertbool insert(const value_type Val){//return type of the method insert is a pairset_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::pairtypename _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老师的课程。