栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

一、迭代器简介

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

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

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

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

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

二、容器的迭代器类型

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

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

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

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

三、迭代器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返回的迭代器指向位置相同。

vector  myInt2;  
vector  ::iterator iterBegin;  
vector  ::iterator iterEnd;    
iterBegin = myInt2 .begin();
iterEnd   = myInt2 .end();

if(iterBegin == iterEnd)
{
    cout << "容器为空" << endl;
}

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

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

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

vector  myInt1 = { 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、结构体成员引用。

vector  vStu;
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更像一个常量指针。如下例子可以更改容器里面的元素值:

vector  myInt1 = { 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 vector  myInt1 = { 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:

vector  myInt1 = { 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;  //程序执行不到这里,没有存在的意义
}

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

vector  myInt1 = { 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

vector  myInt1 = { 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直接清空容器的元素,如果用迭代器对容器内的元素一个一个的删除,一个简单直接且有效的方法如下:

vector  myInt1 = { 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)普通容器释放内存

vector  str;//定义一个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结。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/997152.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号