From cfbf47891485ea7eeefce43e4f923399adb3bada Mon Sep 17 00:00:00 2001 From: lion187 Date: Sun, 18 Nov 2018 14:03:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=202.11=20=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E5=92=8C=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lion187 --- Chapter2 C与C++/2.11 容器和模板.md | 189 ++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 4 deletions(-) diff --git a/Chapter2 C与C++/2.11 容器和模板.md b/Chapter2 C与C++/2.11 容器和模板.md index 93e3cbe..1750915 100644 --- a/Chapter2 C与C++/2.11 容器和模板.md +++ b/Chapter2 C与C++/2.11 容器和模板.md @@ -5,7 +5,7 @@ C++ 提供了一些用于泛型编程的工具,包括各种标准容器、迭 ## 2.11.1 C++ 标准模板库 STL -C++ 标准模板库,提供了很多常用的容器和算法。以往在 C 语言中需要自己实现的排序算法,在 C++ 中只要使用 Vector 容器和 sort() 方法即可轻松实现。 +C++ 标准模板库,提供了很多常用的容器和算法。以往在 C 语言中需要自己实现的排序算法,在 C++ 中只要使用 vector 容器和 sort() 方法即可轻松实现。 就像算法常常以数据结构为基础一样,STL 中的算法,主要围绕容器来实现。而容器则是以模板为基础实现的。最常用的有 10 大标准容器,分为 3 大类: @@ -32,15 +32,194 @@ C++ 标准模板库,提供了很多常用的容器和算法。以往在 C 语 ### 2.11.1.1 Vector +是一个线性顺序结构。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将 vector 看作动态数组。 + +在创建一个 vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由 vector 默认指定,这个大小即 capacity() 函数的返回值。当存储的数据超过分配的空间时 vector 会重新分配一块内存块,但这样的分配是很耗时的,在重新分配空间时它会做这样的动作: + +1. 首先,vector 会申请一块更大的内存块; +2. 然后,将原来的数据拷贝到新的内存块中; +3. 其次,销毁掉原内存块中的对象(调用对象的析构函数); +4. 最后,将原来的内存空间释放掉。 + +如果 vector 保存的数据量很大时,这样的操作一定会导致糟糕的性能(这也是 vector 被设计成比较容易拷贝的值类型的原因)。所以说 vector 不是在什么情况下性能都好,只有在预先知道它大小的情况下 vector 的性能才是最优的。 + +vector 的特点: + +1. 指定一块如同数组一样的连续存储,但空间可以动态扩展。即它可以像数组一样操作,并且可以进行动态操作。通常体现在 push_back() pop_back(); +2. 随机访问方便,它像数组一样被访问,即支持[ ] 操作符和 vector.at(); +3. 节省空间,因为它是连续存储,在存储数据的区域都是没有被浪费的,但是要明确一点 vector 大多情况下并不是满存的,在未存储的区域实际是浪费的; +4. 在内部进行插入、删除操作效率非常低,这样的操作基本上是被禁止的。Vector 被设计成只能在后端进行追加和删除操作,其原因是 vector 内部的实现是按照顺序表的原理; +5. 只能在 vector 的最后进行 push 和 pop ,不能在 vector 的头进行 push 和pop ; +6. 当动态添加的数据超过 vector 默认分配的大小时要进行内存的重新分配、拷贝与释放,这个操作非常消耗性能。 所以要 vector 达到最优的性能,最好在创建 vector 时就指定其空间大小。 + ### 2.11.1.2 List +是一个线性链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。 + +由于其结构的原因,list 随机检索的性能非常的不好,因为它不像 vector 那样直接找到元素的地址,而是要从头一个一个的顺序查找,这样目标元素越靠后,它的检索时间就越长。检索时间与目标元素的位置成正比。 + +虽然随机检索的速度不够快,但是它可以迅速地在任何节点进行插入和删除操作。因为 list 的每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响,不像 vector 会对操作点之后的所有元素的存储地址都有所影响,这一点是 vector 不可比拟的。 + +list 的特点: + +1. 不使用连续的内存空间这样可以随意地进行动态操作; +2. 可以在内部任何位置快速地插入或删除,当然也可以在两端进行 push 和pop; +3. 不能进行内部的随机访问,即不支持[ ] 操作符和 list.at(); +4. 相对于 verctor 占用更多的内存。 + ### 2.11.1.3 Map -### 2.11.1.4 Set +Map 属于关联容器,是一种非线性的树结构,具体的说采用的是一种比较高效的特殊的平衡检索二叉树。 + +Map 提供一种“键- 值”关系的一对一的数据存储能力。其“键”在容器中不可重复,且按一定顺序排列。 + +关联容器的特点是明显的,相对于顺序容器,有以下几个主要特点: + +1. 其内部实现是采用非线性的二叉树结构,具体的说是红黑树的结构原理实现的; +2. set 和 map 保证了元素的唯一性,mulset 和 mulmap 扩展了这一属性,可以允许元素不唯一; +3. 元素是有序的集合,默认在插入的时候按升序排列。 ## 2.11.2 迭代器 -## 2.11.3 模板类 +迭代器是一个对象,可以循环访问 STL 容器中的元素,并提供对各个元素的访问。 STL 容器全都提供迭代器,以便算法可以采用标准方式访问其元素,而不必考虑用于存储元素的容器类型。 + +可以通过使用成员和全局函数(如 begin() 和 end())以及运算符(如 ++ 和 -- )向前或向后移动,来显式使用迭代器。 还可以通过范围 for 循环或(对于某些迭代器类型)下标运算符 [],来隐式使用迭代器。 + +在 STL 中,序列或范围的开头是第一个元素。 序列或范围的末尾始终定义为最后一个元素的下一个位置。 + +## 2.11.3 STL 容器示例 + +vector、list、map 以及迭代器的示例如下: + +```cpp +/** + * @file main.cpp + */ +#include +#include +#include +#include + +using namespace std; + +void ExpOfVector(void) +{ + vector MyVec; + + MyVec.push_back(3.14*2); + MyVec.resize(0); + MyVec.push_back(3.14); + MyVec.push_back(3.14/2); + MyVec.push_back(3.14/4); + MyVec.push_back(3.14/8); + MyVec.insert(MyVec.begin()+1, 5.0); // 在 MyVec 的第1个元素(从第0个算起)的位置插入数值为 5.0 的元素. + + MyVec.pop_back(); + cout<<"Vector Size="< MyList(5); + + MyList.clear(); + + MyList.push_front(3.14*2); + MyList.push_front(3.14*3); + MyList.push_front(3.14*4); + MyList.push_back(3.14*1); + + // 依次输出 MyList 中的每个元素值: 3.14*4、3.14*3、3.14*2、3.14*1. + for(list::iterator itr=MyList.begin(); + itr!=MyList.end(); + itr++) + { + cout<<*itr<::iterator itr=MyList.begin(); + itr!=MyList.end(); + itr++) + { + cout<<*itr<::iterator itr=MyList.begin(); + itr!=MyList.end(); + itr++) + { + cout<<*itr< MyMap; + + MyMap.insert(map::value_type(1, "Student1")); + MyMap.insert(map::value_type(2, "Student2")); + MyMap[3] = "Student3"; + + MyMap.erase(2); // 删除 Key 值为 2 的元素. + + map::iterator itr; + + itr = MyMap.find(3); + if(itr != MyMap.end()) + { + cout<<"The Key="<first<