13.2.3 序列式容器中元素的插入和删除

    在普通数组中,元素的插入和删除是件很烦琐的事情,但在序列式容器中,只要调用操作函数,所有的事情都由STL类库自动完成,而且容器对象都能随着元素的插入和删除自动增大或缩小。

    注意

    在创建数组时,需要指定元素的个数以帮助编译器开辟所需内存区域,而在创建容器对象时不必指明容器对象的最大容量,因为由STL类库管理的容器对象内存是动态的。

    1.push_back(t)和pop_back(void)

    push_back(t)和pop_back(void)允许在容器对象的末尾进行安插和删除操作,从字面上不难看出push_back(t)是在最末端安插一个元素t,容器对象的容量自动增大,pop_back(void)是删除最末端的元素,容器对象的容量自动减小。以vector为例如以下示例代码13.2所示。

    代码13.2 push_back(t)和pop_back(void)函数InsertAndDelete1


    <————————————文件名:example1302.cpp——————————————-> 01 #include<iostream> 02 #include<vector> 03 using namespace std; 04 int main() 05 { 06 vector<int>obV(3,1);//创建一个vector类对象,3个int型元素,全为1 07 obV. push_back(2);//将int型数据2安插在容器对象obV末尾 08 for(unsigned int i=0;i<obV. size();i++) 09 { 10 cout<<obV[i]<<"";//输出处理,看是否安插成功 11 } 12 cout<<endl; 13 obV. pop_back();//将最后一个元素弹出 14 for(unsigned int i=0;i<obV. size();i++) 15 { 16 cout<<obV[i]<<"";//输出处理,看是否弹出成功 17 } 18 cout<<endl; 19 return 0; 20 }

    输出结果如下所示。


    1 1 1 2 1 1 1

    【代码解析】代码第7行将数据插入到容器最后,代码第13行将最后一个元素弹出。最后的输出结果验证了容器对象大小的动态变化。

    2.push_front(t)和pop_front(void)

    list和deque还提供了push_front(t)以及pop_front(void),分别用于在容器对象的头部安插或删除一个元素,不过,这两个操作函数对vector并不适用。以list为例,如以下示例代码13.3所示。

    代码13.3 push_front(t)和pop_front(void)函数InsertAndDelete2


    <————————————文件名:example1303.cpp——————————————-> 01 #include<iostream> 02 #include<list> 03 using namespace std; 04 int main() 05 { 06 int sz[5]={1,2,3,4,5}; 07 list<int>obL(sz,sz+5);//创建一个list对象,包含5个int型元素 08 obL. push_front(0);//将int型数据0安插在容器对象obL头部 09 list<int>:iterator iter=obL. begin(); 10 while(iter!=obL. end()) 11 { 12 cout<<(*iter)<<"";//输出处理,看是否安插成功 13 iter++; 14 } 15 cout<<endl; 16 obL. pop_front();//将第1个元素弹出 17 iter=obL. begin(); 18 while(iter!=obL. end()) 19 { 20 cout<<(*iter)<<"";//输出处理,看是否弹出成功 21 iter++; 22 } 23 cout<<endl; 24 return 0; }

    输出结果如下所示。


    0 1 2 3 4 5 1 2 3 4 5

    【代码解析】代码第8行和第16行使用简单的两个函数(push_front和pop_front)完成了从头部安插和删除一个元素的操作。

    3.front(void)和back(void)

    前面介绍的push_back(t)和push_front(t),以及pop_back(void)和pop_front(void)返回值类型都是void,也就是说,无法从pop_back(void)和pop_front(void)得到被删除的元素值。为此,STL提供了front(void)和back(void)来读取序列式容器对象的最前端元素和最末端元素,这两个函数方法对vector、list和deque都适用。以deque为例,如以下示例代码13.4所示。

    代码13.4 front(void)和back(void)函数InsertAndDelete3


    <————————————文件名:example1304.cpp——————————————-> 01 #include<iostream> 02 #include<deque>//使用deque必须包含的头文件 03 using namespace std; 04 int main() 05 { 06 double sz[6]={0,1,2,3,4,5}; 07 deque<double>obD(sz,sz+6); 08 cout<<obD. front()<<endl;//读取最前端元素的值 09 cout<<obD. back()<<endl;//读取最末端元素的值 10 return 0; 11 }

    输出结果如下所示。


    0 5

    【代码解析】需要注意的是代码第8行的front(void)和第9行的back(void)只能用于元素的读取,而不能用于改写元素的值,如“obD.front()=1;”是非法的。

    说明

    前面介绍的6个函数操作:push_back(t)、push_front(t)、pop_back(void)、pop_front(void)、front(void)和back(void),其复杂度都是固定的,换言之,操作所用的时间与容器对象元素的个数无关。

    4.insert插入操作

    不仅可以在最前端和最末端安插元素,序列式容器还可在中间特定位置安插元素,insert成员函数有如下几种重载形式,编译器根据传递的参数决定调用哪个版本。

    ❑iterator insert(iterator p,elemType t);

    将元素t安插于迭代器p之前,返回的迭代器指向被安插的元素。

    ❑void insert(iterator p,int num,elemType t);

    在迭代器p之前安插num个元素,这些元素的值都为t,无返回值。

    ❑void insert(iterator p,iterator first,iterator last);

    在迭代器p之前安插[first,last)之间的所有元素。

    从以下示例代码中体会3种insert不同的操作,如代码13.5所示。

    代码13.5 序列式容器通用的3种insert操作InsertMethods


    <———————————-文件名:example1305.cpp———————————————> 01 #include<iostream> 02 #include<vector> 03 using namespace std; 04 void disp(vector<int>&x)//定义disp函数用以输出容器对象所有元素 05 { 06 unsigned int i=0; 07 for(;i<x. size();i++) 08 { 09 cout<<x[i]<<""; 10 } 11 } 12 int main() 13 { 14 vector<int>obD(5,0);//创建一个vector<int>容器对象 15 vector<int>:iterator pD=obD. end();//创建迭代器pD 16 pD=obD. insert(pD,1);//在尾部插入元素1,并使迭代器指向新插入的1 17 disp(obD); 18 cout<<endl; 19 obD. insert(pD,2,3);//在新插入的元素1之前插入两个元素3 20 disp(obD); 21 cout<<endl; 22 pD=obD. begin();//很重要,插入后,原来的迭代器可能失效 23 int sz[3]={7,8,9}; 24 obD. insert(pD,sz,sz+3);//将两个指针(相当于迭代器)插入到头部 25 disp(obD); 26 cout<<endl; 27 return 0; 28 }

    输出结果如下所示。


    0 0 0 0 0 1 0 0 0 0 0 3 3 1 7 8 9 0 0 0 0 0 3 3 1

    【代码解析】代码第16行、第19行和第24行演示了3种insert操作的用法,初学者容易忽略的是3种操作的返回值,代码第16行的语句“pD=obD.insert(pD,1);”在迭代器前插入元素1,并用指向新插入的元素1的迭代器为pD赋值,这有效避免发生“迭代器失效”现象。

    “迭代器失效”(Iterator Invalid)将在迭代器一节中进行详细介绍,从字面意义上理解这意味着迭代器不再指向原来的位置,这是由于系统为容器的扩充或删除等操作造成的,因此在继续使用迭代器前,必须对迭代器重新定位。所以语句“pD=obD.begin();”在此十分重要,将pD指向最前端元素,则语句“obD.insert(pD,sz,sz+3);”成功地将7、8和9插入vector的头部。

    5.erase删除操作

    除了使用pop_front(void)和pop_back(void)外,序列式容器可以通过erase成员函数抹除容器中指定位置处的元素,erase函数有如下两种重载形式。

    ❑iterator erase(iterator p);

    抹除迭代器p指定的元素,返回指向所删除元素的下一个元素的迭代器。

    ❑iterator erase(iterator first,iterator last);

    抹除[first,last)之间的所有元素,返回指向被删除的一片元素的下一个元素的迭代器。

    以list容器为例,体会元素的删除操作,如代码13.6所示。

    代码13.6 序列式容器通用的两种erase操作EraseMethods


    <———————————-文件名:example1306.cpp———————————————> 01 #include<iostream> 02 #include<list> 03 using namespace std; 04 void disp(list<int>&x)//定义disp函数用以输出容器对象所有元素 05 { 06 list<int>:iterator it=x. begin(); 07 for(;it!=x. end();it++)//list不支持下标随机访问 08 { 09 cout<<(*it)<<""; 10 } 11 } 12 int main() 13 { 14 int sz[9]={1,2,3,4,5,6,7,8,9}; 15 list<int>obL(sz,sz+9);//使用两个迭代器(指针)创建list<int>对象 16 disp(obL); 17 cout<<endl; 18 list<int>:iterator iter=obL. begin();//创建迭代器iter,指向最前端元素 19 iter++;//指向第2个元素 20 iter=obL. erase(iter);//抹掉第2个元素,iter指向第3个元素 21 disp(obL); 22 cout<<endl; 23 obL. erase(iter,obL.end());//将第3个元素直到最后1个元素都抹掉 24 disp(obL); 25 cout<<endl; 26 return 0; 27 }

    输出结果如下所示。


    1 2 3 4 5 6 7 8 9 1 3 4 5 6 7 8 9 1

    【代码解析】代码中在创建list<int>型迭代器时用“obL.begin()”为其初始化,并用“iter++”使其指向list<int>容器对象obL的第2个元素2,代码第20行语句“iter=obL.erase(iter);”将iter指向的第2个元素从obL中抹去,并返回指向第3个元素的迭代器,赋值给iter,因此,代码第23行语句“obL.erase(iter,obL.end());”将从iter指向的元素到最后1个元素全部从obL中抹去,obL中只剩下了第1个元素1。

    因为vector和deque都支持下标运算符随机访问,所以使用诸如“容器对象名.erase(迭代器p,迭代器p+2);”是合法的,但list不能这样用,换言之,在代码13.6中如果使用语句“obL.erase(iter,iter+3);”,编译器会指出错误。

    6.clear操作

    成员函数clear用于将容器对象清空,clear成员函数只有一个版本,如下所示。


    void clear(void);

    除了返回值外,上述代码等价于“erase(begin(),end())”。