一、迭代器简介
迭代器是一种遍历容器内元素的数据类型。这种数据类型感觉有点像指针,读者就理解为迭代器是用来指向容器中的某个元素的。
string可以通过[ ](下标)访问string字符串中的字符,vector可以通过[ ]访问vector中的元素。在C++中,很少通过下标访问它们,一般都是才有迭代器来访问。
除了vector容器外,C++标准库中还有几个其它种类的容器。这些容器都可以使用迭代器来遍历其它的元素内容。string其实是字符串,不属于容器,但string也支持用迭代器遍历。
通过迭代器,可以读取容器中的元素值、修改容器中某个迭代器所代表(所指向)的元素值。此外,迭代器可以像指针一样通过++(自加)、--(自减)等运算符从容器中的一个元素移动到另外一个元素。
C++标准库中有很多容器,并为每个容器定义了对应的一种迭代器类型,有很多容器不支持[]下标操作,但是容器都支持迭代器操作。建议在访问容器中的元素时不用下标访问,而是用迭代器来访问容器中的元素。
二、容器的迭代器类型
上面讲过,C++标准为每种容器都定义了对应的迭代器类型。这里就以容器vector为例。
vector
vector
可以把vector
三、迭代器begin/end、反向迭代器rbegin/rend操作
1、迭代器
每一种容器,如vector,都定义了一个叫begin的成员函数和一个叫end的成员函数。这两个成员函数正好用来返回迭代器类型。
(1)begin返回一个迭代器类型.
int *p = NULL;
int arrayData[] = {400,500,600};
p = arrayData; //指针p指向arrayData首地址
cout << *p << endl; //打印p指向arrayData首地址存储的元素
vector myInt1 = { 100,200,300 }; //定义一个容器
vector ::iterator iter; //定义迭代器,因为容器是以vector 开头,所以迭代器也必须是以vector 开头
iter = myInt1.begin();//如果myInt1不为空,则返回myInt1第一个元素,即myInt1[0]里面的内容。类似于iter指针指向myInt1.begin首地址。
cout << *iter << endl;
(2)end返回一个迭代器类型(理解成返回一个迭代器)。
iter = myInt1.end();//end返回的迭代器指向的并不是末端元素,而是末端元素的后面,end返回的内容类似于C语言字符串数组中的' '结束符。
(3)如果容器为空,则begin返回的迭代器和end返回的迭代器指向位置相同。
vectormyInt2; vector ::iterator iterBegin; vector ::iterator iterEnd; iterBegin = myInt2 .begin(); iterEnd = myInt2 .end(); if(iterBegin == iterEnd) { cout << "容器为空" << endl; }
下面用示意图对begin和end在迭代器中的指向进行说明:
end返回的迭代器并不指向容器vector中的任何元素,它仅是起到一个容器内容结束标志作用。如果迭代器从容器的begin位置不断往后偏移,当偏移到end位置,表示已经遍历了容器中的所有元素。
(4)通过迭代器访问容器中的元素
vectormyInt1 = { 100,200,300 }; //定义一个容器 vector ::iterator iter; for(iter = myInt1.begin();iter != myInt1.end();iter++) { cout << *iter << " "; } cout << endl;
2、反向迭代器
如果想从后面往前面遍历一个容器,用反向迭代器比较方便。反向迭代器使用的成员函数为rbegin和rend。
(1)rbegin返回一个反向迭代器类型,指向容器的最后一个元素。
(2)rend返回一个反向迭代器类型,指向容器的第一个元素的前面位置。
vector四、迭代器运算符myInt1 = { 100,200,300 }; //定义一个容器 vector ::reverse_iterator iter; for (iter = myInt1.rbegin(); iter != myInt1.rend(); iter++) { cout << *iter << " "; } cout << endl;
1、*iter:返回迭代器iter所指向的引用。必须保证iter迭代器指向有效容器的元素,不能指向无效的end或者rend。
2、++iter(iter++):迭代器自加。让迭代器指向容器中的下一个元素,但已经指向end后就不能再++,否则系统报错。
3、--iter(iter--):迭代器自减。让迭代器指向容器中的前一个元素,但已经指向rend后就不能再--,否则系统报错。
4、iter1 == iter2或iter1 != iter2:判断两个迭代器是否相等。两个迭代器指向同一个元素则相等,否则不等。
5、结构体成员引用。
vectorvStu; student mystu; mystu.num = 100; vStu.push_back(mystu);//把对象mystu复制到vStu容器中,vStu和mystu没有直接关系 mystu.num = 200;//不会改变容器中元素的值,容器中内容是复制进去的 vector ::iterator iter; iter = vStu.begin();//指向第一个元素 cout << (*iter).num << endl;//100,*iter是一个结构体变量,用"."成员来引用成员。 cout << iter->num << endl;//100,iter想象成一个指针,所以“->”引用成员 请注意:一定要确保迭代器指向有效的容器中的元素,否则会导致意想不到的结果。
五、const_iterator常量迭代器
常量迭代器只能从容器中读取元素,不能通过改迭代器修改容器中的元素。const_iterator更像一个常量指针。如下例子可以更改容器里面的元素值:
vectormyInt1 = { 100,200,300 }; //定义一个容器 int data[] = {10,20,30};//定义一个data数组 int i = 0;//定义一个int类型变量 vector ::iterator iter; for (iter = myInt1.begin(),i = 0;(i 我们把上面“vector
::iterator iter;”中的iterator迭代器类型改成常量迭代器“const_iterator”,再运行程序,系统报错:“iter不能给常量赋值”,如下图所示:
可以看到,常量迭代器不能修改容器里面元素的值,我们把上面修改容器元素值的for循环屏蔽掉,上面程序正常运行,说明常量迭代器可以遍历不带const的容器。需要注意的是,如果容器是常量容器,则必须用常量迭代器来访问和遍历,否则会报错。
const vectormyInt1 = { 100,200,300 }; //定义一个常量容器 vector ::const_iterator iter;//必须定义一个常量迭代器 for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//遍历myInt1元素 { cout << *iter << " ";//遍历myInt1元素 } cout << endl;
在C++11中引入两个函数,cbegin和cend,无论容器是否是常量容器,cbegin和cend返回的都是常量迭代器const_iterator。
六、迭代器失效在操作迭代器的过程中(如使用迭代器的for循环体),千万不要在循环体内改变vector对象容量的操作,比如增加、删除vector容器中的元素。如:
for(auto beg = vecvalue.begin(),end = vecvalue.end();beg != end;++beg) { //在这个循环体内不要改变vecvalue对象的容量 }对于向容器中添加元素或者从容器中删除元素操作要小心,因为这些操作可能都会使指向容器元素的迭代器(包括指针、引用)失效。
解决的方法:如果在一个使用了迭代器的循环体中插入元素到容器,只插一个元素后就应该立即跳出循环体,不能再继续使用这些迭代器操作容器。如下所示:
for(auto beg = vecvalue.begin(),end = vecvalue.end();beg != end;++beg) { vecvalue.push_back(123);//push_back函数可能带来内存溢出 break;//立刻跳出循环 }//重新定位迭代器 for(auto beg = vecvalue.begin(),end = vecvalue.end();beg != end;++beg) { ...... }下面讲进行一些灾难程序演示:
(1)灾难程序演示1:
vectormyInt1 = { 100,200,300 }; //定义一个容器 auto beg = myInt1.begin(); auto end = myInt1.end(); while(beg != end) { cout << *beg << endl; } 接着在循环中增加代码,注意while循环体中代码的变化: vector myInt1 = { 100,200,300 }; //定义一个容器 auto beg = myInt1.begin(); auto end = myInt1.end(); while(beg != end) { cout << *beg << endl; myInt1.insert(beg,80);//在begin这个位置插入新元素,可以用insert,插入新元素容量不断加大,begin和end位置可能已经被刷新修改,需要重新定位begin和end break; //插入一个新值则跳出循环 ++beg; //程序执行不到这里,没有存在的意义 } 怎么能往容器里面插入数据,且程序安全有序的运行,以下代码是连续插入多条数据的解决方案:
vectormyInt1 = { 100,200,300 }; //定义一个容器 auto beg = myInt1.begin(); int count = 0; while (beg != myInt1.end()) { beg = myInt1.insert(beg, count + 50);//在begin位置插入元素 count++; if (count > 10) { break; } beg++; } vector ::iterator iter;//定义一个迭代器 for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//迭代器遍历容器的元素并打印 { cout << *iter << " "; } (2)灾难程序演示2
vectormyInt1 = { 100,200,300,400,500}; //定义一个容器 for (auto iter = myInt1.begin(); iter != myInt1.end();++iter) { myInt1.erase(iter); } vector ::iterator iter;//定义一个迭代器 for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//迭代器遍历容器的元素并打印 { cout << *iter << " "; } 程序崩溃,原因是进行erase(iter)后,iter执向的是下一个元素的位置,导致在iter擦除元素500后,返回的是end的指向位置,但是end的位置不是有效的,所以程序会崩溃。
清除容器的元素,直接用clear直接清空容器的元素,如果用迭代器对容器内的元素一个一个的删除,一个简单直接且有效的方法如下:
vectormyInt1 = { 100,200,300,400,500}; //定义一个容器 while (!myInt1.empty()) { auto iter = myInt1.cbegin(); myInt1.erase(iter); } vector ::iterator iter;//定义一个迭代器 for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//迭代器遍历容器的元素并打印 { cout << *iter << " "; } 七、范例演示
1、迭代器遍历字符串。
string str = "jinxueHou"; for (auto iter = str.begin(); iter != str.end(); ++iter) { cout << *iter;//一个一个字符串输出 } cout << endl;2、vector容器常用操作与内存释放。
(1)普通容器释放内存
vectorstr;//定义一个string类型的容器 str.push_back("12345678910");//往容器里面添加字符串 for (int i=0;i<1000000;i++)//一直往容器末尾添加数据 { str.push_back("12345678910");//push_back函数可能带来内存溢出 } cout << "clear前vector的容量为:" << str.capacity() << endl; str.clear(); cout << "clear后vector的容量为:" << str.capacity() << endl; //先创建一个临时拷贝与原先的vector一致,值得注意的是,此时的拷贝其容量是尽可能小的符合所需数据的。 //紧接着将该拷贝与原先的vector v进行 交换。好了此时,执行交换后,临时变量会被销毁,内存得到释放。 //此时的v即为原先 的临时拷贝,而交换后的临时拷贝则为容量非常大的vector(不过已经被销毁) vector (str).swap(str); cout << "swap后vector的容量为:" << str.capacity() << endl;或vector ().swap(str); 释放容器内存方法为:
vector
(v).swap(v); 或 vector ().swap(v); type为容器数据类型,v是容器名称。
(2)指针元素容器内存释放
#include#include using namespace std; class student { public: student(int num) { this->num = num; cout << "num = " << this->num << endl; return; } ~student() { cout << "内存析构调用" << endl; return; } int num; }; int main() { vector vstu; student* mystu1 = new student(100); student* mystu2 = new student(150); student* mystu3 = new student(200); student* mystu4 = new student(250); vstu.push_back(mystu1); vstu.push_back(mystu2); vstu.push_back(mystu3); vstu.push_back(mystu4); cout <<"clear之前容器容量:" << vstu.capacity() << endl; //方式一 for (auto &i:vstu)//必须执行这个步骤,否则析构函数占用的内存无法释放 { delete i;//删除vstu元素 } //方式二 //vector ::iterator vtemp; //for (vtemp = vstu.begin(); vtemp != vstu.end();vtemp++)//必须执行这个步骤,否则析构函数占用的内存无法释放 //{ // delete (*vtemp);//删除vstu元素 //} vstu.clear(); cout << "clear之后容器容量:" << vstu.capacity() << endl; vector ().swap(vstu); cout << "swap之后容器容量: " << vstu.capacity() << endl; return 0; } 到此,迭代器的操作和内存释放应用功能已完成。
2022.06.28结。



