先简单的说下序列式容器,该类容器是有序群集,其中每个元素均有固定的位置——取决于插入时间和地点,和元素值无关。换言之,就是容器中的元素位置取决于你插入该元素的时间或(和)往哪个位置插入。比如有的容器提供尾端追加元素函数,有的容器还提供向某位置插入某元素。STL 提供三个定义好的序列式容器:vector,deque,list。这里学习vector。

vector 将其元素置于一个动态数组中加以管理,与 array 非常类似,两者的唯一差别在于空间运用的灵活性,array 是静态空间,一旦配置了就不能更改,当需要容纳更多的元素时(此时已越界),需要重新手动配置更大的 array 空间,然后重新置入数据。而 vector 是动态空间,在同样的情况下,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。vector 同 array 一样可以很方便的通过索引值直接存取任何一个元素,在数组的尾端追加或移除元素均非常快速(当追加元素超出原有分配空间时,vector需要重新分配空间),但在其中间或头部插入移除元素就比较费时,需要移动安插点之后的所有元素以保持原来的相对位置。


这里通过程序介绍 vector 容器部分常见函数接口,先不涉及迭代器

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> coll, voll;

	for (int i = 0; i < 5; ++i)
	{
		coll.push_back(i);   //尾端追加元素
	}
	for (unsigned int i = 0; i < coll.size(); ++i)
		cout << coll[i] << endl;

	coll.pop_back();         //移除尾端元素
	
	cout << "assign:" << endl;
	coll.assign(10, 10);     //清空vector,插入10个值为10的元素
							 //别忘了,vector空间应用的灵活性,所以赋值个数大于之前元素个数是没问题的
	for (unsigned int i = 0; i < coll.size(); ++i)
		cout << coll[i] << endl;

	coll.at(2) = 20;         //返回指定位置的元素值的引用,做下标检查,可作左值
	cout << coll.at(2) << endl;
	
	cout << endl;
	cout << coll.size() << endl;            //容器当前实际元素数量
	cout << coll.max_size() << endl;        //容器所能容纳的最大元素数量
	cout << coll.capacity() << endl;        //容器实际能够容纳的元素数量

	cout << "swap:" << endl;
	coll.swap(voll);                        //交换两个vector中的元素,容量也互换。所以swap()可用来缩减vector容量
	for (unsigned int i = 0; i < voll.size(); ++i)
		cout << voll[i] << endl;

	cout << "clear:" << endl;
	coll.clear();                           //清空vector
	cout << coll.size() << endl;            //output:0
	cout << coll.capacity() << endl;        //output:0

	coll.reserve(30);                       //预留至少可容纳30个元素的空间
	cout << coll.capacity() << endl;

	return 0;
}
引入迭代器,迭代器是一个“可遍历STL 容器内全部或部分元素”的对象。一个迭代器用来指出容器中的一个特定位置。迭代器是个所谓的 smart pointers,具有遍历复杂数据结构的能力。运用 start,finish,end_of_storage 三个迭代器便可轻易操作vector 容器

//from STL source code v3.3 为便于理解,做了适当修改
iterator start;             //表示目前使用空间的头
iterator finish;            //表示目前使用空间的尾
iterator end_of_storage;    //表示目前可用空间的尾
所有容器类别都提供有一些成员函数,使我们得以获得迭代器并以之遍访所有元素,其中最为重要的是:

  • begin() : 返回一个迭代器,指向容器起始点,// return start;
  • end()  :返回一个迭代器,指向容器结束点。结束点在最后一个元素之后。所以容器的实际区间为 [ begin(), end() ),这只是表达一个概念,链式存储时元素就不是这样区间表示;// return finish;

int main()
{
	vector<int> coll;
	vector<int>::iterator pos, pos_1, pos_2, pos_3;      //vector迭代器由实作版本定义,或许并不是一般指针
	                                               
	for (int i = 0; i < 5; ++i)
	{
		coll.push_back(i);
	}
	for (pos = coll.begin(); pos != coll.end(); ++pos)
		cout << *pos << endl;      

	cout << "insert:" << endl;
	pos = coll.begin();
	pos_1 = coll.insert(pos, 10);        //在pos位置插入一个元素,并返回其迭代器,
                                         //运行后,*pos = 10; *pos_1 = 10; 容器元素:10 0 1 2 3 4
	cout << "erase:" << endl;
	pos_2 = coll.erase(pos_1);           //删除pos_1所指的元素,并返回下一元素迭代器
	                                     //运行后,*pos = 0, *pos_1 = 0, *pos_2 = 0
	pos_3 = coll.erase(coll.begin() + 1, coll.end() - 1);  //删除指定区间中的元素,半开半闭区间
	for (pos = coll.begin(); pos != coll.end(); ++pos)
		cout << *pos << endl;
	//易知,clear()等价于erase(begin(), end()),其实就是  

	cout << coll.front() << endl;   //返回第一个元素的引用
	cout << coll.back() << endl;    //返回最后一个元素的引用

	return 0;
}
C++支持函数重载,上面只是部分函数,具体可参考《C++标准程序库》(侯捷译)
从上可知,安插和删除元素,都会使“作用点”之后的各元素的引用,指针,迭代器“失效”,这里的“失效”是说安插或删除之后的引用、指针以及迭代器关联的元素不再是操作之前的那个元素,因为迭代器关联的是容器中的位置,而不是针对某一特定元素,另外,安插元素可能会是引用指针和迭代器真正失效(容量不够时,安插会导致vector重新分配空间,),而删除不会,关联位置不变。

需要清楚的是,引用,指针以及迭代器关联均是容器中的位置,表示引用或指向容器空间中某位置的元素。为什么强调是容器空间中呢。因为容器构造完成后,容器也就配置了对应的空间,开辟了某片内存区域(空间并不等于内存),初始化迭代器之后,那么迭代器就关联了这块区域的某个位置(你可以运算迭代器改变其关联位置,但是无论怎么运算,其迭代器的合理有效区域均在容器配置的空间内)。一旦安插元素容器容量不够时,容器便会重新配置空间(先配置新空间,再复制旧空间数据,然后销毁旧空间),这时的空间在内存中的位置便不是原来的位置了,自然而然的,迭代器关联的位置也就不是在容器内了,就失效了。


现在的C++标准程序库保证 vector 的元素必须分布于连续空间中。

运用vector 时必须确保vector 的大小足以容纳所有数据,下面这个程序有点意思

int main()
{
    vector<char> coll;
    
    for (int i = 0; i < 11; ++i)      //i<5时,程序异常出错(内存)
        coll.push_back('a');

    strcpy_s(&coll[0], 12, "hello,world");   //12
    //i<5时,coll.assign(12, 'b');不会出错

    cout << coll.size() << endl;             //output:11   当for循环中i<5时,output:5
    cout << coll.capacity() << endl;         //output:13              output:6(6 < 12)

    return 0;
}
这可能就是空间与内存的区别了吧,只有当容器的 capacity 足够容纳所有数据时,才不会抛出异常。strcpy_s 涉及到内存,并不通过容器的空间配置,而是直接拷贝数据到容器起始地址的一段内存中。

注意,千万不要把迭代器当做一个元素的地址来传递。vector 迭代器是由实作版本定义的,也许并不是个一般指针。

	printf("%p\n", coll.begin());  //ERROR (可能会工作,但是是不可移植的)
	printf("%p\n", &coll[0]);      //OK
在VS2013下运行正常,但结果不一样。后面那个才是该元素的真正地址。

其内部机制的实现参见博文详解 vector 内部机制

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐