C++STL---vector知识总结

我们在前面已经学习了string,其实string就是一个元素为字符的vector,所以在我们已经学习了string的基础上,再学习vector是很轻松的。

vector的介绍

1.vector是表示可变大小数组的序列容器

2.vector和数组一样,也是采用连续空间来存储数组,也就是说可以用下标的方式对vector的元素进行访问

3.vector的大小可以动态变化,当vector需要重新分配大小时,会开辟一个新数组,然后将所有元素移到当前这个新数组中,并释放原来的数组空间

4.vector分配空间的策略:vector会分配一些额外的空间适应可能的增长,因此存储空间一般会比实际需要的空间更大一些,所以在末尾插入元素时是在常数的时间复杂度内完成的。

5.因为vector采用的是连续空间来存取元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,尾插尾删操作相对也更加高效,而对于在中间或头部进行插入删除操作的效率就会相对较低。

vector的使用

vector的定义

vector有多种构造方式,下面我们通过代码来分别介绍:

vector<int> v;//构造一个名为v的空容器

vector<int> v1(10,2);//构造一个含有10个数值为2的int型容器

vector<int> v2(v1);//拷贝构造一个v1的int型容器

vector<int> v3(v2.begin(),v2.end());//使用迭代器拷贝构造v2容器的一部分内容

//使用迭代器可以拷贝其他容器的某一段内容
string s("hello");
vector<char> v4(s.begin(),s.end());//这里使用迭代器拷贝string对象中的某一段内容

我们来解释一下,既然vector的元素也可以是char类型,为什么还要string呢?

因为虽然他们底层都是数组中存储char,但还是有不同之处的,相较于T为char的string,string对象指向的空间结尾有'\0',这样就符合C字符串的规范,例如我们可以实现strstr,strcpy等函数,但是vector就不能实现+/-=字符串等的操作。所以vector<char>依然不能替代string。

vector的迭代器使用

begin && end

获取第一个位置的iterator && 获取最后一个数据的下一个位置的iterator

rbegin && rend

获取最后一个数据位置的reserve_iterator && 获取第一个数据的前一个位置的reserve_iterator。

还有const迭代器,使用方法完全跟string类似,这里就不详细介绍了。

我们同学可能会有疑问,例如下面的代码,为什么++rit会倒着走呢?这是因为这里的rit已经不再是原生指针了,他是一个被封装的类对象,重载operator++,才能实现++rit是反着走。

	vector<int>::reverse_iterator rit = v.rbegin();
	while(rit != v.rend())
	{
		cout << *rit << " ";
		++rit; 
	}
vector的空间增长问题

前面几个接口都是老生常谈的接口了,我们看看最后一个接口shrink_to_fit:

通过文档,我们可以知道,他的目的是减小vector对象的capacity为size,这样做可以减少空间浪费,但是最好是不在增长的vector对象,否则增容带来的效率损失很大。

vector增删查改

我们要注意因为vector底层实际是一个数组,所以头插头删的效率很低,因此没有push_front,pop_front这样在头上操作的接口,而剩下的也都是很熟悉的接口了,我们从头到尾捋一遍:

vector增删查改接口说明
push_back尾插
pop_back尾删
find查找(这个是算法模块的实现,不是vector的成员接口)
insert在pos位置插入值为val的元素
erase删除pos位置的数据
swap交换两个vector的数据空间
operator[ ]像数组一样随机访问

1.assign

assign的作用是将新的元素放入vector对象中,替换掉当前内容,并且更新该对象的size。

// vector assign
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> first;
  std::vector<int> second;
  std::vector<int> third;

  //将first内容改为7个100
  first.assign (7,100);             // 7 ints with a value of 100

  std::vector<int>::iterator it;
  it=first.begin()+1;
  //除了first的首尾元素,将剩下的5个元素赋值给second对象
  second.assign (it,first.end()-1); // the 5 central values of first

  int myints[] = {1776,7,4};
  third.assign (myints,myints+3);   // assigning from array.
  //这里是用数组赋值给third,注意这里第二个参数一定是要加三,因为迭代器是左闭右开的,
  //所以右面一定要多加一个

  std::cout << "Size of first: " << int (first.size()) << '\n';
  std::cout << "Size of second: " << int (second.size()) << '\n';
  std::cout << "Size of third: " << int (third.size()) << '\n';
  return 0;
 }

我们这里一定要注意迭代器都是左闭右开的,所以很多地方第二个参数都要多加一个。

2.find && sort

vector并没有提供find,但是算法里提供了函数模板的find函数,之所以算法里提供find的原因是不仅你vector要使用find函数,list也要使用find函数,所以这里的find提供一个模板就解决了,你可以是vector的迭代器,也可以是list的迭代器。但可以是string的迭代器吗?不可以,或者说没必要,因为string提供了自己的find函数,为什么还要使用这个呢?而且string不仅要支持find一个字符,还要find一个字符串。(使用find的时候需要包含algorithm头文件)

使用find的格式:

vector<int>::iterator pos = find(v.begin(), v.end(), 2);

这里的sort函数也是如此,是算法中的。

	int a[] = { 1, 20, 10, 8, 7, 5 };
	vector<int> v(a, a + 6);
	//升序
	sort(v.begin(), v.end());
	//降序,这里需要传一个比较器对象,这里就涉及仿函数,后面我们会详细介绍,使用它需要包functional
	/*greater<int> gt;
	sort(v.begin(), v.end(), gt);*/
	sort(v.begin(), v.end(), greater<int>());//同上,更推荐使用匿名对象

	//sort不仅可以对容器排序,还可以对数组排序,因为指向数组空间的指针是天然的迭代器
	int b[] = { 3, 4, 8, 2, 7 };
	sort(b, b + 5);
}
vector迭代器失效

迭代器的作用就是为了能够让算法不关心底层的数据结构,采取统一的方法使用,其本质就是一个指针,或是对指针进行的封装。vector的迭代器就是原生指针T*。所以迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了(其实就类似于野指针问题),使用一块被释放的空间(即使用已经失效的迭代器)就会导致程序崩溃。

下面我们来逐个分析错误:

1:

int main()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	vector<int>::iterator it = v1.begin();

	v1.reserve(5);//对v1进行扩容,v1此时指向的空间不再是原来的空间了,所以再访问it指向的内容就会报错
	while (it != v1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}

结果为:

后面的例子也都会造成这种结果

2:

int main()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
	if(pos != v1.end())
	{
		v1.insert(pos, 20);	//这里也是insert会导致增容,造成类似野指针的问题
	}

    cout<<*pos<<endl;
	return 0;
}

3.

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 8; i++)
	{
		v.push_back(i);
	}
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) //删除容器当中的全部偶数
		{
			v.erase(it);
		}
		it++;
	}
	return 0;
}

这段代码我们原意是要删除所有偶数的,但是我们仔细琢磨,就会发现其实代码会访问到不属于容器的内存空间,进而导致程序崩溃,并且在迭代器遍历容器的元素的时候,对所以奇数也都没有进行判断。
为了解决这个问题,我们可以用一个迭代器接收erase()函数的返回值,erase返回的是删除元素的下一个元素的迭代器:
 

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 8; i++)
	{
		v.push_back(i);
	}
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) //删除容器当中的全部偶数
		{
			it = v.erase(it); //删除后获取下一个元素的迭代器
		}
		else
		{
			it++; //是奇数则it++
		}
	}
	return 0;
}

我们解决迭代器失效的最好办法就是:每次使用前,对迭代器进行重新赋值。

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值