C++:vector的内部行为

C++中的vector是一个非常灵活的数组,它可以自动扩充大小来容纳新的元素,也可以快速地索引存储的元素,然而,这种使用上的便捷也是有代价的,因为vector的底层数据结构确实是一个数组,只是封装了一些便利的操作,像push_back()、reserve()等,下面我们就通过例子来看一下这些简便操作背后的行为,为了说明问题,我们定义了一个类,并在其构造函数中输出一些信息,类定义如下:

class A {
	static int i;
public:
	int id;

	A() {
		id = i++;
		cout<<"A: constructor"<<endl;
	}

	A(const A&) {
		id = i++;
		cout<<"A: copy constructor"<<endl;
	}

	~A() { cout<<"A: destructor"<<endl; }
};

int A::i = 0;

这个类有一个默认构造函数、一个拷贝构造函数和一个析构函数,同时我们试图给A的每个对象一个id,首先来看一下vector的最简单用法:定义一个vector:

vector<A> v1(2);
cout<<"id of the 1st element: "<<v1[0].id<<endl;
cout<<"id of the 1nd element: "<<v1[1].id<<endl;

定义v1的时候我们默认它将保存两个对象,程序的输出结果如下:

A: constructor

A: copy constructor

A: copy constructor

A: destructor

id of the 1st element: 1

id of the 2nd element: 2

A: destructor

A: destructor

可以看到,在定义v1的时候,总共调用了1次默认构造函数和2次拷贝构造函数,定义过后,v1中便已经存储了2个对象,且它们的id分别为1、2,这说明v1中的2个对象是通过拷贝构造函数得到的,那么拷贝的是谁呢?当然就是调用默认构造函数生成的那个临时对象,这个临时对象在定义完v1后就被析构了,所以在输出id之前会先打印一个析构函数被调用的消息。接下来换一种定义方式:

vector<A> v1;
v1.reserve(2);
cout<<"id of the 1st element: "<<v1[0].id<<endl;
cout<<"id of the 1nd element: "<<v1[1].id<<endl;

这里通过reserve来为v1保留两个对象的存储空间,输出结果如下:

id of the 1st element: 0

id of the 2nd element: 0

可以看到,在这种情况下,v1中不会自动构造并保存2个对象,但是我们还能侥幸地对这两个对象进行操作,尽管这两个操作是毫无意义的。接下来看看reserve()是怎么预留存储空间的:

vector<A> v1(2);
v1.reserve(3);

输出结果如下:

#1  A: constructor

#2  A: copy constructor

#3  A: copy constructor

#4  A: destructor

#5  A: copy constructor

#6  A: copy constructor

#7  A: destructor

#8  A: destructor

#9  A: destructor

#10  A: destructor

大家可以数数共调用了几次构造函数,前三次构造函数在我们的预期之中,但是随着reserve的调用,我们又调用了两次拷贝构造函数,这是因为reserve为我们弄了个更大的数组,并试图把原数组里面的东西放到新数组里,之后便会删掉原来的数组,这一点也可以从#7、#8两行的析构函数可以看出来(析构掉了原数组里的两个对象),所以改变v1的大小并不像使用起来那么简单,有可能引发大量的构造析构操作。下面再来看一个例子:

A a;
vector<A> v1;
v1.reserve(2);
v1.push_back(a);
v1.push_back(a);
v1.push_back(a);

在这个例子中,开始给v1预留了2个对象的空间,但随后插入了3个对象,可以看看输出结果是什么:

A: constructor

A: copy constructor

A: copy constructor

A: copy constructor

A: copy constructor

A: copy constructor

A: destructor

A: destructor

A: destructor

A: destructor

A: destructor

A: destructor

可以看到,共调用了5个拷贝构造函数!前两个好理解,是伴随着前两个push_back而被调用的,但是第三个push_back的时候,因为v1中的空间不够,所以重新弄了个数组,首先把原来数组里的两个对象拷贝过来,然后才将第三个对象push进新数组里,所以第三个push_back引发了3个拷贝构造函数,当然同时也额外引发了两个析构函数,所以vector虽然好用,但是某些时候可能引发大量的内存分配及构造析构操作,如果应用程序对这个敏感,就要考虑其他的容器了,像list。

时间: 2024-08-24 20:39:07

C++:vector的内部行为的相关文章

vector内部的实现1

写vector的内部方法 1 #include<vector> 2 using std::vector; 3 //写一个动态内存 4 5 class CA{ 6 int a; 7 public: 8 CA(int i){ i = a; } 9 CA(); 10 }; 11 template <class T> 12 class CStack 13 { 14 T pBuff[10]; 15 public: 16 CStack(); 17 ~CStack(); 18 void push

C++温习-标准库-vector

vector是C++中最基本的顺序容器,可把它看作是一个自动可变长度的数组来使用,使用中,比直接定义数组来使用方便,但运行效率会比数组低一些. vector的内部实现,还是数组的形式,当新插入数据使得大于容量时,就会进行重新分配空间,并移动数据,vector并不是每次插入新的数据都重新分配空间,并且移动数据,毕竟这样的效率太低了,通常,它是会新开一个大一些的空间,来避免过于频繁的分配空间和移动数据. vector 当然也是类模板. template < class T, class Alloc

谈谈vector容器的三种遍历方法

说明:本文仅供学习交流,转载请标明出处,欢迎转载! vector容器是最简单的顺序容器,其使用方法类似于数组,实际上vector的底层实现就是采用动态数组.在编写程序的过程中,常常会变量容器中的元素,那么如何遍历这些元素呢?本文给出三种遍历方法. 方法一:采用下标遍历 由于vector容器就是对一个动态数组的包装,所以在vector容器的内部,重载了[]运算符,函数原型为:reference operator [] (size_type n);所以我们可以采用类似于数组的方式来访问vector容

vector动态二维数组(容器的容器)占用内存分析

之前在这里写过一篇"C++中的动态二维数组".在C++中没有动态二维(多维)数组.但是根据原理我们可以自己创建. 在看过STL的vector源代码后"<STL源码剖析>---stl_vector.h阅读笔记"后,想到可以用容器的容器来做二维数组. 创建一个2x4的二维数组.想到的办法是:先创建一个容器的容器,外层大小的2(2行),然后里面容器小大为4(4列). int row=2,col=4; vector<vector<int> &g

关于C++的vector的实例学习

一.简介 我们在学习C++的过程中,学到STL是必然的,那么STL的入门就是vector了. vector是同一种类型的对象的集合,vector很像数组,空间是连续的,能非常高效和方便的访问单个元素,但是它支持动态增加和压缩数据,所以这是矛盾的,这个问题,我们会在后期的STL源码的分析中来讲解vector的内部实现. vector 是一个类模板(class template).使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型. vector需要的头文件是 #include <vec

(STL初步)不定长数组:vector

STL是指C++的标准模板库.(存储着一些常用的算法和容器) vector是一个不定长数组.它把一些常用的操作"封装"在vector类型内部. 例如,a是一个vector.1对元素的操作有,可以用a.size()读取它的大小,a.resize()改变它的大小,a.push_back()向尾部添加元素,a.pop_back()删除最后一个元素.2对数组的操作有:a.clear()清空,a.empty()测试是否为空. vectors是一个模板类. 它的使用声明:vetor<int&

UVA - 00101 The Blocks Problem(STL,vector)

5.2.2不定长数组vector 1.vector就是一个不定长数组.不仅如此,它把一些常用操作”封装“在vector类型内部.例如,若a是一个vector,可以用a.size()读取它的大小,a.resize()改变大小,a.push_back()向尾部添加元素,a.pop_back()删除最后一个元素,a.clear()清空,a.empty测试是否为空. 2.vector是一个模板类,所以需要用vector<int>a这样的方式来声明一个vector.vector<int>是一

实战c++中的vector系列--vector&lt;unique_ptr&lt;&gt;&gt;赋值给vector&lt;unique_ptr&lt;&gt;&gt;

之前博客讲到 vector可以使用insert方法,将一个vector copy到另一个vector的后面. 之前的博客也讲到过,如果vector容器内部放的是unique_ptr是需要进行所有权转移的. 现在就来八一八如何vector<unique_ptr<>> insert to vector<unique_ptr<>> 如果常规的vector,我们就可以这么使用insert: // inserting into a vector #include &l

vector 初始化所有方法

简介:vector可用于代替C中的数组,或者MFC中的CArray,从许多说明文档或者网上评论,一般一致认为应该多用vector,因为它的效率更高,而且具备很好的异常安全性.而且vector是STL推荐使用的默认容器,除非你知道你有特殊需要,使用vector不能满足你的需求,例如需要容器在head和tail高效的插入和删除,或者在任何位置高效的删除和插入操作,那么你可能使用deque或者list更加合适. vector是连续内存容器,换句话说,标准要求所有标准库实现的时候,vector中的元素的