set的特性是所有元素都会根据键值自动排序,set的元素不像map那样同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。Set不允许两个元素拥有相同的键值。不能通过迭代器修改set元素的值。
multiset和set的唯一区别在于multiset允许键值重复。
我们采用红黑树作为set和multiset的底层数据结构,set和multiset的实现完完全全是在红黑树的基础上封装的一层接口,所有的set和multiset操作都转而调用红黑树的API。有关STL红黑树的实现,请移步:STL简单红黑树的实现
我用VS2013写的程序(github),set和multiset版本的代码位于cghSTL/version/cghSTL-0.4.2.rar
在STL中,set和multiset的底层都需要以下几个文件:
1. globalConstruct.h,构造和析构函数文件,位于cghSTL/allocator/cghAllocator/
2. cghAlloc.h,空间配置器文件,位于cghSTL/allocator/cghAllocator/
3. rb_tree.h,红黑树的实现,位于cghSTL/associative containers/RB-tree/
set的实现文件cghSet.h,位于cghSTL/associative containers/cghSet/
set的测试文件test_cghSet.cpp,位于cghSTL/test/
multiset的实现文件cghMultiset.h,位于cghSTL/associative containers/cghMultiset/
multiset的测试文件test_cghMultiset.cpp,位于cghSTL/test/
先看第一个,globalConstruct.h构造函数文件
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 功能:全局构造和析构的实现代码 ******************************************************************/ #include "stdafx.h" #include <new.h> #include <type_traits> #ifndef _CGH_GLOBAL_CONSTRUCT_ #define _CGH_GLOBAL_CONSTRUCT_ namespace CGH { #pragma region 统一的构造析构函数 template<class T1, class T2> inline void construct(T1* p, const T2& value) { new (p)T1(value); } template<class T> inline void destroy(T* pointer) { pointer->~T(); } template<class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { // 本来在这里要使用特性萃取机(traits编程技巧)判断元素是否为non-trivial // non-trivial的元素可以直接释放内存 // trivial的元素要做调用析构函数,然后释放内存 for (; first < last; ++first) destroy(&*first); } #pragma endregion } #endif
按照STL的接口规范,正确的顺序是先分配内存然后构造元素。构造函数的实现采用placement new的方式;为了简化起见,我直接调用析构函数来销毁元素,而在考虑效率的情况下一般会先判断元素是否为non-trivial类型,non-trivial的元素可以直接释放内存,可以理解为non-trivial全部分配在栈上,不用我们操心,由程序自动回收,trivial的元素要分配在堆上,需要手工调用析构函数来销毁,然后释放内存。
cghAlloc.h是空间配置器文件,空间配置器负责内存的申请和回收。
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 功能:cghAllocator空间配置器的实现代码 ******************************************************************/ #ifndef _CGH_ALLOC_ #define _CGH_ALLOC_ #include <new> #include <cstddef> #include <cstdlib> #include <climits> #include <iostream> namespace CGH { #pragma region 内存分配和释放函数、元素的构造和析构函数 // 内存分配 template<class T> inline T* _allocate(ptrdiff_t size, T*) { set_new_handler(0); T* tmp = (T*)(::operator new((size_t)(size * sizeof(T)))); if (tmp == 0) { std::cerr << "out of memory" << std::endl; exit(1); } return tmp; } // 内存释放 template<class T> inline void _deallocate(T* buffer) { ::operator delete(buffer); } // 元素构造 template<class T1, class T2> inline void _construct(T1* p, const T2& value) { new(p)T1(value); } // 元素析构 template<class T> inline void _destroy(T* ptr) { ptr->~T(); } #pragma endregion #pragma region cghAllocator空间配置器的实现 template<class T> class cghAllocator { public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; template<class U> struct rebind { typedef cghAllocator<U> other; }; static pointer allocate(size_type n, const void* hint = 0) { return _allocate((difference_type)n, (pointer)0); } static void deallocate(pointer p, size_type n) { _deallocate(p); } static void deallocate(void* p) { _deallocate(p); } void construct(pointer p, const T& value) { _construct(p, value); } void destroy(pointer p) { _destroy(p); } pointer address(reference x) { return (pointer)&x; } const_pointer const_address(const_reference x) { return (const_pointer)&x; } size_type max_size() const { return size_type(UINT_MAX / sizeof(T)); } }; #pragma endregion #pragma region 封装STL标准的空间配置器接口 template<class T, class Alloc = cghAllocator<T>> class simple_alloc { public: static T* allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::allocate(n*sizeof(T)); } static T* allocate(void) { return (T*)Alloc::allocate(sizeof(T)); } static void deallocate(T* p, size_t n) { if (0 != n)Alloc::deallocate(p, n*sizeof(T)); } static void deallocate(void* p) { Alloc::deallocate(p); } }; #pragma endregion } #endif
classcghAllocator是空间配置器类的定义,主要的四个函数的意义如下:allocate函数分配内存,deallocate函数释放内存,construct构造元素,destroy析构元素。这四个函数最终都是通过调用_allocate、_deallocate、_construct、_destroy这四个内联函数实现功能。
我们自己写的空间配置器必须封装一层STL的标准接口,
template<classT, class Alloc = cghAllocator<T>> classsimple_alloc
红黑树作为set的底层数据结构,其实现比较复杂,这里不展开讲了,有兴趣的童鞋请移步另一篇博客:STL简单红黑树的实现
cghSet的内部结构可以分为以下部分:
1. 一堆typedef,注意,set的底层数据结构是红黑树,cghSet自己没有成员变量,所有的操作均转而调用红黑树的API,有关STL红黑树的实现,请移步:STL简单红黑树的实现;
2. cghSet的构造与析构函数;
3. 提供给用户的api
cghSet的代码注释已经写得很详细了,童鞋们可以参考cghSet的内部结构来总体把握cghSet的框架,通过注释来理解cghSet的工作原理。
cghSet.h
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 文件内容:cghSet的实现 ******************************************************************/ #ifndef _CGH_SET_ #define _CGH_SET_ #include "globalConstruct.h" #include "cghAlloc.h" #include "rb_tree.h" namespace CGH{ /* 默认情况下采用递增排序 */ template<class key, class compare = std::less<key>, class Alloc = cghAllocator<key>> class cghSet{ #pragma region typedef private: typedef key key_type; typedef key value_type; typedef compare key_compare; typedef compare value_compare; typedef cgh_rb_tree<key_type, value_type, std::identity<value_type>, key_compare, Alloc> rep_type; rep_type t; public: typedef typename rep_type::const_pointer pointer; typedef typename rep_type::const_pointer const_pointer; typedef typename rep_type::const_reference reference; typedef typename rep_type::const_reference const_ference; typedef typename rep_type::iterator iterator; typedef typename rep_type::size_type size_type; typedef typename rep_type::difference_type difference_type; #pragma endregion #pragma region 构造函数 public: cghSet() :t(compare()){} cghSet(const cghSet<key, compare, Alloc>&x) :t(x.t){} cghSet<key, compare, Alloc>& operator=(const cghSet<key, compare, Alloc>&x) { t = x.t; return *this; } #pragma endregion #pragma region 提供给用户API /* 返回键比较函数 */ key_compare key_comp()const{ return t.key_comp(); } /* 返回值比较函数 */ value_compare value_comp()const{ return t.key_comp(); } /* 返回迭代器,指定set的第一个元素 */ iterator begin(){ return t.begin(); } /* 返回迭代器,指定set的最后一个元素 */ iterator end(){ return t.end(); } /* set是否为空 */ bool empty() const{ return t.empty(); } /* set大小 */ size_type size()const{ return t.size(); } /* set最大容量 */ size_type max_size()const{ return t.max_size(); } /* 插入元素到set中 */ std::pair<iterator, bool> insert(const value_type&x) { std::pair<typename rep_type::iterator, bool> p = t.insert_unique(x); return std::pair<iterator, bool>(p.first, p.second); } /* 返回迭代器,指向要查找的元素 如果没有找到,返回end */ iterator find(const key_type&x){ return t.find(x); } #pragma endregion }; } #endif
cghMultiset的内部结构可以分为以下部分:
1. 一堆typedef,注意,multiset的底层数据结构是红黑树,multiset自己没有成员变量,所有的操作均转而调用红黑树的API,有关STL红黑树的实现,请移步:STL简单红黑树的实现;
2. cghMultiset的构造与析构函数;
3. 提供给用户的api
cghMultiset.h
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 文件内容:cghMultiset的实现 ******************************************************************/ #ifndef _CGH_MULTI_SET_ #define _CGH_MULTI_SET_ #include "globalConstruct.h" #include "cghAlloc.h" #include "rb_tree.h" namespace CGH{ template<class key, class compare = std::less<key>, class Alloc = cghAllocator<key>>/* 默认情况下采用递增排序 */ class cghMultiset{ #pragma region typedef private: typedef key key_type; typedef key value_type; typedef compare key_compare; typedef compare value_compare; typedef cgh_rb_tree<key_type, value_type, std::identity<value_type>, key_compare, Alloc> rep_type; rep_type t; public: typedef typename rep_type::const_pointer pointer; typedef typename rep_type::const_pointer const_pointer; typedef typename rep_type::const_reference reference; typedef typename rep_type::const_reference const_ference; typedef typename rep_type::iterator iterator; typedef typename rep_type::size_type size_type; typedef typename rep_type::difference_type difference_type; #pragma endregion #pragma region 构造函数 public: cghMultiset() :t(compare()){} cghMultiset(const cghMultiset<key, compare, Alloc>&x) :t(x.t){} cghMultiset<key, compare, Alloc>& operator=(const cghMultiset<key, compare, Alloc>&x) { t = x.t; return *this; } #pragma endregion #pragma region 提供给用户的API /* 返回键比较函数 */ key_compare key_comp()const{ return t.key_comp(); } /* 返回值比较函数 */ value_compare value_comp()const{ return t.key_comp(); } /* 返回迭代器,指定cghMultiset的第一个元素 */ iterator begin(){ return t.begin(); } /* 返回迭代器,指定cghMultiset的最后一个元素 */ iterator end(){ return t.end(); } /* cghMultiset是否为空 */ bool empty() const{ return t.empty(); } /* cghMultiset大小 */ size_type size()const{ return t.size(); } /* cghMultiset最大容量 */ size_type max_size()const{ return t.max_size(); } /* 插入元素到cghMultiset中 */ iterator insert(const value_type&x) { //std::pair<typename rep_type::iterator, bool> p = t.insert_equal(x); return t.insert_equal(x); } /* 返回迭代器,指向要查找的元素 如果没有找到,返回end */ iterator find(const key_type&x){ return t.find(x); } #pragma endregion }; } #endif
测试环节的主要内容已在注释中说明
test_cghSet.cpp
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 文件内容:cghSet的测试 ******************************************************************/ #include "stdafx.h" #include "cghSet.h" using namespace::std; int _tmain(int argc, _TCHAR* argv[]) { using namespace::CGH; cghSet<int> test; test.insert(3); test.insert(2); test.insert(5); test.insert(4); test.insert(1); test.insert(5); std::cout << endl << "乱序插入:3,2,5,4,1,5" << endl << endl; std::cout << "经set容器排序后的结果:" << endl << endl; for (cghSet<int>::iterator iter = test.begin(); iter != test.end(); ++iter) { std::cout << *iter << ", "; } std::cout << endl << endl << "set不允许插入重复的键值,我们两次插入5,但是输出结果里,5只出现了一次"; std::cout << endl << endl << "----------------------"; std::cout << endl << endl << "使用find函数在set中找值为3的元素" << endl << endl; cghSet<int>::iterator iter = test.find(3); if (iter != test.end()) { std::cout << "iter != test.end(),找到了,*iter = " << *iter; } std::cout << endl << endl << "----------------------"; std::cout << endl << endl << "使用find函数在set中找值为6的元素" << endl << endl; cghSet<int>::iterator iter2 = test.find(6); if (iter2 == test.end()) { std::cout << "iter2 == test.end(),没有找到"; } std::cout << endl << endl << "----------------------"; std::cout << endl << endl << "set的大小:" << test.size() << endl << endl; system("pause"); return 0; }
测试结果如下图所示
测试环节的主要内容已在注释中说明
test_cghMultiset.cpp
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 文件内容:cghMultiset的测试 ******************************************************************/ #include "stdafx.h" #include "cghMultiset.h" using namespace::std; int _tmain(int argc, _TCHAR* argv[]) { using namespace::CGH; cghMultiset<int> test; test.insert(3); test.insert(2); test.insert(5); test.insert(4); test.insert(1); test.insert(5); std::cout << endl << "乱序插入:3,2,5,4,1,5" << endl << endl; std::cout << "经set容器排序后的结果:" << endl << endl; for (cghMultiset<int>::iterator iter = test.begin(); iter != test.end(); ++iter) { std::cout << *iter << ", "; } std::cout << endl << endl << "multiset允许插入重复的键值,我们两次插入5,于是输出结果里,5出现了两次"; std::cout << endl << endl << "----------------------"; std::cout << endl << endl << "使用find函数在set中找值为3的元素" << endl << endl; cghMultiset<int>::iterator iter = test.find(3); if (iter != test.end()) { std::cout << "iter != test.end(),找到了,*iter = " << *iter; } std::cout << endl << endl << "----------------------"; std::cout << endl << endl << "使用find函数在set中找值为6的元素" << endl << endl; cghMultiset<int>::iterator iter2 = test.find(6); if (iter2 == test.end()) { std::cout << "iter2 == test.end(),没有找到"; } std::cout << endl << endl << "----------------------"; std::cout << endl << endl << "set的大小:" << test.size() << endl << endl; system("pause"); return 0; }
测试结果如下图所示