一. 迭代器简介
迭代器是一种遍历容器内元素的数据类型(比如读String
中的每个字符、Vector
中的每个元素)。这种数据类型有点像指针,我们可以理解为使用迭代器不仅能指向容器中的某个元素,而且还能修改某个迭代器所指向的元素值。
前面我们学习String字符串、Vector容器的时候知道,想要访问里面的数据时,可通过下标运算符[]
来访问String
中的字符、Vector
中的元素。但是在C++
中,很少使用下标运算符[]
这种方式来访问它们中的数据,比较常用的访问方式是本文中介绍的迭代器方式。
此外,在C++
中类似Vector
的其他容器(list
、map
)不一定都提供了[]
这种方式来访问其数据,但是这些容器基本都提供了迭代器的方式来对他们的数据进行访问,从这个角度来看,使用迭代器来访问容器中的元素也更为普遍,所以大家有必要来学习迭代器的使用方法。
二. 容器的迭代器类型
1、迭代器格式:容器名<容器类型>::iterator 对象名
2、定义迭代器:对比定义vector
容器
vector<int> iv = { 100,200,300 }; //定义容器iv vector<int>::iterator iter; //定义迭代器iter,也必须是vector<int>类型 vector<int>::reverse_iterator riter; //定义反向迭代器riter,也必须是vector<int>类型
Ps:大家理解的时候,就把整个vector<int>::iterator
理解成一个类型,当用这个类型定义一个变量(iter
)时,这个变量就是个迭代器。
三. 迭代器begin()/end()操作,反向迭代器rbegin()/rend()操作
1、begin()/end():用来返回一个迭代类型(可以理解成返回一个迭代器)
① begin():如果容器中有元素,则begin
返回的迭代器,指向的是容器中的第一个元素。
vector<int> iv = { 100,200,300 }; //定义容器iv vector<int>::iterator iter; //定义迭代器iter,也必须是vector<int>类型 iter = iv.begin(); //相当于iter指向vector容器iv中的第0个元素100,即iv[0]
② end():如果容器中有元素,则end
返回的迭代器,指向的并不是容器中的末端元素,而是末端元素的下一位元素(不存在)。
vector<int> iv = { 100,200,300 }; //定义容器iv vector<int>::iterator iter; //定义迭代器iter,也必须是vector<int>类型 iter = iv.end(); //相当于iter指向vector容器iv中的第末尾第2个元素的下一位,所以是不存在数据的(乱码)
③ 如果一个容器为空,则begin()/end()返回的迭代器相同,都为空。
vector<int> iv2; vector<int>::iterator iterbegin = iv2.begin(); vector<int>::iterator iterend = iv2.end(); if(iterbegin == iterend) { qDeubg() << "容器iv2为空"; //条件成立 }
④ 迭代器begin()/end()
遍历容器中数据的方法:
vector<int> iv = { 100,200,300 }; for (vector<int>::iterator iter = iv.begin(); iter != iv.end(); iter++) { cout << *iter << endl; //后面讲*对迭代器中的容器取值 }
ps:end()
实际是作为一个结束标记的作用,表明在for循环中,判断是否已经执行到最后一步,遍历完容器中的所有元素了。
实际工作中,可能需要使用迭代器从容器的末尾开始遍历数据,直到容器的首位数据,这时候就需要使用反向迭代器rbegin()
/rend()
操作了。
2、rbegin()/rend():反向迭代器
① rbegin():返回一个反向迭代器,指向反向迭代器的第一个元素。
② rend():返回一个反向迭代器,指向反向迭代器末尾元素的下一个位置。
③ 反向迭代器rbegin()/rend()
遍历容器中数据的方法:
vector<int> iv = { 100,200,300 }; for (vector<int>::reverse_iterator riter = iv.rbegin(); riter != iv.rend(); riter++) { cout << *riter << endl; }
小结:
- 迭代器操作函数
begin()
/end()
指向容器中的位置关系如下: - 反向迭代器操作函数
rbegin()
/rend()
指向容器中的位置关系如下:
四. 迭代器相关操作
示例:
vector<int> iv = { 100,200,300 }; for (vector<int>::iterator iter = iv.begin(); iter != iv.end(); iter++) { cout << *iter << endl; //后面讲*对迭代器中的容器取值 }
① *iter
:返回迭代器iter
所指元素的引用(必需保证这个迭代器指向的是有效的容器元素,不能指向end()
)。
eg:
vector<int>::iterator iter = iv.end(); //cout << *iter << endl; //不可以,系统会直接崩溃报错 vector<int>::iterator iter1 = iv.begin(); cout << *iter1 << endl; //可以,指向容器iv的第0个元素100
② ++iter
/iter++
:让迭代器指向容器中的下一位元素,已经指向end()
的时候不能在++
。
vector<int>::iterator iter = iv.end(); //iter++; //不可以,系统会直接崩溃报错 vector<int>::iterator iter1 = iv.begin(); iter1++; //可以,首位元素下走一位,指向容器iv的第1个元素200
③ --iter
/iter--
:让迭代器指向容器中的上一个元素,已经指向begin()
的时候不能在--
。
vector<int>::iterator iter = iv.begin(); //iter--; //不可以,系统会直接崩溃报错 vector<int>::iterator iter = iv.end(); iter--; cout << *iter << endl; //输出容器末位元素300
④ insert(const_iterator _Where, _Ty&& _Val)
:为容器插入新值,第一个参数为插入位置,第二个参数为插入的元素。
vector<int> iv = { 100,200,300 }; auto beg = iv.begin(); //auto根据iv.begin()自动推断beg为迭代器类型,相当于vector<int>::iterator beg iv.insert(beg, 50);
⑤ 迭代器引用结构struct
中的成员数据:引用*iter
或指针iter
struct student{ int num; };
//1.结构数据存入到容器中 vector<student> iv; student stu; stu.num = 10; iv.push_back(stu); //赋值操作,iv和stu是两个内存地址 //2.迭代器读取容器中的结构数据 vector<student>::iterator iter; iter = iv.begin(); //或两句并一句:auto iter = iv.begin(); cout << (*iter).num << endl; //100,*iter表引用 cout << iter->num << endl; //100, iter表指针
强调:*iter
表引用,iter
表指针。
五. const iterator迭代器
const iterator
:表示迭代器指向的元素值不能改变,而不是表示这个迭代器本身不能改变(本身可以不断指向下一个元素)。
① 特性:只能从容器中读元素,不能通过迭代器改写容器中的元素值,感觉起来更像常量指针。
② 示例:
vector<int> iv = { 100,200,300 }; for (vector<int>::const_iterator iter = iv.begin(); iter != iv.end(); iter++) { cout << *iter << endl; //可以正常读容器中元素 //*iter = 4; //报错 }
③ 适用场合:
- 容器是常量
const
的时候,对应的迭代器也必须是const iterator
; - 容器中的元素值不想被迭代器改写,可以使用
const iterator
。
5.1. cbegin()和cend()操作
cbegin()/cend()
:C++11
引用的新函数,和begin()/end()
类似,只不过cbegin()/cend()
返回的常量迭代器(指向的元素值不能被改变)。
vector<int> iv = { 100,200,300 }; for (auto iter = iv.cbegin(); iter != iv.cend(); iter++) { cout << *iter << endl; //可以正常读容器中元素 //*iter = 4; //报错,cbegin返回的是常量迭代器,*iter指向的元素值被赋值改变 }
ps:即使未定义const iterator
,但是通过cbegin()/cend()
返回常量迭代器仍不可以改变容器中的值。不考虑容器是否为const
的时候,两者的作用相类似(容器中的元素值不想被迭代器改写)。
六. 迭代器失效及弥补
示例: 范围for
语句和迭代器iterator
遍历容器中的数据
vector<int> iv = { 100,200,300 }; //范围for for (auto vecitem : iv){ iv.push_back(555); //加上这句后,范围for语句遍历容器数据失效,程序崩溃 cout << vecitem << endl; } //迭代器iterator for (auto iter = iv.begin(); iter != iv.end(); iter++){ iv.push_back(555); //加上这句后,迭代器遍历容器数据失效,程序崩溃 cout << *iter << endl; }
小结1:
- 范围
for
语句遍历容器中数据 == 迭代器操作遍历容器中数据,两者等价。 - 在迭代器遍历容器内元素的过程中,使用往容器中增加元素或删除容器中的元素等操作,会使指向容器元素的指针、引用、迭代器失效,轻则程序乱码,重则程序崩溃。参考:范围for进一步讲解
解决:
如果我们在for循环或迭代器遍历容器数据的时候,一定要在容器中插入/删除某个数据时,记得操作完之后要立马break
出去,然后在重新遍历容器数据。
vector<int> iv = { 100,200,300 }; for (auto iter = iv.begin(); iter != iv.end(); iter++){ iv.push_back(555); break; } for (auto iter = iv.begin(); iter != iv.end(); iter++){ //... }
小结2: 最明智防止迭代器失效的方法:break
。
6.1. 灾难程序演示
大家在程序运行结束之前,可能会习惯释放掉容器中的数据,有人可能会写出如下代码进行释放:
vector<int> iv = { 100,200,300 }; //... for (auto iter = iv.begin(); iter != iv.end(); iter++) { iv.erase(iter); //erase函数,移除iter位置上的元素,返回下一个位置元素 }
运行直接崩溃:迭代器失效
正确的释放方法:
vector<int> iv = { 100,200,300 }; //... //释放方式一: auto iter = iv.begin(); while (iter != iv.end()) { iter = iv.erase(iter); } //释放方式二:首先判断容器是否为空,个人更常用 while (!iv.empty()) { auto iter = iv.begin(); iter = iv.erase(iter); }
小结3:
- 这种释放方式一般适用于容器中存放的不是整数
100,200,300
,而是指针的话,需要把指针指向的元素一个一个提取出来并释放掉(下面范例演示)。 - 如果容器中存放的是数据,使用
iv.clear
释放会方便。
七. 范例演示
7.1 用迭代器遍历string数据
string str("I Love BlackSilk!"); //我爱黑丝 for (auto iter = str.begin(); iter != str.end(); iter++) { *iter = toupper(*iter); //toupper函数:小写字母转换成大写字母 } cout << str << endl;
7.2 vector容器常用操作与内存释放
实例程序:容器中存储的是指针的话,必须自己手动释放内存
1、容器中存储指针数据
struct conf { char itemname[40]; //条目名 char itemcontent[100]; //条目内容 };
//SeverName = 1区; //表示服务器名称 //SeverID = 10000; //表示服务里ID conf *pconf1 = new conf; strcpy_s(pconf1->itemname,sizeof(pconf1->itemname),"SeverName"); strcpy_s(pconf1->itemcontent,sizeof(pconf1->itemcontent),"1区"); conf *pconf2 = new conf; strcpy_s(pconf2->itemname,sizeof(pconf2->itemname),"SeverID"); strcpy_s(pconf2->itemcontent,sizeof(pconf2->itemcontent),"100000"); vector<conf *> conflist; conflist.push_back(pconf1); //这里conflist[0] = pconf1指向的内容,两者指针拷贝,内存地址相同 conflist.push_back(pconf2); //这里conflist[1] = pconf2指向的内容,两者指针拷贝,内存地址相同
2、读取容器中的数据
下面我们增加一个函数功能,我希望输出服务器名称SeverName
时,函数返回的是1区
;输出服务器名称SeverID
时,函数返回的是100000
。
char *getinfo(vector<conf *>&conflist, const char *pitem) { for (auto pos = conflist.begin(); pos != conflist.end(); pos++) { if (_stricmp((*pos)->itemname, pitem) == 0) //_stricmp函数:判断两个字符串是否相等 { return (*pos)->itemcontent; } } return nullptr; }
char *p_tmp = getinfo(conflist, "ServerName"); //返回1区
3、手动释放掉容器中存储的指针指向的数据
//释放上面蓝色部分我们自己new分配出来的内存,否则会造成内存泄露 vector<conf *>::iterator pos; for (auto pos = conflist.begin(); pos != conflist.end(); pos++)//遍历容器内指向右边蓝色内存的指针 { delete (*pos); //释放的指针*pos代表容器中指向右边蓝色内存的指针*pconf1、*pconf2,没有破坏(增/减)迭代器本身数据 } conflist.clear(); //清空迭代器数据(写不写都行,程序结束系统会自动清空)
下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。 |