|
| 1 | +# C++ STL概述 |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +## stl六大组件 |
| 6 | + |
| 7 | +1.容器 |
| 8 | + |
| 9 | +2.迭代器 |
| 10 | + |
| 11 | +3.分配器 |
| 12 | + |
| 13 | +4.算法 |
| 14 | + |
| 15 | +5.仿函数 |
| 16 | + |
| 17 | +6.适配器 |
| 18 | + |
| 19 | + 其中分配器负责容器的内存管理,迭代器提供了访问容器元素的方法,容器用来存放元素,算法用来处理容器中的数据。 |
| 20 | + |
| 21 | + STL采用的编程方法GP,和OOP不同。OOP把数据和处理数据的方法组合在一个类中,而GP尝试将数据和方法分开。STL的数据存放在容器中,而处理数据的方法放在算法中,而迭代器作为沟通容器和算法的桥梁。 |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +## 分配器 |
| 26 | + |
| 27 | +在我们创建一个对象时,会有两步操作: |
| 28 | + |
| 29 | +1.申请对象所需要的内存空间 |
| 30 | + |
| 31 | +2.构造对象。 |
| 32 | + |
| 33 | +当我们销毁一个对象时,也有两步操作: |
| 34 | + |
| 35 | +1.析构对象, |
| 36 | + |
| 37 | +2 释放给对象分配的空间。 |
| 38 | + |
| 39 | +而分配器的主要功能就是内存空间的分配和回收,对象的构造和析构。 |
| 40 | + |
| 41 | +### 内存空间的分配和回收 |
| 42 | + |
| 43 | +```C++ |
| 44 | +//申请空间 |
| 45 | +template <class T> |
| 46 | +T* allocator<T>::allocate() |
| 47 | +{ |
| 48 | + return static_cast<T*>(::operator new(sizeof(T))); |
| 49 | +} |
| 50 | +template <class T> |
| 51 | +T* allocator<T>::allocate(size_type n) |
| 52 | +{ |
| 53 | + if (n == 0) |
| 54 | + return nullptr; |
| 55 | + return static_cast<T*>(::operator new(n * sizeof(T))); |
| 56 | +} |
| 57 | +template <class T> |
| 58 | + |
| 59 | +//释放空间 |
| 60 | +void allocator<T>::deallocate(T* ptr) |
| 61 | +{ |
| 62 | + if (ptr == nullptr) |
| 63 | + return; |
| 64 | + ::operator delete(ptr); |
| 65 | +} |
| 66 | +``` |
| 67 | +
|
| 68 | +### 对象构建和析构 |
| 69 | +
|
| 70 | +``` |
| 71 | +//创建对象 |
| 72 | +template <class Ty> |
| 73 | +void construct(Ty* ptr) |
| 74 | +{ |
| 75 | + ::new ((void*)ptr) Ty(); |
| 76 | +} |
| 77 | +template <class Ty1, class Ty2> |
| 78 | +void construct(Ty1* ptr, const Ty2& value) |
| 79 | +{ |
| 80 | + ::new ((void*)ptr) Ty1(value); |
| 81 | +} |
| 82 | +//析构对象 |
| 83 | +template <class Ty> |
| 84 | +void destroy (Ty* pointer) |
| 85 | +{ |
| 86 | + if (pointer != nullptr) |
| 87 | + { |
| 88 | + pointer->~Ty(); |
| 89 | + } |
| 90 | +} |
| 91 | +``` |
| 92 | +
|
| 93 | +## 迭代器 Iterators |
| 94 | +
|
| 95 | +迭代器是一种设计理念: |
| 96 | +迭代器的提供了一个遍历容器内部所有元素的接口,使得可以在不暴露容器内部实现的情况下,通过迭代器访问容器的元素。 |
| 97 | +除此之外,STL中迭代器一个最重要的作用就是作为容器与STL算法的粘结剂,只要容器提供迭代器的接口,不同的容器就可以使用同一套算法代码。 |
| 98 | +
|
| 99 | +
|
| 100 | +
|
| 101 | +迭代器重载了++ -- * []等运算符,使得我们可以使用迭代器访问容器,每种容器都定义了自己的迭代器,迭代器根据容器的内部构造的不同实现细节也不一样。 |
| 102 | +
|
| 103 | +容器都包含两个迭代器成员: |
| 104 | +
|
| 105 | +begin():指向容器首元素的迭代器; |
| 106 | +
|
| 107 | +end():指向容器最后一个元素后的迭代器,尾后迭代器。 |
| 108 | +
|
| 109 | +### list中的迭代器: |
| 110 | +
|
| 111 | +```C++ |
| 112 | +typedef T value_type; |
| 113 | +typedef list_iterator<T> self; |
| 114 | +base_ptr node_; |
| 115 | +reference operator*() const |
| 116 | +{ |
| 117 | + return node_->value; |
| 118 | +} |
| 119 | +pointer operator->() const |
| 120 | +{ |
| 121 | + return &(operator*()); |
| 122 | +} |
| 123 | +self& operator++() |
| 124 | +{ |
| 125 | + MYSTL_DEBUG(node_ != nullptr); |
| 126 | + node_ = node_->next; |
| 127 | + return *this; |
| 128 | +} |
| 129 | +self& operator--() |
| 130 | +{ |
| 131 | + MYSTL_DEBUG(node_ != nullptr); |
| 132 | + node_ = node_->prev; |
| 133 | + return *this; |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +### 迭代器的分类 |
| 138 | + |
| 139 | +input_iterator(输入迭代器):提供对数据的只读访问; |
| 140 | + |
| 141 | +output_iterator(输出迭代器): 提供对数据的只写访问; |
| 142 | + |
| 143 | +forward_iterator (前向迭代器):提供读写操作,并且能向前推进迭代器; |
| 144 | + |
| 145 | +bidirectional_iterator双向迭代器:提供读写操作,并且能向前向后操作; |
| 146 | + |
| 147 | +random_access_iterator(随机访问迭代器):提供读写操作,并能以跳跃的方式访问容器内的任意数据。 |
| 148 | + |
| 149 | +## 容器 |
| 150 | + |
| 151 | +容器分为顺序容器和关联容器: |
| 152 | +顺序容器提供了控制元素存储和访问顺序的能力,这种顺序不依赖于元素的值,而是与元素加入容器时的位置有关系。 |
| 153 | + |
| 154 | +而关联容器时根据关键字的值来存储元素。 |
| 155 | + |
| 156 | +顺序容器有: |
| 157 | + |
| 158 | +vector |
| 159 | + |
| 160 | + deque |
| 161 | + |
| 162 | + list |
| 163 | + |
| 164 | + forward_list |
| 165 | + |
| 166 | + array |
| 167 | + |
| 168 | +string |
| 169 | + |
| 170 | +关联容器: |
| 171 | + |
| 172 | +map |
| 173 | +set |
| 174 | +multimap |
| 175 | +multiset |
| 176 | +unordered_map |
| 177 | +unordered_ set |
| 178 | +unordered_multimap |
| 179 | +unordered_multiset |
| 180 | + |
| 181 | +### vector |
| 182 | + |
| 183 | +Vector中元素的存储方式是顺序存储,并且vector的存储空间是动态的,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。 |
| 184 | + |
| 185 | +vector结构体 |
| 186 | + |
| 187 | +``` |
| 188 | +struct _Vector_impl_data |
| 189 | +{ |
| 190 | + pointer start;//指向vector的头 |
| 191 | + pointer finish;//指向vector尾元素后面的位置 |
| 192 | + pointer end_of_storage;//指向vector存储空间的末尾 |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +vector的扩容策略:在vector中添加元素后,如果分配的空间不够用,vector会尝试申请当前空间大小二倍的新空间,然后把旧空间的元素复制过去,释放旧空间,更新指针。 |
| 197 | + |
| 198 | +```C++ |
| 199 | +void reallocate() |
| 200 | +{ |
| 201 | + auto n=(size()>0)?2*size():1; |
| 202 | + THROW_ERROR_IF(n > max_size(), |
| 203 | + "n can not larger than max_size() in vector<T>::reallocate(n)"); |
| 204 | + const auto old_size = size(); |
| 205 | + auto tmp = data_allocator::allocate(n); |
| 206 | + mystl::uninitialized_move(start, finish, tmp); |
| 207 | + data_allocator::deallocate(start,end_of_storage-start); |
| 208 | + start = tmp; |
| 209 | + end_of_storage= start + n; |
| 210 | +} |
| 211 | + |
| 212 | +``` |
| 213 | + |
| 214 | + |
| 215 | + |
| 216 | +### array |
| 217 | + |
| 218 | +array 容器的大小是固定的,无法动态的扩展或收缩。 |
| 219 | +相较于数组而言,array在不损失性能的情况下,泛用性强,并且安全性高。 |
| 220 | + |
| 221 | +### deque |
| 222 | + |
| 223 | +deque是双向队列,可以在容器的头和尾插入元素,deque的数据连续存储,可以动态的分配空间。 |
| 224 | + |
| 225 | +deque让使用者感觉其拥有线性连续空间,实际其内部的空间是一段一段的缓冲区,由map将它们串联在一起。 |
| 226 | + |
| 227 | +deque使用下面的结构体来控制元素的存储: |
| 228 | + |
| 229 | +迭代器: |
| 230 | + |
| 231 | +```C++ |
| 232 | + value_pointer cur; // 指向所在缓冲区的当前元素 |
| 233 | + value_pointer first; // 指向所在缓冲区的头部 |
| 234 | + value_pointer last; // 指向所在缓冲区的尾部 |
| 235 | + map_pointer node; // 缓冲区所在节点 |
| 236 | +``` |
| 237 | + |
| 238 | +```C++ |
| 239 | +iterator begin_; // 指向第一个节点 |
| 240 | +iterator end_; // 指向最后一个结点 |
| 241 | +map_pointer map_; // 指向一块 map,map 中的每个元素都是一个指针,指向一个缓冲区 |
| 242 | +size_type map_size_; // map 内指针的数目 |
| 243 | +``` |
| 244 | + |
| 245 | +deque是的存储空间扩充: |
| 246 | +当我们想deque中添加元素时,如果当前缓冲区没有空间,则会申请一个新的缓冲区,然后将新缓冲区的指针放到map中的相应位置(如果是在尾部添加元素就放到后面,头部添加元素就放在前面)。并且最开始的缓冲区指针放在map的中间,向两边扩充。如果map数组不够用了,就会创建一个新的更大map,然后把旧map中存储的指针拷贝过来,并更新以上元素的值。 |
| 247 | + |
| 248 | +### list |
| 249 | + |
| 250 | +list为双向链表,可以快速的在任何位置插入和删除元素。 |
| 251 | + |
| 252 | +list的内部时通过双向循环链表实现的。 |
| 253 | + |
| 254 | +### forward_list |
| 255 | + |
| 256 | +forward_list为单向链表,只能单向的访问。比list更能节省内存空间。 |
| 257 | + |
| 258 | +### set |
| 259 | + |
| 260 | +set内部通过红黑树(RBtree)实现,插入元素的效率高。 |
| 261 | + |
| 262 | +### unordered_set |
| 263 | + |
| 264 | +unordered_set通过散列表实现,查找元素的效率高。其采用分离链表法来处理哈希冲突。当散列表中元素的数量超过一定值时,为了保证查找的效率,散列表会采用某种策略进行再散列。 |
| 265 | + |
0 commit comments