3.4.1 使用迭代器(1)
和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为begin和end的成员,其中begin成员负责返回指向第一个元素(或第一个字符)的迭代器。如有下述语句:
- // 由编译器决定b和e的类型;参见2.5.2节(第68页)
- // b表示v的第一个元素,e表示v尾元素的下一位置
- auto b = v.begin(), e = v.end(); //b 和e的类型相同
nd成员则负责返回指向容器(或string对象)"尾元素的下一位置(one past the end)"的迭代器,也就是说,该迭代器指示的是容器的一个本不存在的"尾后(off the end)"元素。这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。特殊情况下如果容器为空,则begin和end返回的是同一个迭代器。
如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
一般来说,我们不清楚(不在意)迭代器准确的类型到底是什么。在上面的例子中,使用auto关键字定义变量b和e(参见2.5.2节,第68页),这两个变量的类型也就是begin和end的返回值类型,第108页将对相关内容做更详细的介绍。
迭代器运算符
表3.6列举了迭代器支持的一些运算。使用==和!=来比较两个合法的迭代器是否相等,如果两个迭代器指向的元素相同或者都是同一个容器的尾后迭代器,则它们相等;否则就说这两个迭代器不相等。
表3.6:标准容器迭代器的运算符
|
表3.6:标准容器迭代器的运算符
|
|
*iter
|
返回迭代器iter所指元素的引用
|
|
iter->mem
|
解引用iter并获取该元素的名为mem的成员,
等价于(*iter).mem
|
|
++iter
|
令iter指示容器中的下一个元素
|
|
--iter
|
令iter指示容器中的上一个元素
|
|
iter1 == iter2
|
判断两个迭代器是否相等(不相等),如果两个
迭代器指示的是同一个元素或者它们是同一个
容器的尾后迭代器,则相等;反之,不相等
|
|
iter1 != iter2
|
和指针类似,也能通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示着某个元素(参见2.3.2节,第53页)。试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。
举个例子,3.2.3节(第94页)中的程序利用下标运算符把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.4.1节,第12页)来从一个元素移动到下一个元素。从逻辑上来说,迭代器的递增和整数的递增类似,整数的递增是在整数值上"加1",迭代器的递增则是将迭代器"向前移动一个位置"。
因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。
之前有一个程序把string对象中第一个单词改写为大写形式,现在利用迭代器及其递增运算符可以实现相同的功能:
- // 依次处理s的字符直至我们处理完全部字符或者遇到空白
- for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
- *it = toupper(*it); // 将当前字符改成大写形式
和3.2.3节(第94页)的那个程序一样,上面的循环也是遍历s的字符直到遇到空白字符为止,只不过之前的程序用的是下标运算符,现在这个程序用的是迭代器。
循环首先用s.begin的返回值来初始化it,意味着it指示的是s中的第一个字符(如果有的话)。条件部分检查是否已到达s的尾部,如果尚未到达,则将it解引用的结果传入isspace函数检查是否遇到了空白。每次迭代的最后,执行++it令迭代器前移一个位置以访问s的下一个字符。
循环体内部和上一个程序if语句内的最后一句话一样,先解引用it,然后将结果传入toupper函数得到该字母对应的大写形式,再把这个大写字母重新赋值给it所指示的字符。
关键概念:泛型编程(www.cppentry.com)
原来使用C或Java的程序员在转而使用C++(www.cppentry.com)语言之后,会对for循环中使用!=而非<进行判断有点儿奇怪,比如上面的这个程序以及94页的那个。C++(www.cppentry.com)程序员习惯性地使用!=,其原因和他们更愿意使用迭代器而非下标的原因一样:因为这种编程(www.cppentry.com)风格在标准库提供的所有容器上都有效。
之前已经说过,只有string和vector等一些标准库类型有下标运算符,而并非全都如此。与之类似,所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符。因此,只要我们养成使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型。