使用迭代器
和指针不一样,获取迭代器不是使用取址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为begin和end的成员,其中begin成员负责返回第一个元素(或第一个字符)的迭代器。如有下述语句:
//由编译器决定b和e的类型
//b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin(), e = v.end(); //b和e的类型相同
end成员则负责返回指向容器(或string对象)“尾元素的下一位置(one past the end)”的迭代器,也就是说,该迭代器指示的是容器的一个本不存在的“尾后(off the end)”元素。这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器(off-the-off iterator)或者简称为尾后迭代器(end iterator).特殊情况下
如果容器为空,则begin和end返回的是同一个迭代器。
Note:如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
一般来说,我们不清楚(不在意)迭代器准确的类型到底是什么。在上面的例子中,使用auto关键字定义变量b和e,这两个变量的类型也就是begin和end的返回值类型。
迭代器运算符
使用==和!=来比较两个合法的迭代器是否相等,如果两个迭代器指向的元素相同或者是同一个容器的尾后迭代器,则他们相等;否则就说这两个迭代器不相等。
*iter 返回迭代器iter所指元素的引用
iter->mem 解引用iter并获取该元素的名为mem的成员,等价于 (*iter).mem
++iter 令iter指示容器中的下一个元素
--iter 令iter指示容器中的上一个元素
iter1 == iter2 判断两个迭代器是否相等(不相等),如果两个迭代器指示的是同一个元
iter1 != iter2 素或者他们是同一个容器的尾后迭代器,则相等;反之,不相等
和指针类似,也能通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示着某个元素。
试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。
举个例子,利用下标运算符把string对象的第一个字母改为了大写形式,下面利用迭代器实现同样的功能:
string s("some string");
if (s.begin() != s.end()) { //确保s非空
auto it = s.begin(); //it表示s的第一个字符
*it = toupper(*it); //将当前字符改成大写形式
本例和原来的程序一样,首先检查s是否为空,显然通过检查begin和end返回的结果是否一致就能做到这一点。如果返回的结果一样,说明s为空;如果返回的结果不一样,说明s不为空,此时s中至少包含一个字符。
我们在if内部,声明了一个迭代器变量it并把begin返回的结果赋给它,这样就得到了指示
s中第一个字符的迭代器,接下来通过解引用运算符将第一个字符更改为大写形式。和原来的程序
一样,输出结果将是:
Some string
将迭代器从一个元素移动到另外一个元素
迭代器使用递增(++)运算符来从一个元素移动到下一个元素。从逻辑上来说,迭代器的递增
和整数的递增类似,整数的递增是在整数值上“加1”,迭代器的递增是将迭代器“向前移动一个位置”。
Note: 因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用操作。
之前有一个程序把string对象中第一个单词改写为大写形式,现在利用迭代器及其递增运算符可以
实现相同功能:
//依次处理s的字符直至我们处理完全部字符或者遇到空白
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
*it = toupper(*it); //将当前字符改成大写形式
这个程序用的是迭代器,上面循环遍历s的字符直到遇到空白字符为止。
循环首先用s.begin的返回值来初始化it,意味着it指示的是s中的第一个字符(如果有的话)。
条件部分检查是否已达到s的尾部,如果尚未到达,则将it解引用的结果传入isspace函数检查是否遇到
空白。每次迭代的最后,执行++it令迭代器前移一个位置以访问s的下一个字符。
先解引用it,然后将结果传入toupper函数得到该字母对应的大写形式,再把这个大写字母重新赋值给
it所指示的字符。
关键概念:泛型编程
原来使用C或Java的程序员在转而使用C++语言之后,会对for循环中使用!=而非<进行判断有点儿奇怪,
比如上面的这个程序。C++程序员习惯性地使用!=,其原因和他们更愿意使用迭代器而非下标的原因一样:因为
这种编程风格在标准库提供的所有容器上都有效。
之前已经说过,只有string和vector等一些标准库类型有下标运算符,而并非全都如此。与之类似,
所有标准容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符。因此,只要我们养成
使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型。
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对象是一个常量,只能使用const_iterator;如果vector对象或string对象不是常量,那么
既能使用iterator也能使用const_iterator.
begin和end运算符
begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;
如果对象不是常量,返回iterator:
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类型的返回值,C++11新标准引入了两个新函数,分别是cbegin
和cend;
auto it3 = v.cbegin(); //it3的类型是vector<int>::const_iterator
类似于begin和end,上述两个新函数也分别返回指示容器第一个元素或最后元素下一位置的迭代器。
有所不同的是,不论vector对象(或string对象)本身是否是常量,返回值都是const_iterator.
结合解引用和成员访问操作
解引用迭代器可获得迭代器所指的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它
的成员。例如,对于一个由字符串组成的vector对象来说,要想检查其元素是否为空,令it是该vector
对象的迭代器,只需检查it所指字符串是否为空就可以了,其代码如下所示:
(*it).empty()
注意,(*it).empty()中圆括号必不可少。该表达式的含义是先对it解引用,然后解引用的操作再执行
点运算符。如果不加圆括号,点运算符将由it来执行,而非it解引用的结果:
(*it).empty() //解引用it,然后调用结果对象的empty成员
*it.empty() //错误:试图访问it的名为empty的成员,但it是个迭代器,
//没有empty成员
上面第二个表达式的含义是从名为it的对象中寻找其empty成员,显然it是一个迭代器,
它没有哪个成员是叫empty的,所以第二个表达式将发生错误。
为了简化上述表达式,C++语言定义了箭头运算符(->).箭头运算符把解引用和成员访问
两个操作结合在一起,也就是说,it->mem和(*it).mem表达的意思相同。
例如,假设用一个名为text的字符串向量存放文本文件中的数据,其中的元素或者是一句话
或者是一个用于表示段落分隔的空字符串。如果要输出text中第一段内容,可以利用迭代器写一个
循环令其遍历text,直到遇到空字符串为止:
//依次输出text的每一行直至遇到第一个空白字符为止
for (auto it = text.cbegin();
it != text.cend() && it->empty(); ++it)
cout << *it << endl;
我们首先初始化it令其指向text的第一个元素,循环重复执行直至处理完text的所有元素
或者发现某个元素为空。每次迭代时只要发现还有元素并且尚未遇到空元素,就输出当前正在处理的元素。
值得注意的是,因为循环从头到尾只是读取text的元素而未向其中写值,所以使用了cbegin()和cend来控制
整个迭代过程。
WARNNING: 谨记,但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素
iter + n 迭代器加上一个整数值仍得一个迭代器,迭代器指示的新位置与原来相比向前移动了若干个
元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置
iter - n 迭代器减去一个整数值仍得一个迭代器,迭代器指示的新位置与原来相比向后移动了若干个
元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置
iter1 += n 迭代器加法的复合赋值语句,将iter1加n的结果赋给iter1
iter1 -= n 迭代器减法的复合赋值语句,将iter1减n的结果赋给iter1
iter1 = iter2 两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前
移动差值个元素后将得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一
个容器中的元素或者尾元素的下一位置
>,>=,<,<= 迭代器的关系运算符,如果某迭代器指向的容器位置在另一个迭代器所指位置之前,
则说前者小于后者。参与运算的两个迭代器必须指向的是同一个容器中的元素或者
尾元素的下一位置。