c++知识细节-迭代器及失效分析/实战
简介
迭代器是一种遍历容器内元素的数据类型
,有点像指向容器中各个元素的指针.在c++中,很少用[]下标的形式来访问容器,因为可能某种容器不能使用[]来访问,而迭代器的方式是所有容器都支持的,更通用.
容器的迭代器类型
vertor<int> iv{100,200,300};
vector<int>::iterator iter; //定义迭代器时,也必须是vector<int>下定义的iterator成员.
可以把vector::iterator整个理解成一个数据类型,且与容器类型紧密相关.
迭代器操作
- begin()/end(): 用来返回容器的迭代器类型.
iter = iv.begin();//如果容器中有元素,则begin返回一个迭代器,指向容器中的第一个元素
iter.iv.end(); //end返回的并不是最后一个元素,而是末端元素的后边.,可以理解成一个不存在的元素.
如果容器为空,则begin()和end()返回的是同一个迭代器,都是尾后迭代器.
- 具体使用案例
vector<int> iv = {100,200,300};
for (vector<int>::iterator iter = iv.begin(); iter!= iv.end(); iter++)
{
cout << *iter; //100,200,300
}
- rbegin()/rend(): 反向迭代器.应用于想从后往前遍历的场景.r代表reverse.注意其对应的类型为vector::reverse_iterator.
for (vector<int>::reverse_iterator riter = iv.rbegin(); riter!= iv.rend(); riter++)
{
cout << *riter; //300,200,100
}
迭代器运算符
- *iter: 返回迭代器
iter所指向元素的引用
.必须保证这个迭代器指向的是有效的容器元素;不能指向end(),因为end()指向了末端元素的后边,是一个不存在的元素.
iter = iv.begin();
cout << *iter;
- iter++;++iter: 让迭代器指向下一个元素,已经指向end()后就不能再++了.
- iter–;--iter: 让迭代器指向容器中上一个元素,已经指向begin()后就不能再–了.
const_iterator迭代器
常量迭代器.此迭代器指向的元素值不能改变,迭代器的指向可以改变.即只能从容器中读元素,而不能改写容器中的元素值,感觉像是一个常量指针.
vector<int>::const_iterator citer = iv.begin();
Tips:如果容器对象为常量,则遍历时必须用常量迭代器.
- cbegin()/cend(): c++11新引入的两个新函数,这两个函数返回的都是常量迭代器,此时可以用auto类型来接.
迭代器失效问题
范围for等价与使用迭代器来遍历容器内元素.
for (auto ivitem: iv)
{
iv.push_back(666);
cout << ivitem;
}
等价于
for (auto iter = iv.begin(); beg != iv.end(); iter++)
{
iv.push_back(666);
cout << *iter;
}
在操作迭代器的过程中,千万不要改变容器的容量,也就是不要增删容器中的元素.这些操作可能回事指向容器元素的指针,引用,迭代器失效,一旦失效,就不能再代表任何容器中的元素,此时若继续使用这些失效的指针,迭代器等,就会发生程序错误,甚至导致程序崩溃.
Tips:如果真的要在循环中插入数据,一定要在插入后即时更新迭代器.比如退出循环重新开一个新循环.或者是使用insert()后返回新的迭代器来覆盖旧的迭代器.例子如下:
beg = iv.begin();
int icount = 0;
while(beg != iv.end())
{
beg = iv.insert(beg,icount + 80);
icount++;
if (icount > 10)
break;
++beg;
}
此例子中,每次insert元素后都对beg迭代器进行了更新,且每进入下一次进入循环时也对end()迭代器进行了更新.循环退出的条件是用icount来控制的.虽然这样写不会有问题,单还是不推荐这种写法.
- vector容器的清空方法
erase(iter): 移除iter位置上的元素,返回下一个元素的位置
如果vector内元素为普通类型,可以直接用iv.clear()方法来实现清空,但是如果vector内存放的是指针元素,此时需要单独去清除每个指针指向的内存.如果直接循环erase()会发生失效问题:
for (auto iter = iv.begin(); iter != iv.end(); iter++)
{
iv.erase(iter);
}
可用类型下面代码的方法来避免迭代器失效问题.
while(!iv.empty())
{
auto iter = iv.begin(); //每次都更新begin()
iv.erase(iter);
}
范例演示
- 用迭代器遍历string类型数据
string str("I Love China");
for (auto iter = str.begin(); iter != str.end(); iter++)
{
*iter = toupper(*iter);
}
cout << str;
- vector常用操作与内存释放
struct conf
{
char itemname[40];
char itemcontent[100];
};
conf * pconf1 = new conf;
strcpy_s(pconf1->itemname, sizeof(pconf1->itemname),"ServerName");
strcpy_s(pconf1->itemcontent, sizeof(pconf1->itemcontent),"1区");
conf * pconf2 = new conf;
strcpy_s(pconf2->itemname, sizeof(pconf2->itemname),"ServerId");
strcpy_s(pconf2->itemcontent, sizeof(pconf2->itemcontent),"111111");
vector<conf *> conflist;
conflist.push_back(pconf1); //[0]
conflist.push_back(pconf2); //[1]
char * p_tmp = getinfo(conflist, "ServerName");
//用完之后我们要释放内存
vector<conf*>::iterator pos;
for (pos = conflist.begin();pos != conflist.end();pos ++)
{
delete (*pos); //*pos才是指向内容的指针
}
/********/
char * getinfo(vector<conf *> &conflist, const char *pitem)
{
for (auto pos = conflist.begin(); pos != conflist.end();++pos)
{
if (_strcmp((*pos)->itemname,pitem) == 0)
{
return (*pos)->itemcontent;
}
}
return nullptr;
}