STL中vector值得注意的两个问题

转载 2012年03月28日 23:21:26

一、 相关背景:

1.      STL有非常重要的两块内容,一个是容器,另外一个是算法。

2.     容器有顺序容器、关联容器和顺序容器适配器之分。算法独立于容器存在,但可以和容器紧密结合使用,从而发挥出

相当大的威力。

3.         顺序容器包括:vectorlistdeque

关联容器包括:mapmultimapsetmultiset

顺序容器适配器包括:stackqueuepriority_queue

 

尽管从名字上看起来很像容器,但bitset其实并不是容器,它只是一种数据类型。

 

stack可以由vectorlist或者deque作为底层容器。缺省地,stack的底层容器是deque

queue可以由list或者deque作为底层容器;缺省地,queue的底层容器deque

priority_queue可以由vectordeque作为底层容器。缺省地,priority_queue的底层容器vector

 

二、vector对象超出其作用域时的情况

vector支持随机访问,因而要取得其中的某个元素的效率是非常高的。除最后一个元素外,要删除其中一个元素,由于涉及到内存迁移,平均效率相对list而言要低很多。

 

下面要谈到的问题,效率并不在考虑范围之内。

vector::erase的原型如下:

iterator erase(iterator position);

iterator erase(iterator first, iterator last);

对应的相关说明如下:

Parameters:

position

Iterator pointing to a single element to be removed from the vector.

first, last

Iterators specifying a range within the vector to be removed: [first,last). i.e., the range includes all the elements between first and last, including the element pointed by first but not the one pointed by last.

 

Removes from the vector container either a single element (position) or a range of elements ([first,last)).

 

This effectively reduces the vector size by the number of elements removed, calling each element's destructor before.

 

Because vectors keep an array format, erasing on positions other than the vector end also moves all the elements after the segment erased to their new positions, which may not be a method as efficient as erasing in other kinds of sequence containers (deque, list).

 

This invalidates all iterator and references to elements after position or first.

上面的说明中,有下划线的那句的含义是:

这实际上就是减少容器的大小,减少的数目就是被删除元素的数目,在删除元素之前,将会调用被删元素的析构函数

 

下面我们来看看代码,是不是真的如此。除非特别说明,所有结果都是VC2005中的情况。

 

 

 

注:下面的代码都在同一个cpp文件中,并有如下语句:

#include <iostream>

#include <algorithm>

#include <string>

#include <vector>

using namespace std;

 

先定义一个类:

class Student

{

public:

         Student(const string name = "Andrew"const int age = 7) : name(name), age(age)

         {

}

 

         ~Student()

         {

                   cout << name << "\tdeleted." << endl;

         }

 

         const string get_name() const

         {

                   return name;

         }

 

         const int get_age() const

         {

                   return age;

         }

 

private:

         string name;

         int age;

};

这个类很简单,将作为vector中的元素类型。它包含了两个私有成员变量nameage,公有的构造函数、析构函数以及分别读取两个成员变量的成员函数get_nameget_age

 

int main(void)

{

         {

                   vector<Student> svec;

                   Student stu01;

                   Student stu02("Bob", 6);

                   Student stu03("Chris", 5);

 

                   svec.push_back(stu01);

                   svec.push_back(stu02);

                   svec.push_back(stu03);

         } // svec的作用域到此结束

 

         cout << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

 

上面程序运行结果:

Andrew     deleted.

Andrew     deleted.

Bob           deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

Chris         deleted.

Bob           deleted.

Andrew     deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

svec作用域结束前,每个元素对象的析构函数被调用了几次(STL中所有容器分配内存都是使用allocator的机制,如果在代码中显式调用了析构函数,则本想本身是被销毁了,但该对象所占用的内存,并不会交还给系统。可以参考相关资料进行深入研究。在G++中,上述析构函数被调用的次数要比VC2005中少一些,可见不同的编译器,在这方面的实现细节也略有不同。后面玄机逸士会专门撰文对此进行探讨)

 

不管怎么说,vector中的元素对象的确是被销毁了。

 

如果将main函数改成如下的样子:

int main(void)

{

         {

                   vector<Student*> svec;

                   svec.push_back(new Student);

                   svec.push_back(new Student("Bob", 6));

                   svec.push_back(new Student("Chris", 5));

         } // svec的作用域到此结束

         cout << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

这次显然没有调用元素对象的析构函数。也就是说这些元素并没有被销毁。在G++中的情况也是如此。

 

这就说明:如果vector中的元素是对象指针,则指针所指向的对象在vector的作用域结束时,是不会被自行销毁的。

 

三、vector中的erase

情形1 如果将前面的main函数改成:

int main(void)

{

         {

                   vector<Student> svec;

                   Student stu01;

                   Student stu02("Bob", 6);

                   Student stu03("Chris", 5);

 

                   svec.push_back(stu01);

                   svec.push_back(stu02);

                   svec.push_back(stu03);

 

                   svec.erase(svec.begin(), svec.end());

                   cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         } // svec的作用域到此结束

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果为:

Andrew     deleted.

Andrew     deleted.

Bob           deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Chris         deleted.

Bob           deleted.

Andrew     deleted.

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 

很明显1之上的析构函数调用时由erase语句产生的,12之间的语句是svec作用域结束时产生的。因此,This effectively reduces the vector size by the number of elements removed, calling each element's destructor before.在这里是成立的。

g++中的情况与此类似。

 

情形2:如果将main函数改写成:

int main(void)

{

         {

                   vector<Student*> svec;

                   svec.push_back(new Student);

                   svec.push_back(new Student("Bob", 6));

                   svec.push_back(new Student("Chris", 5));

                   svec.erase(svec.begin(), svec.end());

                   cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         }

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果为:

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 

可见析构函数并没有得到调用,由此可知:

如果vector中的元素是对象指针,则指针所指向的对象在调用完erase时,是不会被自行销毁的,即This effectively reduces the vector size by the number of elements removed, calling each element's destructor before.在这种情况下是不成立的。g++中的情况与此类似。

 

情形3:如果将main函数改写成:

int main(void)

{

         {

                   vector<Student> svec;

                   Student stu01;

                   Student stu02("Bob", 6);

                   Student stu03("Chris", 5);

 

                   svec.push_back(stu01);

                   svec.push_back(stu02);

                   svec.push_back(stu03);

 

// (1)

                   //svec.erase(svec.begin(), svec.end());     

 

                  // (2)

                   for(vector<Student>::iterator iter = svec.begin(); iter != svec.end(); ) 

                   {

                            iter = svec.erase(iter);       // earse函数返回的是,指向被删元素后面那个元素的迭代器

                            //iter--;                                   // 因为上面语句删除了第一个元素stu01,现在iter指向了stu01

                   }                                                       // 后面的元素stu02,由于stu01已经不存在,stu02便是第一个

                                                        // 元素了,亦即此时iter == svec.begin()true的,因此其

// 后的iter--;语句是错误的。

 

// (3)

                   //vector<Student>::iterator iter = svec.begin();            

                   //while(iter != svec.end())

                   //{

                   //       iter = svec.erase(iter);

                   //}

 

                   // (4)

//for(vector<Student>::iterator iter = svec.begin(); iter != svec.end(); iter++)

                   //{

                   //       svec.erase(iter);

                   //       iter--;

                   //}

                   cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         }

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

(1)(2)(3)VC2005中均能够正确删除svec中的所有元素对象,(4)将会出现运行时错误,这是因为erase后会导致iter失效。(1)(2)(3)(4)g++环境中,全部正确。不过建议即便在g++环境中,也不要像(4)那样写代码。

 

情形4:如果将main函数写成:

int main(void)

{

         vector<Student*> svec;

         svec.push_back(new Student);

         svec.push_back(new Student("Bob", 6));

         svec.push_back(new Student("Chris", 5));

         svec.erase(svec.begin(), svec.end());

         cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         for(vector<Student*>::iterator iter = svec.begin(); iter != svec.end(); iter++)

         {

                   cout << (*iter)->get_name() << endl;

         }

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果为:

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

这说明,erase的确将svec中的元素删除了,但元素本身所指向的内存依然存在于内存中,因为未见到相关析构函数被调用。

 

情形5:如果将main函数写成:

int main(void)

{

    vector<Student*> svec;

    svec.push_back(new Student);

    svec.push_back(new Student("Bob", 6));

    svec.push_back(new Student("Chris", 5));

    //svec.erase(svec.begin(), svec.end());

    cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

    for(vector<Student*>::iterator iter = svec.begin(); iter != svec.end(); )

    {

              Student *temp = *iter;

        iter = svec.erase(iter);

        //iter--;

              delete temp;

    }  

   

    // 看看svec中是否还有元素?

       cout << "If there is any element in svec?" << endl;

       cout << "There is " << svec.size() << " element(s) in svec." << endl;

 

    cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

   

    return 0;

}

则输出结果为:

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Andrew     deleted.

Bob           deleted.

Chris         deleted.

If there is any element in svec?

There is 0 element(s) in svec.

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

这说明,erase的确将svec中的元素删除了,且元素本身所指向的内存也被回收。

 

四、 结论

通过上述代码及其运行结果表明:

1.     vector中,如果元素是对象的指针,当该vector超出其作用域或调用erase删除元素时,那么元素本身在该vector

    种会被删除,但对象本身并没有得到销毁。在这种情况下,销毁的工作要由程序员自己来做。

2.     erase删除vector容器中的元素对象时,元素对象的析构函数会被多次调用。

STL之Vector(二):Vector常用函数

STL之Vector(二):Vector常用函数
  • ac_hexin
  • ac_hexin
  • 2016年07月25日 17:28
  • 1589

STL之二:vector容器用法详解

vector类称作向量类,它实现了动态数组,用于元素数量变化的对象数组。像数组一样,vector类也用从0开始的下标表示元素的位置;但和数组不同的是,当vector对象创建后,数组的元素个数会随着ve...
  • longshengguoji
  • longshengguoji
  • 2013年01月15日 23:16
  • 42524

STL 算法vector/set集合-交集,并集,差集,对称差

针对这里提及的四个集合运算必须特别注意:  1、第一个算法需保证第一集合和第二集合有序,并从小到大排序,内部使用默认“ 2、第二个算法需保证第一集合和第二集合有序,排序方式参照Compare确定,内部...
  • kalilili
  • kalilili
  • 2014年12月26日 21:13
  • 5702

STL中vector值得注意的两个问题

一、 相关背景: 1.      在STL有非常重要的两块内容,一个是容器,另外一个是算法。 2.     容器有顺序容器、关联容器和顺序容器适配器之分。算法独立于容器存在,但可以和容器紧...
  • QQ276592716
  • QQ276592716
  • 2011年08月18日 14:34
  • 2088

codeigniter搭配php 5.3中两个值得注意的问题

codegniter 1.7跟PHP5。3配合时,有些地方要改下的,主要是php 5.3有不少跟php 5.2不同的地方了,要特别留意下, 1 PHP:Deprecated: Function se...
  • jackyrongvip
  • jackyrongvip
  • 2013年07月01日 16:25
  • 739

腾讯2016研发工程师笔试题(一)----两个值得注意的选择题

腾讯2016研发工程师笔试题(一)1,以下代码是否完全正确,执行可能得到的结果是__。class A{ int i; }; class B{ A *p; public: B(){p=...
  • xj178926426
  • xj178926426
  • 2017年10月20日 23:55
  • 164

U-boot的环境变量值得注意的有两个:bootcmd 和bootargs

U-boot的环境变量值得注意的有两个:bootcmd 和bootargs。 u-bootcmd     bootcmd是自动启动时默认执行的一些命令,因此可以在当前环境中定义各种不同配置,不同...
  • shan614667793
  • shan614667793
  • 2017年10月25日 16:10
  • 224

C++中STL Vector相关的两个问题

看到的一道百度笔试题:   C++中STL Vector相关 (1).push_back函数的内存分配是怎样的?   答:vector其中一个特点:内存空间只会增长,不会减小。援引C++ Pr...
  • wangwenjing90
  • wangwenjing90
  • 2012年12月12日 10:05
  • 842

医院信息化建设中值得注意的几个问题.

  • 2009年02月18日 09:11
  • 318KB
  • 下载

ASP.NET安全[开发ASP.NET MVC应用程序时值得注意的安全问题](转)

概述   安全在web领域是一个永远都不会过时的话题,今天我们就来看一看一些在开发ASP.NET MVC应用程序时一些值得我们注意的安全问题。本篇主要包括以下几个内容 : 认证授权XSS跨...
  • ful1021
  • ful1021
  • 2015年04月14日 16:07
  • 352
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:STL中vector值得注意的两个问题
举报原因:
原因补充:

(最多只允许输入30个字)