• C++学习第八课--迭代器精彩演绎、失效分析及弥补、实战笔记


    一、迭代器简介

          迭代器是一种遍历容器内元素的数据类型。这种数据类型感觉有点像指针,读者就理解为迭代器是用来指向容器中的某个元素的。

          string可以通过[ ](下标)访问string字符串中的字符,vector可以通过[ ]访问vector中的元素。在C++中,很少通过下标访问它们,一般都是才有迭代器来访问。

           除了vector容器外,C++标准库中还有几个其它种类的容器。这些容器都可以使用迭代器来遍历其它的元素内容。string其实是字符串,不属于容器,但string也支持用迭代器遍历。

           通过迭代器,可以读取容器中的元素值、修改容器中某个迭代器所代表(所指向)的元素值。此外,迭代器可以像指针一样通过++(自加)、--(自减)等运算符从容器中的一个元素移动到另外一个元素。

           C++标准库中有很多容器,并为每个容器定义了对应的一种迭代器类型,有很多容器不支持[]下标操作,但是容器都支持迭代器操作。建议在访问容器中的元素时不用下标访问,而是用迭代器来访问容器中的元素。

    二、容器的迭代器类型

    上面讲过,C++标准为每种容器都定义了对应的迭代器类型。这里就以容器vector为例。

    vector <int> myInt1={100,200,300};  //定义一个容器

    vector <int> ::iterator iter;       //定义迭代器,因为容器是以vector <int> 开头,所以迭代器也必须是以vector <int> 开头

    可以把vector <int> ::iterator看成iterator迭代器类型,当用这个类型定义一个变量时,这个变量就是一个迭代器变量。

    三、迭代器begin/end、反向迭代器rbegin/rend操作

    1、迭代器

    每一种容器,如vector,都定义了一个叫begin的成员函数和一个叫end的成员函数。这两个成员函数正好用来返回迭代器类型。

    (1)begin返回一个迭代器类型.

    1. int *p = NULL;
    2. int arrayData[] = {400,500,600};
    3. p = arrayData; //指针p指向arrayData首地址
    4. cout << *p << endl; //打印p指向arrayData首地址存储的元素
    5. vector <int> myInt1 = { 100,200,300 }; //定义一个容器
    6. vector <int> ::iterator iter; //定义迭代器,因为容器是以vector <int> 开头,所以迭代器也必须是以vector <int> 开头
    7. iter = myInt1.begin();//如果myInt1不为空,则返回myInt1第一个元素,即myInt1[0]里面的内容。类似于iter指针指向myInt1.begin首地址。
    8. cout << *iter << endl;

    (2)end返回一个迭代器类型(理解成返回一个迭代器)。

    iter = myInt1.end();//end返回的迭代器指向的并不是末端元素,而是末端元素的后面,end返回的内容类似于C语言字符串数组中的'\0'结束符。

    (3)如果容器为空,则begin返回的迭代器和end返回的迭代器指向位置相同。

    1. vector <int> myInt2;
    2. vector <int> ::iterator iterBegin;
    3. vector <int> ::iterator iterEnd;
    4. iterBegin = myInt2 .begin();
    5. iterEnd = myInt2 .end();
    6. if(iterBegin == iterEnd)
    7. {
    8. cout << "容器为空" << endl;
    9. }

    下面用示意图对begin和end在迭代器中的指向进行说明:

     end返回的迭代器并不指向容器vector中的任何元素,它仅是起到一个容器内容结束标志作用。如果迭代器从容器的begin位置不断往后偏移,当偏移到end位置,表示已经遍历了容器中的所有元素。

    (4)通过迭代器访问容器中的元素

    1. vector <int> myInt1 = { 100,200,300 }; //定义一个容器
    2. vector <int> ::iterator iter;
    3. for(iter = myInt1.begin();iter != myInt1.end();iter++)
    4. {
    5. cout << *iter << " ";
    6. }
    7. cout << endl;

     2、反向迭代器

    如果想从后面往前面遍历一个容器,用反向迭代器比较方便。反向迭代器使用的成员函数为rbegin和rend。

    (1)rbegin返回一个反向迭代器类型,指向容器的最后一个元素。

    (2)rend返回一个反向迭代器类型,指向容器的第一个元素的前面位置。

    1. vector <int> myInt1 = { 100,200,300 };  //定义一个容器
    2. vector <int> ::reverse_iterator iter;
    3. for (iter = myInt1.rbegin(); iter != myInt1.rend(); iter++)
    4. {
    5. cout << *iter << " ";
    6. }
    7. cout << endl;

     四、迭代器运算符

    1、*iter:返回迭代器iter所指向的引用。必须保证iter迭代器指向有效容器的元素,不能指向无效的end或者rend。

    2、++iter(iter++):迭代器自加。让迭代器指向容器中的下一个元素,但已经指向end后就不能再++,否则系统报错。

    3、--iter(iter--):迭代器自减。让迭代器指向容器中的前一个元素,但已经指向rend后就不能再--,否则系统报错。

    4、iter1 == iter2或iter1 != iter2:判断两个迭代器是否相等。两个迭代器指向同一个元素则相等,否则不等。

    5、结构体成员引用。

    1. vector <student> vStu;
    2. student mystu;
    3. mystu.num = 100;
    4. vStu.push_back(mystu);//把对象mystu复制到vStu容器中,vStu和mystu没有直接关系
    5. mystu.num = 200;//不会改变容器中元素的值,容器中内容是复制进去的
    6. vector <student>::iterator iter;
    7. iter = vStu.begin();//指向第一个元素
    8. cout << (*iter).num << endl;//100,*iter是一个结构体变量,用"."成员来引用成员。
    9. cout << iter->num << endl;//100,iter想象成一个指针,所以“->”引用成员
    10. 请注意:一定要确保迭代器指向有效的容器中的元素,否则会导致意想不到的结果。

    五、const_iterator常量迭代器

    常量迭代器只能从容器中读取元素,不能通过改迭代器修改容器中的元素。const_iterator更像一个常量指针。如下例子可以更改容器里面的元素值:

    1. vector <int> myInt1 = { 100,200,300 }; //定义一个容器
    2. int data[] = {10,20,30};//定义一个data数组
    3. int i = 0;//定义一个int类型变量
    4. vector <int> ::iterator iter;
    5. for (iter = myInt1.begin(),i = 0;(i<sizeof(data))&&(iter != myInt1.end()); iter++,i++)//遍历myInt1元素和data数组元素
    6. {
    7. *iter = *iter+data[i];//修改myInt1容器的元素值
    8. }
    9. for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//遍历myInt1元素
    10. {
    11. cout << *iter << " ";//遍历myInt1元素
    12. }
    13. cout << endl;

    我们把上面“vector <int> ::iterator iter;”中的iterator迭代器类型改成常量迭代器“const_iterator”,再运行程序,系统报错:“iter不能给常量赋值”,如下图所示:

     

     

    可以看到,常量迭代器不能修改容器里面元素的值,我们把上面修改容器元素值的for循环屏蔽掉,上面程序正常运行,说明常量迭代器可以遍历不带const的容器。需要注意的是,如果容器是常量容器,则必须用常量迭代器来访问和遍历,否则会报错。

    1. const vector <int> myInt1 = { 100,200,300 }; //定义一个常量容器
    2. vector <int> ::const_iterator iter;//必须定义一个常量迭代器
    3. for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//遍历myInt1元素
    4. {
    5. cout << *iter << " ";//遍历myInt1元素
    6. }
    7. cout << endl;

     

     在C++11中引入两个函数,cbegin和cend,无论容器是否是常量容器,cbegin和cend返回的都是常量迭代器const_iterator

    六、迭代器失效

    在操作迭代器的过程中(如使用迭代器的for循环体),千万不要在循环体内改变vector对象容量的操作,比如增加、删除vector容器中的元素。如:

    1. for(auto beg = vecvalue.begin(),end = vecvalue.end();beg != end;++beg)
    2. {
    3. //在这个循环体内不要改变vecvalue对象的容量
    4. }

    对于向容器中添加元素或者从容器中删除元素操作要小心,因为这些操作可能都会使指向容器元素的迭代器(包括指针、引用)失效。

    解决的方法:如果在一个使用了迭代器的循环体中插入元素到容器,只插一个元素后就应该立即跳出循环体,不能再继续使用这些迭代器操作容器。如下所示:

    1. for(auto beg = vecvalue.begin(),end = vecvalue.end();beg != end;++beg)
    2. {
    3. vecvalue.push_back(123);//push_back函数可能带来内存溢出
    4. break;//立刻跳出循环
    5. }
    1. //重新定位迭代器
    2. for(auto beg = vecvalue.begin(),end = vecvalue.end();beg != end;++beg)
    3. {
    4. ......
    5. }

    下面讲进行一些灾难程序演示:

    (1)灾难程序演示1:

    1. vector <int> myInt1 = { 100,200,300 }; //定义一个容器
    2. auto beg = myInt1.begin();
    3. auto end = myInt1.end();
    4. while(beg != end)
    5. {
    6. cout << *beg << endl;
    7. }
    8. 接着在循环中增加代码,注意while循环体中代码的变化:
    9. vector <int> myInt1 = { 100,200,300 }; //定义一个容器
    10. auto beg = myInt1.begin();
    11. auto end = myInt1.end();
    12. while(beg != end)
    13. {
    14. cout << *beg << endl;
    15. myInt1.insert(beg,80);//在begin这个位置插入新元素,可以用insert,插入新元素容量不断加大,begin和end位置可能已经被刷新修改,需要重新定位begin和end
    16. break; //插入一个新值则跳出循环
    17. ++beg; //程序执行不到这里,没有存在的意义
    18. }

    怎么能往容器里面插入数据,且程序安全有序的运行,以下代码是连续插入多条数据的解决方案:

    1. vector <int> myInt1 = { 100,200,300 }; //定义一个容器
    2. auto beg = myInt1.begin();
    3. int count = 0;
    4. while (beg != myInt1.end())
    5. {
    6. beg = myInt1.insert(beg, count + 50);//在begin位置插入元素
    7. count++;
    8. if (count > 10)
    9. {
    10. break;
    11. }
    12. beg++;
    13. }
    14. vector <int> ::iterator iter;//定义一个迭代器
    15. for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//迭代器遍历容器的元素并打印
    16. {
    17. cout << *iter << " ";
    18. }

     (2)灾难程序演示2

    1. vector <int> myInt1 = { 100,200,300,400,500}; //定义一个容器
    2. for (auto iter = myInt1.begin(); iter != myInt1.end();++iter)
    3. {
    4. myInt1.erase(iter);
    5. }
    6. vector <int> ::iterator iter;//定义一个迭代器
    7. for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//迭代器遍历容器的元素并打印
    8. {
    9. cout << *iter << " ";
    10. }

    程序崩溃,原因是进行erase(iter)后,iter执向的是下一个元素的位置,导致在iter擦除元素500后,返回的是end的指向位置,但是end的位置不是有效的,所以程序会崩溃。

    清除容器的元素,直接用clear直接清空容器的元素,如果用迭代器对容器内的元素一个一个的删除,一个简单直接且有效的方法如下:

    1. vector <int> myInt1 = { 100,200,300,400,500}; //定义一个容器
    2. while (!myInt1.empty())
    3. {
    4. auto iter = myInt1.cbegin();
    5. myInt1.erase(iter);
    6. }
    7. vector <int> ::iterator iter;//定义一个迭代器
    8. for (iter = myInt1.begin(); iter != myInt1.end(); iter++)//迭代器遍历容器的元素并打印
    9. {
    10. cout << *iter << " ";
    11. }

    七、范例演示

    1、迭代器遍历字符串。

    1. string str = "jinxueHou";
    2. for (auto iter = str.begin(); iter != str.end(); ++iter)
    3. {
    4. cout << *iter;//一个一个字符串输出
    5. }
    6. cout << endl;

    2、vector容器常用操作与内存释放。

    (1)普通容器释放内存

    1. vector <string> str;//定义一个string类型的容器
    2. str.push_back("12345678910");//往容器里面添加字符串
    3. for (int i=0;i<1000000;i++)//一直往容器末尾添加数据
    4. {
    5. str.push_back("12345678910");//push_back函数可能带来内存溢出
    6. }
    7. cout << "clear前vector的容量为:" << str.capacity() << endl;
    8. str.clear();
    9. cout << "clear后vector的容量为:" << str.capacity() << endl;
    10. //先创建一个临时拷贝与原先的vector一致,值得注意的是,此时的拷贝其容量是尽可能小的符合所需数据的。
    11. //紧接着将该拷贝与原先的vector v进行 交换。好了此时,执行交换后,临时变量会被销毁,内存得到释放。
    12. //此时的v即为原先 的临时拷贝,而交换后的临时拷贝则为容量非常大的vector(不过已经被销毁)
    13. vector <string>(str).swap(str);
    14. cout << "swap后vector的容量为:" << str.capacity() << endl;或vector <string>().swap(str);

    释放容器内存方法为:

    vector<type>(v).swap(v);  或  vector<type>().swap(v);

    type为容器数据类型,v是容器名称。

    (2)指针元素容器内存释放

    1. #include <iostream>
    2. #include <vector>
    3. using namespace std;
    4. class student
    5. {
    6. public:
    7. student(int num)
    8. {
    9. this->num = num;
    10. cout << "num = " << this->num << endl;
    11. return;
    12. }
    13. ~student()
    14. {
    15. cout << "内存析构调用" << endl;
    16. return;
    17. }
    18. int num;
    19. };
    20. int main()
    21. {
    22. vector<student*> vstu;
    23. student* mystu1 = new student(100);
    24. student* mystu2 = new student(150);
    25. student* mystu3 = new student(200);
    26. student* mystu4 = new student(250);
    27. vstu.push_back(mystu1);
    28. vstu.push_back(mystu2);
    29. vstu.push_back(mystu3);
    30. vstu.push_back(mystu4);
    31. cout <<"clear之前容器容量:" << vstu.capacity() << endl;
    32. /*内存释放:程序员自己new就需要自己释放,否则会造成内存泄漏*/
    33. //方式一
    34. for (auto &i:vstu)//必须执行这个步骤,否则析构函数占用的内存无法释放
    35. {
    36. delete i;//删除vstu元素
    37. }
    38. //方式二
    39. //vector<student*>::iterator vtemp;
    40. //for (vtemp = vstu.begin(); vtemp != vstu.end();vtemp++)//必须执行这个步骤,否则析构函数占用的内存无法释放
    41. //{
    42. // delete (*vtemp);//删除vstu元素
    43. //}
    44. vstu.clear();
    45. cout << "clear之后容器容量:" << vstu.capacity() << endl;
    46. vector<student*>().swap(vstu);
    47. cout << "swap之后容器容量: " << vstu.capacity() << endl;
    48. return 0;
    49. }

    到此,迭代器的操作和内存释放应用功能已完成。

    2022.06.28结。

  • 相关阅读:
    【JUC】ThreadLocal
    线性表:数组、链表、栈、队列
    Redis 第二章:通用命令
    R语言奇异值分解
    本周遇到的一些问题记录
    flutter 安装流程
    更新操作及自动填充
    分组后合并记录中的字段值
    MVVM中wpf设置控件是否可见
    React从入门到精通
  • 原文地址:https://blog.csdn.net/euxnijuoh/article/details/125509948