目录
一、迭代器
1.定义
迭代器 简介. 迭代器 (iterator)有时又称 光标 (cursor)是程序设计的 软件设计模式 ,可在容器对象(container,例如 链表 或 数组 )上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。. 各种语言实现迭代器的方式皆不尽同,有些面向对象语言像 Java, C#, Ruby, Python, Delphi 都已将迭代器的特性内置语言当中,完美的跟语言集成,我们称之隐式迭代器(implicit iterator),但像是C++语言本身就没有迭代器的特色,但STL仍利用 模板 实现了功能强大的迭代器。
2.关系
访问容器中的元素需要通过迭代器进行,迭代器是算法与容器交流的桥梁。
3.适用前提
无论是序列容器还是关联容器,最常做的操作无疑是遍历容器中存储的元素,而实现此操作,多数情况会选用“迭代器(iterator)”来实现。那么,迭代器到底是什么呢?
我们知道,尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等需要对数据进行遍历的操作方法应该是类似的。
既然类似,完全可以利用泛型技术,将它们设计成适用所有容器的通用算法,从而将容器和算法分离开。但实现此目的需要有一个类似中介的装置,它除了要具有对容器进行遍历读写数据的能力之外,还要能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据。
这是泛型思维发展的必然结果,于是迭代器就产生了。简单来讲,迭代器和 C++ 的指针非常类似,它可以是需要的任意类型,通过迭代器可以指向容器中的某个元素,如果需要,还可以对该元素进行读/写操作。
4.定义方法及实现
请参照代码
#include <iostream>
using namespace std;
struct List
{
int value;
List* pNext;
};
void AddList(List*& rpHead, List*& rpEnd, int n)
{
List* pTemp = new List;
pTemp->value = n;
pTemp->pNext = NULL;
if (rpHead == NULL)
{
rpHead = pTemp;
}
else
{
rpEnd->pNext = pTemp;
}
rpEnd = pTemp;
}
class Iterator
{
private:
List* pMark;
public:
Iterator(List* p)
{
pMark = p;
}
public:
bool operator!=(List* p)
{
if (pMark != p)
{
return true;
}
else
{
return false;
}
}
int operator*()
{
return pMark->value;
}
List* operator++(int)
{
List* pTemp = pMark;
pMark = pMark->pNext;
return pTemp;
}
};
int main()
{
List* pHead = NULL;
List* pEnd = NULL;
AddList(pHead, pEnd, 1);
AddList(pHead, pEnd, 2);
AddList(pHead, pEnd, 3);
AddList(pHead, pEnd, 4);
List* pMark = pHead;
Iterator ite = pHead;
while (ite != NULL)
{
cout << *ite << endl;
ite++;
}
/*while (pMark != NULL)
{
cout << pMark->value << " ";
pMark = pMark->pNext;
}
cout << endl;*/
return 0;
}
输出:
1
2
3
4
D:\c++\迭代器\x64\Debug\迭代器.exe (进程 8692)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
二、容器
1.定义
首先,我们必须理解一下什么是容器,在C++ 中容器被定义为:在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。并且容器解决了代码的冗余问题使方案执行起来更加高效快捷
2.一些常用的容器
STL 对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器。
顺序性容器 是 一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。这个位置和 元素本身无关,而和操作的时间和地点有关,顺序性容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。比如我们一次性对一个顺序性容器追加三 个元素,这三个元素在容器中的相对位置和追加时的逻辑次序是一致的。
关联式容器 和 顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置 入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。
关联式容器另一个显著的特点是它是以键值的方式来保存数据,就是说它能把关键字和值关联起来保存,而顺序性容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)。这在下面具体的容器类中可以说明这一点。
容器适配器 是一个比较抽象的概念, C++的 解释是:适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器是让一种已存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机 制。其实仅是发生了接口转换。那么你可以把它理解为容器的容器,它实质还是一个容器,只是他不依赖于具体的标准容器类型,可以理解是容器的模版。或者把它 理解为容器的接口,而适配器具体采用哪种容器类型去实现,在定义适配器的时候可以由你决定。
1.array
#include <iostream>
#include <array>
using namespace std;
int main()
{
array<int, 5> arr = { 1,2,3,4,5};
//容器类名 + :: + iterator + 名字
array<int, 5>::iterator ite = arr.begin();
while (ite != arr.end())
{
cout << *ite << endl;
ite++;
}
/*for (int i = 0; i < 5; i++)
{
cout << arr[i] << " ";
}*/
cout << endl;
/*cout << arr.front() << endl;
cout << arr.back() << endl;
cout << arr.size() << endl;*/
/*cout << arr.at(5) << endl;*/
system("pause");
return 0;
}
2.vector
向量 vector :
是一个线性顺序结构。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。
在创建一个vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定,这个大小即capacity ()函数的返回值。当存储的数据超过分配的空间时vector 会重新分配一块内存块,但这样的分配是很耗时的,在重新分配空间时它会做这样的动作:
首先,vector 会申请一块更大的内存块;
然后,将原来的数据拷贝到新的内存块中;
其次,销毁掉原内存块中的对象(调用对象的析构函数);
最后,将原来的内存空间释放掉。
如果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 时就指定其空间大小。
#include <iostream>
#include <vector>
using namespace std;
//
int main()
{
// //vector<int> vec(10,5);//10个元素 默认初始化为0
// //
//
//
// //vec[0] = 1;
// //vec[1] = 2;
// //vec.push_back(100);
// //cout << vec.size() << " " << vec.capacity() << endl;\
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);
vector<int>::iterator ite = vec.begin();
while (ite != vec.end())
{
if (*ite == 3)
{
//ite = vec.erase(ite);
ite = vec.insert(ite, 100);
cout << *ite << " ";
ite++;
}
cout << *ite << " ";
ite++;
}
cout << endl;
//
// vector<int> vec;
//
// for (int i = 0; i < 15; i++)
// {
// vec.push_back(i);
// cout << vec.size() << " " << vec.capacity() << endl;
// }
//
//
// /*for (int i = 0; i < 10; i++)
// {
// cout << vec[i] << " " ;
// }
// cout << endl;*/
system("pause");
return 0;
}
3.list
是一个线性链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。
由于其结构的原因,list 随机检索的性能非常的不好,因为它不像vector 那样直接找到元素的地址,而是要从头一个一个的顺序查找,这样目标元素越靠后,它的检索时间就越长。检索时间与目标元素的位置成正比。
虽然随机检索的速度不够快,但是它可以迅速地在任何节点进行插入和删除操作。因为list 的每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响,不像vector 会对操作点之后的所有元素的存储地址都有所影响,这一点是vector 不可比拟的。
list 的特点:
(1) 不使用连续的内存空间这样可以随意地进行动态操作;
(2) 可以在内部任何位置快速地插入或删除,当然也可以在两端进行push 和pop 。
(3) 不能进行内部的随机访问,即不支持[ ] 操作符和vector.at() ;
(4) 相对于verctor 占用更多的内存。
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> lst;
lst.push_back(1);
lst.push_back(2);
lst.push_back(3);
lst.push_back(4);
lst.push_front(5);
lst.pop_back();
lst.pop_front();
//lst.clear();
/*list<int>::iterator ite = lst.begin();
while (ite != lst.end())
{
cout << *ite << endl;
ite++;
}*/
cout << lst.front() << endl;
cout << lst.back() << endl;
cout << *lst.begin() << endl;
//cout << *lst.end() << endl;
system("pause");
return 0;
}
4.map
#include <iostream>
#include <map>
using namespace std;
int main()
{
/*map<char, int> mp;*/
multimap<char, int> mp;
/*mp['a'] = 100;
mp['b'] = 200;
mp['c'] = 300;
mp['d'] = 400;
mp['b'] = 300;*/
pair<char, int> pr('e', 500);
pair<char, int> pr1('c', 500);
pair<char, int> pr2('c', 500);
mp.insert(pr);
mp.insert(pr1);
mp.insert(pr2);
map<char, int>::iterator ite = mp.begin();
while (ite != mp.end())
{
cout << ite->first << "\t" << ite->second << endl;
ite++;
}
ite = mp.find('c');
//ite->first = 'e'; 依然不可被修改
ite->second = 700;
/*mp.insert(pr);
mp.insert(pr1);*/
//ite = mp.find('e');
ite->first = 'f'; 键值不可被修改是常量 实值可以修改
//ite->second = 400;
//mp.erase('a');
return 0;
}
5.unordered_map
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
unordered_map<int, char> mp;
mp[200] = 'B';
mp[100] = 'A';
mp[300] = 'C';
mp[400] = 'D';
mp[400] = 'X';
unordered_map<int, char>::iterator ite1 = mp.begin();
while (ite1 != mp.end())
{
cout << ite1->first << "\t" << ite1->second << endl;
ite1++;
}
cout << "---------------------------------" << endl;
unordered_multimap<int, char> mp1;
pair<int, char> pr1(200, 'B');
pair<int, char> pr2(200, 'D');
pair<int, char> pr3(300, 'C');
pair<int, char> pr4(100, 'A');
pair<int, char> pr5(100, 'X');
mp1.insert(pr1);
mp1.insert(pr2);
mp1.insert(pr3);
mp1.insert(pr4);
mp1.insert(pr5);
unordered_multimap<int, char>::iterator ite2 = mp1.begin();
while (ite2 != mp1.end())
{
cout << ite2->first << "\t" << ite2->second << endl;
ite2++;
}
return 0;
}
6.set
#include <iostream>
using namespace std;
#include <set>
// 去重
int main()
{
set<int> s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
set<int> ::iterator ite = s.begin();
while (ite != s.end())
{
cout << *ite << " ";
ite++;
}
return 0;
}
7.适配器
STL 中包含三种适配器:栈stack 、队列queue 和优先级priority_queue 。
适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现,即可以把适配器看作“它保存一个容器,这个容器再保存所有元素”。
STL 中提供的三种适配器可以由某一种顺序容器去实现。默认下stack 和queue 基于deque 容器实现,priority_queue 则基于vector 容器实现。当然在创建一个适配器时也可以指定具体的实现容器,创建适配器时在第二个参数上指定具体的顺序容器可以覆盖适配器的默认实现。
由于适配器的特点,一个适配器不是可以由任一个顺序容器都可以实现的。
栈stack 的特点是后进先出,所以它关联的基本容器可以是任意一种顺序容器,因为这些容器类型结构都可以提供栈的操作有求,它们都提供了push_back 、pop_back 和back 操作;
队列queue 的特点是先进先出,适配器要求其关联的基础容器必须提供pop_front 操作,因此其不能建立在vector 容器上;
优先级队列priority_queue 适配器要求提供随机访问功能,因此不能建立在list 容器上。
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
int main()
{
queue<int> que;
que.push(1);
que.push(2);
que.push(3);
while (que.empty() != true)
{
cout << que.front() << " ";
que.pop();
}
cout << endl;
cout << "-----------------------------------" << endl;
stack<int> sta;
sta.push(1);
sta.push(2);
sta.push(3);
while (sta.empty() == false)
{
cout << sta.top() << " ";
sta.pop();
}
cout << endl;
return 0;
}
输出:
1 2 3
-----------------------------------
3 2 1
D:\c++\迭代器\x64\Debug\迭代器.exe (进程 9888)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
3.底层
array vector 数组
list 双向链表
deque 多段连续空间
map set 红黑树
UNordered_map UNordered_set 哈希表
4.算法函数
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
void Print(int value)
{
cout << value << endl;
}
int main()
{
list<int> lst;
lst.push_back(1);
lst.push_back(2);
lst.push_back(3);
lst.push_back(4);
::for_each(lst.begin(), lst.end(), &Print);
list<int>::iterator ite = ::find(lst.begin(), lst.end(), 3);
cout << *ite << endl;
return 0;
}
输出:
1
2
3
4
3
D:\c++\迭代器\x64\Debug\迭代器.exe (进程 20516)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
总结
容器还有另一个特点是容器可以自行扩展。在解决问题时我们常常不知道我们需要存储多少个对象,也就是说我们不知道应该创建多大的内存空间来保存我们的对象。 显然,数组在这一方面也力不从心。容器的优势就在这里,它不需要你预先告诉它你要存储多少对象,只要你创建一个容器对象,并合理的调用它所提供的方法,所 有的处理细节将由容器来自身完成。它可以为你申请内存或释放内存,并且用最优的算法来执行您的命令。
容器是随着面向对象语言的诞生而提出的,容器类在面向对象语言中特别重要,甚至它被认为是早期面向对象语言的基础。在现在几乎所有的面向对象的语言中也都伴随着一个容器集,在C++ 中,就是标准模板库(STL )。
和其它语言不一样,C++ 中处理容器是采用基于模板的方式。标准C++ 库中的容器提供了多种数据结构,这些数据结构可以与标准算法一起很好的工作,这为我们的软件开发提供了良好的支持!