文章目录
1 容器 算法 迭代器初识
在上一篇中,了解STL中容器、算法、迭代器概念之后,再通过代码来进一步认识STL。
STL中最常用的容器为Vector,可以理解为数组,下面将学习如何向这个容器中插入数据、并遍历这个容器。
2 vector存放内置数据类型
在模板的学习中,有一个案例是写一个数组类的实现。那么在STL中有一个容器可以实现数组类的功能。在这个学习当中,涉及的容器是 vector
,算法是for_each
,迭代器是 vector<int>::iterator
2.1 步骤
-
首先,创建一个vector容器,可以理解为数组,也可以看之前的笔记-类比模板案例中数组类的实现 中,类比
MyArray<int> arr;
。要注意的是,在使用STL的每一个容器时,都要包含这个容器的头文件,#include <iostream>
; -
其次,使用容器内置函数push_back在尾部插入新数据;
-
然后,有三种方式访问容器中的数据;
-
测试三种访问方式,比较不同。
2.2 三种访问方式
- 第一种访问方式:首先创建起始迭代器和结束迭代器,再配合while循环以及星号*解引用的方式访问数据,循环结束的条件就是起始迭代器=结束迭代器。其中,遍历的过程如下,
- while判断循环的条件 itBegin != itEnd 也就是起始迭代器≠结束迭代器
- 第一次,起始迭代器指向10,不等于结束迭代器的指向,访问到数据并打印输出,再向后偏移;
- 第二次,起始迭代器指向20,不等于结束迭代器的指向,访问到数据并打印输出,再向后偏移;
- 重复过程,直到第五次访问到50并输出;
- 在第六次访问时,起始迭代器指向50的下一个,等于结束迭代器的指向,结束循环。
-
第二种访问方式:和第一中类似,不同的是,创建起始迭代器和结束迭代器配合for循环一次性实现容器数据访问
-
第三种访问方式:通过算法for_each来访问,使用这个算法需要配合一个自定义的打印函数实现。要注意的是,在使用STL的每一个算法时,都要包含这个算法的头文件,
#include <algorithm>
。通过查看for_each的底层实现,可以发现这个算法其实和第二种方法很类似,是在封装成STL的一个算法了。
2.3 三种访问方式的代码、效果、注意点
第一种访问方式
void test1()
{
vector<int> v1; //创建一个vector容器,需要包含头文件 #include <vector>
int s = 60;
for (int i = 1; i < 6; i++)
{
v1.push_back(i*10); //向容器插入数组-尾部插入
}
//通过迭代器访问容器中的数据
cout << "第一种访问方式" << endl;
vector<int>::iterator itBegin = v1.begin();//起始迭代器
vector<int>::iterator itEnd = v1.end();//结束迭代器
while (itBegin != itEnd)//起始迭代器不等于结束迭代器时
{
cout << *itBegin << "\t";//解引用的方式 访问数据并输出
itBegin++;//起始迭代器往后偏移
}
cout << endl << string(s, '-') << endl;
}
注意点:
创建一个vector容器时需要包含头文件,#include <vector>
;
iterator是迭代器,创建迭代器时要加上作用域vector<int>::
,vector::iterator表明是在vector这个类下的;
v.begin()是起始迭代器,指向容器的第一个元素;
v.end()是结束迭代器,指向容器最后一个元素的下一个位置,不能用结束迭代器来访问数据,该迭代器指向的数据没有意义;
通过星号*解引用的方式访问数据,起始迭代器偏移操作,可以按照指针的理解。
第二种访问方式 常用
void test2()
{
//第二种访问方式 常用
vector<int> v2;
for (int i = 6; i < 11; i++)
{
v2.push_back(i * 10);
}
cout << "第二种访问方式" << endl;
for (vector<int>::iterator it = v2.begin(); it != v2.end(); it++)//把第一种方式结合for循环实现
{
cout << *it << "\t";
}
cout << endl << string(s, '-') << endl;
}
注意点:
这方式是把容器的起始迭代器和结束迭代器结合for循环的三要素一起实现了;
for循环的起始值是,容器的起始迭代器vector<int>::iterator it = v2.begin()
,it代表了起始迭代器;
for循环的循环条件是,容器的起始迭代器与结束迭代器不相等,it != v2.end()
;
for循环的循环步长是,it起始迭代器偏移操作,可以按照指针的理解;
输出数据时,也是通过通过星号*解引用的方式访问数据。
第三种访问方式
void myprint(int val)
{
cout << val << "\t";
}
void test3()
{
//第三种访问方式 遍历算法
vector<int> v3;
for (int i = 11; i < 16; i++)
{
v3.push_back(i * 10);
}
cout << "第三种访问方式" << endl;
for_each(v3.begin(), v3.end(), myprint);//查看底层实现 右键->转到定义 本质是一个for循环 和第二种访问方式类似
cout << endl << string(s, '-') << endl;
}
注意点:
for_each是STL的一个遍历算法,非质变算法,需要需要包含算法的头文件 #include
for_each需要传入的参数是,容器的起始迭代器,容器的结束迭代器,功能函数;
在这个地方,由于需要输入并打印数据,因此功能函数只需要传入数据并实现打印即可。
这个算法很好地说明了迭代器是算法和容器的桥梁,通过迭代器访问到数据,再通过算法的功能函数实现数据的打印。
三种访问方式展示
for_each的底层实现
此外,可以查看for_each的底层实现,选中for_each,右键,转到定义。for_each本质是一个for循环,和第二种访问方式类似
template <class _InIt, class _Fn>
_CONSTEXPR20 _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是一个类模板,其中_First就是传入的容器起始迭代器, _Last是传入的容器结束迭代器,_Func就是传入的功能函数;
和第二种访问方式有所不同的是,首先要对传入的容器起始迭代器和结束迭代器做一个检测;
然后,是数据的访问,也就是这一块for循环了,同样也是通过判断起始迭代器是否等于结束迭代器来一步一步地访问数据,也是星号*解引用的方式访问;
最后,将访问到的数据*_UFirst
传给功能函数return _Func;
,再通过功能函数实现输出。
for (; _UFirst != _ULast; ++_UFirst) {
_Func(*_UFirst);
}
3 Vector存放自定义数据类型
学习目标: 前面展示了Vector存放内置数据类型的方式与访问方法,接下来是Vector存放自定义数据类型的方式与访问方法。
场景描述: 创建一个自定义数据类型的Person类,把Person类放入容器并访问。把Person类放入容器时,有两种方式,一是容器存放Person类的数据,二是容器存放Person类的指针。存放方式的不同也导致要用不同的方式访问。
容器存放Person类的数据:
class Person
{
public:
Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
//容器存放自定义数据类型的数据
void test1()
{
vector<Person> v;
int s = 60;
Person p1("刘备", 23);
Person p2("关羽", 22);
Person p3("张飞", 29);
Person p4("貂蝉", 24);
Person p5("西施", 25);
v.push_back(p5);
v.push_back(p4);
v.push_back(p3);
v.push_back(p2);
v.push_back(p1);
//第一种 for循环
cout << "容器存放自定义数据类型的数据 第一种访问 -> 解引用" << endl;
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名:" << it->m_name << "\t年龄:" << it->m_age << endl;
}
cout << endl << string(s, '-') << endl;
cout << "容器存放自定义数据类型的数据 第二种访问 * 解引用" << endl;
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名:" << (*it).m_name << "\t年龄:" << (*it).m_age << endl;//(*it)返回的是Person数据类型
}
cout << endl << string(s, '-') << endl;
}
注意点:
由于容器存放的是自定义数据类型Person,所以迭代器也是这个类型,即 *it 是Person类型。在之前的类的学习中,访问类中的数据,通过点 . 访问即可;
另一种访问方式是,通过箭头的方式访问数据,it本质是一个指针,因此可以使用->访问Person类的数据。
容器存放Person类的指针:
//容器存放自定义数据类型的指针
void test2()
{
vector<Person*> v;
int s = 60;
Person p1("刘备", 23);
Person p2("关羽", 22);
Person p3("张飞", 29);
Person p4("貂蝉", 24);
Person p5("西施", 25);
v.push_back(&p5);
v.push_back(&p4);
v.push_back(&p3);
v.push_back(&p2);
v.push_back(&p1);
//第一种 for循环
cout << "容器存放自定义数据类型的指针 -> 解引用" << endl;
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名:" << (*it)->m_name << "\t年龄:" << (*it)->m_age << endl;//(*it)返回的是Person*类型的 指针
}
cout << endl << string(s, '-') << endl;
}
注意点:
创建容器时,容器存放数据的类型是自定义数据类型的指针,即Person*;
在向容器存入数据的时候,需要取值符号&,存放指针地址;
在向容器访问数据的时候,需要使用箭头的方式,*it返回的是自定义数据类型的指针,因此使用->的方式访问类中数据。
效果
4 Vector容器嵌套容器
学习目标: 容器中嵌套容器,我们将所有数据进行遍历输出
代码:
void test()
{
vector<vector<int>> v;//vector< >是一个大容器,vector<int>是大容器里嵌套的小容器
vector<int> v1;//大容器嵌套的小容器
vector<int> v2;
vector<int> v3;
vector<int> v4;
vector<int> v5;
//每个小容器存数据
for (int i = 0; i < 6; 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;//一个小容器数据输出结束 下一个小容器数据换行输出
}
}
效果:
注意点:
vector<vector<int>>::iterator it
这个迭代器it对应的是大容器的迭代器
大容器存放的是小容器,因此还需要再创建小容器的迭代器vector<int>::iterator vit = (*it).begin()
,来访问实际数据。而it返回的是vector<int>
容器类型
it是大容器的迭代器, *it
是大容器<>内的数据类型,即vector,即it还是一个容器