完成 2.11 容器和模板

Signed-off-by: lion187 <cy187lion@sina.com>
This commit is contained in:
lion187 2018-11-18 14:03:48 +08:00
parent e085eba5fe
commit cfbf478914
1 changed files with 185 additions and 4 deletions

View File

@ -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 <vector>
#include <list>
#include <map>
#include <iostream>
using namespace std;
void ExpOfVector(void)
{
vector<double> 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="<<MyVec.size()<<endl; // 输出 4.
cout<<"Vector First="<<MyVec.front()<<endl; // 返回 MyVec 的第一个元素 , 3.14.
cout<<"Vector Last="<<MyVec.back()<<endl; // 返回 MyVec 的最后一个元素, 3.14/4.
cout<<"Vector At 1="<<MyVec.at(1)<<endl; // 返回 MyVec 的第二个元素, 5.0.
// 依次输出 MyVec 中的每个元素值.
for(vector<double>::iterator itr=MyVec.begin();
itr!=MyVec.end();
itr++)
{
cout<<*itr<<endl;
}
MyVec.clear();
cout<<"Vector Size="<<MyVec.size()<<endl;
cout<<"---------------------------------------"<<endl;
}
void ExpOfList(void)
{
list<double> 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<double>::iterator itr=MyList.begin();
itr!=MyList.end();
itr++)
{
cout<<*itr<<endl;
}
MyList.pop_back();
MyList.pop_front();
// 依次输出 MyList 中的每个元素值: 3.14*3、3.14*2.
for(list<double>::iterator itr=MyList.begin();
itr!=MyList.end();
itr++)
{
cout<<*itr<<endl;
}
MyList.insert(MyList.begin()++, 5.0);
// 依次输出 MyList 中的每个元素值: 5.0、3.14*3、3.14*2.
for(list<double>::iterator itr=MyList.begin();
itr!=MyList.end();
itr++)
{
cout<<*itr<<endl;
}
cout<<"Vector Size="<<MyList.size()<<endl;
cout<<"---------------------------------------"<<endl;
}
void ExpOfMap(void)
{
map<int, string> MyMap;
MyMap.insert(map<int, string>::value_type(1, "Student1"));
MyMap.insert(map<int, string>::value_type(2, "Student2"));
MyMap[3] = "Student3";
MyMap.erase(2); // 删除 Key 值为 2 的元素.
map<int, string>::iterator itr;
itr = MyMap.find(3);
if(itr != MyMap.end())
{
cout<<"The Key="<<itr->first<<endl;
cout<<"The Value="<<itr->second<<endl;
}
else
cout<<"Do not Find"<<endl;
itr = MyMap.find(2);
if(itr != MyMap.end())
{
cout<<"The Key="<<itr->first<<endl;
cout<<"The Value="<<itr->second<<endl;
}
else
cout<<"Do not Find"<<endl;
cout<<"---------------------------------------"<<endl;
}
int main(void)
{
ExpOfVector();
ExpOfList();
ExpOfMap();
return 0;
}
```
## 2.11.4 模板类
使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型 int 类型的 swap 函数,这个函数就只能实现 int 型,对 double字符这些类型无法实现要实现这些类型的交换就要重新编写另一个 swap 函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个 swap 模板函数,即可以实现 int 型,又可以实现 double 型的交换。
@ -112,4 +291,6 @@ void main(){
## 练习
基于模板实现堆栈操作。
1、 基于模板实现堆栈操作。
2、 使用 Vector 进行从小到大排序。