文章目录
我们已经知道可以使用下标运算符来访问string对象的字符或者vector对象的元素,我们还有另外一种更为通用的机制可以实现同样的目的,这就是迭代器。
类似于指针类型,迭代器也提供了对对象的间接访问。就迭代器而言,其对象是容器或者string对象当中的字符。使用迭代器可以访问某元素,迭代器也能从一个元素移动到另外一个元素。也分为有效以及无效之分,让我们一起见证一下。
使用迭代器
和指针不一样的是,获取迭代器不是使用取地址符号,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为begin和end的成员,其中begin成员负责返回指向第一个元素的迭代器。
具体定义:
auto b = v.begin();
auto e = v.end();
关于begin成员负责返回指向第一个元素的迭代器。
end成员负责返回指向容器(或者string)尾元素的下一位置,也就是说,该迭代器指示的是容器的一个本不存在的尾后元素。这样子的迭代器没什么实际的含义,仅仅是一个标记而已,表示我们处理完了容器中的所有元素。
PS:如果容器(或string)为空,则begin和end返回的是同一个迭代器们都是尾后迭代器。
一般来说我们也不在意迭代器的准确的类型是什么,只需要使用auto就可以了。
迭代器运算符
常用的迭代器运算符:
*iter //1
iter->mem//2
++iter//3
–iter//4
iter1==iter2//5
iter1!=iter2//6
- 返回迭代器所指向的元素的引用。
- 解引用iter并获取该元素名为mem的成员,等效于(*iter).mem
- 令iter指向下一个元素
- 令iter指向上一个元素
- 判断两个迭代器是否指向同一个元素,如果相同则为true,不同为false。
- 判断两个迭代器是否指向不同的元素,如果不同则为true,相同为false。
下面测试一段代码:
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
int main()
{
string s("hello world");
if (s.begin() != s.end())
{
auto iter = s.begin();
*iter = toupper(*iter);
}
cout << s;
return 0;
}
这里是一段把第一位元素的小写换成大写的过程,大家进行理解一下。
将迭代器从一个元素移动到另外一个元素
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
int main()
{
string s("hello world");
if (s.begin() != s.end())
{
auto iter = s.begin();
while (iter != s.end())
{
*iter = toupper(*iter);
iter++;
}
}
cout << s;
return 0;
}
这是一个把所有小写换算成大写的过程,大家进行理解一下。
实际上将迭代器从一个元素移动到另外一个元素的过程就类似于指针的移动的过程,也就是利用==++或者–==进行的操作。
PS:因为end返回的迭代器并不指向任何一个元素,所以不能对其使用解引用,递增操作也不行。
PS:还有就是我们有时候也会很好奇iter != s.end()这一段,为什么不使用<,当iter<s.end()的时候不是也可以运行的吗?实际上这源于长期以来C++程序员的习惯,而且很多迭代器中也没有定义<给你使用,所以习惯使用!=吧。
迭代器类型
就像不知道string和vector里面的size_type成员到底是什么类型一样,一般来说,我们也无须知道迭代器的精准类型,但这里提一嘴,实际上那些拥有迭代器的标准类型使用iterator和const_iterator来表示迭代器的类型。
#include<iostream>
#include<string>
#include<cctype>
#include<vector>
using namespace std;
int main()
{
vector<int>::iterator it1;//1
string::iterator it2;//2
vector<int>::const_iterator it3;//3
string::const_iterator it4;//4
return 0;
}
- it1能读写vector<int>的元素
- it2能读写string对象的字符
- it3只能读元素,不能写元素
- it4只能读字符,不能写字符
由此我们可以得出const_iterator和iterator的区别就是前者只允许读,后者可读可写。
begin和end运算符
begin和end返回的具体类型由对象是否是常量决定的,如果对象是常量,begin和end返回const_iterator,如果不是常量,则返回iterator。
但有时候这种行为并不是我们想要的,我们希望我们的迭代器对其只进行读取操作而不修改内部的值,这就需要用到C++11引入的两个新的函数了分别是cbegin和cend。只要用到这一组函数,返回的类型就都是const_interator了。
结合解引用和成员访问操作
解引用迭代器可以获得迭代器所指的对象。如果该对象的类型恰好就是类,那就有可能就可以进一步访问它的成员函数
但是格式需要注意,其与类指针调用成员函数的方法一致,但是不可省略解引用:
对于类指针调用成员函数:
p.empty();
(*p).empty();
两个都是正确的,因为调用第一种的时候,编译器会将第一种默认修改成第二种,但是迭代器就不行了。
对于迭代器调用成员函数:
it.empty();
(*it).empty();
后者才是正解。
为了简化上面的式子,C++语言还定义了箭头运算符(->)。箭头运算符把解引用和成员访问两个操作结合在一起,也就是说it->mem和(*it).mem意思相同。
还有一个重点错误项:
#include<string>
#include<cctype>
using namespace std;
int main()
{
string text = "hello world";
auto it = text.cbegin();
cout << typeid(*it).name() << endl;
return 0;
}
我们运行完这段代码得到的是char类型,而非string类型,自行理解一下,也就是说该it没有指向对应的string函数的功能。
某些对vector对象的操作会使迭代器失效
虽然vector对象可以动态的增长,但是也会有一些副作用,已知的一个限制是不能在范围for循环中向vector对象添加元素。另外一个限制是任何一种可能改变的vector对象容量的操作,比如push_back,都会让该vector的对象迭代器失效。
迭代器运算
迭代器的递增运算令迭代器每次移动一个元素,所有的标准库容器都有支持递增运算的迭代器。相同的也有==和!=两个有效的迭代器
对于现在学的string和vector的迭代器则提供了更多额外的运算符,一方面可以方便迭代器每次跨过多个元素,另外也支持迭代器进行关系运算,所有这些运算都被称作迭代器运算
vector和string迭代器支持的运算:
iter+n//1
iter-n//2
iter1+=n//3
iter1-=n//4
iter1-iter2//5、>=、<、<=//6
- 迭代器加上n个位置,结果迭代器或者指示容器内的一个元素,或者指向容器内的尾元素的下一个位置
- 和上面的方向相反
- n是距离,向后移动n格,且满足第一条最后的规律
- n是距离,向前移动n格,且满足第一条最后的规律
- 计算二者之间的距离
- 判断两个迭代器的前后关系。
迭代器的算术运算
可以令迭代器和一个整数相加或者相减,返回值是向后或者向前移动了若干的位置的迭代器
而既然存在iter1-iter2,所以得出的结果类型是difference_type,因为其值可正可负。
主要要切记的是,并不存在iter1+iter2这个功能
使用迭代器运算
使用迭代器运算的一个经典算法是二分搜索,二分搜索从有序的序列中寻找某个给定的值。二分搜索从序列的中间位置开始搜索,如果中间位置的元素正好就是我们要找的元素,搜索完成,如果不是,加入该元素小于要找的元素,则在序列的后半部分进行寻找,加入该元素大于要找的元素,则在序列的前半部分进行搜索。在缩小范围内计算一个新的中间元素并且重复之前的过程,直至最终找到目标或者没有目标可以继续搜索。
#include<iostream>
#include<string>
#include<cctype>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
int sought;
cin >> sought;
vector<int> num{ 1,3,4,5,2,7,8,5,6,8 };
auto beg = num.begin(), end = num.end();
auto mid = num.begin() + (num.end() - num.begin()) / 2;
sort(beg,end);
while (mid != end && *mid != sought)
{
if (sought < *mid)
end = mid;
else
beg = mid + 1;
mid = beg + (mid - beg) / 2;
}
cout << mid - num.begin();
return 0;
}
自行理解一下,上面也进行分析了。