迭代器:容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。算法必须通过迭代器才能访问容器中的元素
每个容器都有自己专属的迭代器。
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针。
vector
是 C++ 标准模板库(STL)中的一种序列式容器,类似于动态数组。它能够存储任意类型的元素,并在内存中以连续的空间进行存储,因此可以随机访问其中的元素。
主要特点
动态扩展 :
vector
的大小可以动态调整,在需要时会自动扩展容量。当添加元素导致超出当前容量时,它会分配一块更大的内存空间,将原有元素复制过去,然后继续添加新元素。随机访问 :由于底层是连续的内存存储,因此支持使用索引(如
vec[i]
)对元素进行快速随机访问。高效的插入和删除操作 :在容器尾部进行插入和删除操作效率较高,时间复杂度为常数级别(O(1))。但在容器中间或开头进行插入和删除操作时,可能需要移动大量元素,时间复杂度为线性级别(O(n))。
创建一个vector容器和对应的迭代器
#include<iostream>
#include<vector>
using namespace std;
int main(){
//创建一个基本的vector容器
vector<int> v;
//向vector容器中插入数据
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//创建一个vector迭代器
//v.begin()指向容器中第一个元素
vector<int>::iterator itBegin = v.begin();
//v.end()指向容器中最后一个元素的下一个位置(注意不是最后一个元素)
vector<int>::iterator itEnd = v.end();
cout << *itBegin << endl;
cout << *itEnd << endl;
}
这里要注意迭代器指针 v.begin()和v.end()的位置
通过迭代器遍历数据
第一种遍历方式
通过一个while循环遍历
vector<int>::iterator itBegin = v.begin(); // 定义起始迭代器
vector<int>::iterator itEnd = v.end(); // 定义结束迭代器
while(itBegin != itEnd){
cout << *itBegin << endl;
itBegin++;
}
通过图示说明迭代器的遍历过程如下:
这中写法较为容易理解,但不常用,我们来看第二种遍历方法。
第二种遍历方式
通过一个for循环遍历
for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout << *it << endl;
}
这种遍历算法是在循环内部生成一个迭代器,遍历过程与上图相同,相比于while遍历,代码简洁了些。较为常用
第三种遍历方式
利用STL提供的 for each 遍历方法
使用这个遍历方法需要引入头文件 #include<algorithm>
#include<algorithm>
#include<iostream>
using namespace std;
// 编写访问函数
void myPrint(int val){
cout << val << endl;
}
// 遍历起始位置,遍历结束位置,访问方法
for_each(v.begin(), v.end(), myPrint)
for_each() 遍历函数不需要编写循环体,当通过迭代器访问访问到一个元素时,需要自定义访问方法传入到 for_each() 中告诉函数如何访问该元素。就比如vector容器中存储的是对象时,迭代器不知道如何访问对象并输出结果,这个时候就需要定义访问访问方法告诉函数如何访问该对象中的某个数据类型。
我们看一下 for_each 内部的具体实现:
template <class _InIt, class _Fn>
_Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
for (; _UFirst != _ULast; ++_UFirst) {
_Func(*_UFirst);
}
return _Func;
}
不难发现,for_each 的基本思想与前面的 while循环 和 for循环 遍历方法是一样的,只不过对代码实现了进一步的封装,用户不需要编写循环体,但需要编写访问函数。
通过迭代器访问自定义数据
定义一个Person类,通过一个迭代器并结合for循环访问自定义数据
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Person{
public:
Person(string name, int age){
this->m_Name = name;
this->m_age = age;
}
string m_Name;
int m_age;
};
int main(){
vector<Person>v;
Person p1("aaa",10);
Person p2("bbb",20);
Person p3("ccc",30);
Person p4("ddd",40);
Person p5("eee",50);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
for(vector<Person>::iterator it = v.begin(); it != v.end(); it++){
cout << (*it).m_Name << endl;
cout << (*it).m_age << endl;
}
for(vector<Person>::iterator it = v.begin(); it != v.end(); it++){
cout << it->m_Name << endl;
cout << it->m_age << endl;
}
}
注意两种遍历中迭代器的表示方式,it 表示指向该对象的指针 *(it) 表示该对象
输出结果都是一样的
这里我们再使用 for_each() 遍历试一试
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
class Person{
public:
Person(string name, int age){
this->m_Name = name;
this->m_age = age;
}
string m_Name;
int m_age;
};
void myPrint(Person p){
cout << p.m_Name << endl;
cout << p.m_age << endl;
}
int main(){
vector<Person>v;
Person p1("aaa",10);
Person p2("bbb",20);
Person p3("ccc",30);
Person p4("ddd",40);
Person p5("eee",50);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
for_each(v.begin(),v.end(),myPrint);
}
这里需要注意的是,我们在定义访问函数的时候,这个函数的参数是指针还是自定义数据类型?
我们再看一下之前 for_each 的实现
template <class _InIt, class _Fn>
_Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
for (; _UFirst != _ULast; ++_UFirst) {
_Func(*_UFirst);
}
return _Func;
}
可以看到 _Func(*_UFirst) 这条语句,说明在执行访问函数时,传入的参数是自定义数据类型,所以我们的访问函数的参数也要设定为自定义数据类型。
输出的结果跟之前一样:
通过迭代器访问指针
与之前的遍历方式相同,只需要在声明容器时设置好数据类型为指针,在插入元素时要插入的指针,遍历方法与基本差不多
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
class Person{
public:
Person(string name, int age){
this->m_Name = name;
this->m_age = age;
}
string m_Name;
int m_age;
};
void myPrint(Person *p){
cout << p->m_Name << endl;
cout << p->m_age << endl;
}
int main(){
vector<Person*>v;
Person p1("aaa",10);
Person p2("bbb",20);
Person p3("ccc",30);
Person p4("ddd",40);
Person p5("eee",50);
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
v.push_back(&p5);
for(vector<Person*>::iterator it = v.begin(); it != v.end(); it++){
cout << (**it).m_Name << endl;
cout << (**it).m_age << endl;
}
for(vector<Person*>::iterator it = v.begin(); it != v.end(); it++){
cout << (*it)->m_Name << endl;
cout << (*it)->m_age << endl;
}
for_each(v.begin(),v.end(),myPrint);
}
这里注意迭代器的数据类型 vector<Person*>::iterator it it一定是与尖括号里的数据类型的指针,也就是说 it 是 Person* 的指针,如果忘记了迭代器的类型,就看尖括号里声明的类型即可。
通过迭代器访问嵌套容器
嵌套容器可以类似理解为数组中的数组,也就是二维数组,C++ 允许容器中嵌套容器。但用迭代器访问时一定要理清指针的表示关系
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
int main(){
//创建大容器
vector<vector<int>>v;
//创建小容器
vector<int>v1;
vector<int>v2;
vector<int>v3;
vector<int>v4;
vector<int>v5;
//给每个容器都放入一些数据
for(int i = 0; i < 4; i++){
v1.push_back(i + 1);
v2.push_back(i + 2);
v3.push_back(i + 3);
v4.push_back(i + 4);
v5.push_back(i + 5);
}
//将小容器插入到大容器中
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
v.push_back(v5);
// 定义大循环,遍历每个小容器
for(vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++){
// 定义小循环,遍历每个容器中的元素
for(vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++){
cout << *vit << " " ;
}
cout << endl;
}
}
结果如下: