【C++基础】第十六课:迭代器

迭代器,迭代器的使用,迭代器运算

Posted by x-jeff on April 16, 2020

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.前言

我们已经知道可以使用下标运算符来访问string对象的字符或vector对象的元素,还有另外一种更通用的机制也可以实现同样的目的,这就是迭代器(iterator)

⚠️所有标准库容器都可以使用迭代器。但是其中只有少数几种才同时支持下标运算符。

严格来说,string对象不属于容器类型,但是string支持很多与容器类型类似的操作,也支持迭代器。

vector是标准库容器。

迭代器有无效有效之分。有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其他所有情况都属于无效。

2.使用迭代器

1
2
//b表示v的第一个元素,e表示v尾元素的下一位置
auto b=v.begin(),e=v.end();

begin成员负责返回指向第一个元素(或第一个字符)的迭代器

注意:begin返回的并不是第一个元素本身,而是指向该元素的一个迭代器。

end成员则负责返回指向容器(或string对象)“尾元素的下一位置(one past the end)”的迭代器。该迭代器指示的是容器的一个本不存在的“尾后(off the end)”元素,这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器

❗️特殊情况下如果容器为空,则beginend返回的是同一个迭代器,都是尾后迭代器。

2.1.迭代器运算符

举个例子:利用迭代器将string对象的第一个字母改写为大写形式:

1
2
3
4
5
6
string s("some string");
if(s.begin()!=s.end())
{
	auto it=s.begin();
	*it=toupper(*it);//输出为:Some string
}

迭代器变量it像一个指针一样,存放着第一个字符的地址,这个地址由begin返回。然后可以通过解引用符*访问第一个字符。

2.2.将迭代器从一个元素移动到另外一个元素

迭代器使用递增(++)运算符来从一个元素移动到下一个元素,即将迭代器“向前移动一个位置”。

⚠️因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。

1
2
for(auto it=s.begin();it!=s.end()&&!isspace(*it);++it)
	*it=toupper(*it);//输出为SOME string

2.3.迭代器类型

通常我们并不知道且无需知道迭代器的精确类型。而实际上,那些拥有迭代器的标准库类型使用iteratorconst_iterator来表示迭代器的类型:

1
2
3
4
5
vector<int>::iterator it;//it能读写vector<int>的元素
string::iterator it2;//it2能读写string对象中的字符

vector<int>::const_iterator it3;//it3只能读元素,不能写元素
string::const_iterator it4;//it4只能读字符,不能写字符

❗️const_iterator和常量指针差不多,能读取但不能修改它所指的元素值。

相反,iterator的对象可读可写。如果vector对象或string对象是一个常量,只能使用const_iterator;如果vector对象或string对象不是常量,那么既能使用iterator也能使用const_iterator

2.4.beginend运算符

beginend返回的具体类型由对象是否是常量决定,如果对象是常量,beginend返回const_iterator;如果对象不是常量,返回iterator

1
2
3
4
vector<int> v;
const vector<int> cv;
auto it1=v.begin();//it1的类型是vector<int>::iterator
auto it2=cv.begin();//it2的类型是vector<int>::const_iterator

⚠️如果对象只需读操作而无需写操作的话最好使用常量类型(比如const_iterator)。为了便于专门得到const_iterator类型的返回值,C++11新标准引入了两个新函数,分别是cbegincend

1
auto it3=v.cbegin();//it3的类型是vector<int>::const_iterator

类似于beginend,上述两个新函数也分别返回指示容器第一个元素或最后元素下一位置的迭代器。有所不同的是,不论vector对象(或string对象)本身是否是常量,返回值都是const_iterator

2.5.结合解引用和成员访问操作

1
2
3
4
5
6
7
vector<int> vcTest1={1,2,3,4,5};
vector<int> vcTest2={6,7,8};
vector<vector<int>> vcTest3={vcTest1,vcTest2};
auto iter2=vcTest3.begin();
cout<<iter2->size()<<endl;//输出为5
cout<<(*iter2).size()<<endl;//输出为5。等同于上一条语句。
cout<<*iter2.size()<<endl;//错误:iter是个迭代器,没有size成员。

2.6.某些对vector对象的操作会使迭代器失效

使用vector时的一些限制:

  1. 不能在范围for循环中向vector对象添加元素。
  2. 任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效。

‼️但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。

3.迭代器运算

2.1部分定义的迭代器运算符适用于所有的标准库容器。

除此之外,stringvector的迭代器提供了更多额外的运算符(所有这些运算被称作迭代器运算):

接下来我们简单介绍下这些计算。

3.1.迭代器的算术运算

1
2
//计算得到最接近vi中间元素的一个迭代器
auto mid=vi.begin()+vi.size()/2;

又例如,假设itmid是同一个vector对象的两个迭代器,可以用下面的代码来比较它们所指的位置孰前孰后:

1
2
if(it<mid)
	//处理vi前半部分的元素

只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个迭代器的距离。所谓距离指的是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为difference_type的带符号整型数