【C++初阶】vector容器

在这里插入图片描述

个人主页:@Weraphael
✍作者简介:目前学习C++和算法
✈️专栏:C++航路
希望大家多多支持,咱一起进步!
如果文章对你有帮助的话
欢迎 评论 点赞 收藏 加关注✨


目录

  • 一、什么是vector
  • 二、vector初始化
      • 2.1 默认构造函数(常见)
      • 2.2 构造函数将n个元素拷贝给本身
      • 2.3 拷贝构造函数(常见)
      • 2.4 区间拷贝
      • 2.5 数组方式
  • 三、迭代器的使用
      • 3.1 begin + end(常见)
      • 3.2 rbegin + rend
  • 四、遍历
      • 4.1 operator[]
      • 4.2 迭代器遍历
      • 4.3 范围for
  • 五、空间增长问题
      • 5.1 size
      • 5.2 empty
      • 5.3 resize
      • 5.4 reserve
      • 5.5 swap
  • 六、vector的插入与删除
      • 6.1 push_back - 尾插
      • 6.2 pop_back - 尾删
      • 6.3 insert - 插入
      • 6.4 erase - 删除pos位置的数据
      • 6.5 clear - 清空所有数据
  • 七、几个常用算法
      • 7.1 sort - 排序
      • 7.2 reverse
      • 7.3 find
  • 八、迭代器失效问题
      • 8.1 什么是迭代器失效
      • 8.2 为什么string不存在迭代器失效问题
      • 8.3 几个常见的迭代器失效样例
      • 8.4 如何解决迭代器失效问题

一、什么是vector

  • vector容器和数组非常相似,与普通数组的区别:数组是静态空间,而vector可以动态扩展
  • 动态扩展并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间
  • 使用vector容器需要包含头文件:#include
  • vector可以存储多种不同的数据类型,是因为它是一个模板容器
    【C++初阶】vector容器_第1张图片
    通过使用模板参数,我们可以在vector中指定要存储的数据类型,例如:
vector<int> vi // 整型容器
vector<char> vc // 字符型容器
vector<double> vd // 浮点型容器
vector<string> vs // string类型容器
vector<vector<int>> // 本质是二维数组
// 等等...

类模板实例化与普通类的实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,而普通类的类名就是类型。

注意:需要注意数据类型的统一性。例如,如果创建了一个存储整数的vector,就应该只向其中存储整数类型的数据,否则可能会出现类型错误或数据损坏的问题。

二、vector初始化

2.1 默认构造函数(常见)

#include 
#include 
using namespace std;
int main()
{
	// 默认构造函数
	vector<int> v;

	return 0;
}

默认构造的对象,sizecapacity都为0。

【C++初阶】vector容器_第2张图片

2.2 构造函数将n个元素拷贝给本身

【函数原型】

vector<T> v(n,elem);

【代码示例】

#include 
#include 
using namespace std;

int main()
{
	// 构造函数将n个元素拷贝给本身
	// 将3个100拷贝给本身
	vector<int> v(3, 100);

	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

2.3 拷贝构造函数(常见)

【函数原型】

vector (const vector& x);

【代码示例】

#include 
#include 
using namespace std;

int main()
{
	// 构造函数将3个100拷贝给本身
	vector<int> v1(3, 100);

	cout << "v1:";
	for (int i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << ' ';
	}
	cout << endl;

	// 拷贝构造函数
	// v2是v1的副本
	vector<int> v2(v1);

	cout << "v2:";
	for (int i = 0; i < v2.size(); i++)
	{
		cout << v2[i] << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

2.4 区间拷贝

【函数原型】

vector(v.begin(),v.end());

【代码示例】

#include 
#include 
using namespace std;

int main()
{
	// 构造函数将n个元素拷贝给本身
	// 将5个100拷贝给本身
	vector<int> v1;

	// 写入1 2 3 4 5
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);

	// 区间拷贝
	// 拷贝v1对象中的2、3、4、5
	vector<int> v2(v1.begin() + 1, v1.end());
	cout << "v2:";
	for (int i = 0; i < v2.size(); i++)
	{
		cout << v2[i] << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第3张图片

2.5 数组方式

#include 
#include 
#include 
using namespace std;

int main()
{
	vector<int> v1{ 1,2,3,4,5,6 };
	vector<char> v2{ 'h','e','l','l','o' };
	vector<string> v3{ "hello", "vector" };

	return 0;
}

【输出结果】

【C++初阶】vector容器_第4张图片

三、迭代器的使用

3.1 begin + end(常见)

大家可以认为迭代器是指针。begin指向第一个数据的位置,end指向最后一个数据的下一个位置

【C++初阶】vector容器_第5张图片

【代码示例】

#include 
#include 
using namespace std;

int main()
{
	vector<char> v;
	// 插入abcdefg
	v.push_back('a');
	v.push_back('b');
	v.push_back('c');
	v.push_back('d');
	v.push_back('e');
	v.push_back('f');
	v.push_back('g');

	vector<char>::iterator begin = v.begin();

	// begin指向第一个元素,对齐解引用就能得到
	cout << "第一个元素为:" << *begin << endl;

	vector<char>::iterator end = v.end();
	// end指向最后一个元素的下一个位置
	cout << "最后一个元素为:" << *(end - 1) << endl;

	return 0;
}

【输出结果】

在这里插入图片描述

3.2 rbegin + rend

rbeginrend是反着来的。rbegin指向的是最后一个元素,rend指向的是第一个元素的前一个位置
【C++初阶】vector容器_第6张图片

【代码示例】

#include 
#include 
using namespace std;

int main()
{
	vector<char> v;
	// 插入abcdefg
	v.push_back('a');
	v.push_back('b');
	v.push_back('c');
	v.push_back('d');
	v.push_back('e');
	v.push_back('f');
	v.push_back('g');

	// vector::iterator 如果认为太长
	// 可用auto
	auto vci = v.rbegin();
	// vci指向最后一个元素
	cout << "最后一个元素:" << *vci << endl;

	auto vcc = v.rend();
	// vcc指向第一个元素的前一个位置
	// 对其-1。就指向第一个元素
	cout << "第一个元素" << *(vcc - 1) << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第7张图片

四、遍历

4.1 operator[]

【C++初阶】vector容器_第8张图片

vector底层重载了下标访问操作符[],因此可以像数组一样变量

#include 
#include 
using namespace std;
int main()
{
	vector<char> v;
	// 插入abcdefg
	v.push_back('a');
	v.push_back('b');
	v.push_back('c');
	v.push_back('d');
	v.push_back('e');
	v.push_back('f');
	v.push_back('g');
	
	// operator[]
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第9张图片

4.2 迭代器遍历

#include 
#include 
using namespace std;
int main()
{
	vector<char> v;
	// 插入abcdefg
	v.push_back('a');
	v.push_back('b');
	v.push_back('c');
	v.push_back('d');
	v.push_back('e');
	v.push_back('f');
	v.push_back('g');
	
	// 迭代器
	vector<char>::iterator begin = v.begin();
	while (begin != v.end())
	{
		cout << *begin << ' ';
		begin++;
	}
	cout << endl;

	// 以上用for循环也是可以的
	for (vector<char>::iterator begin = v.begin(); begin != v.end(); begin++)
	{
		cout << *begin << ' ';
	}

	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第10张图片

4.3 范围for

string类时,我们讲过 范围for的底层是迭代器。

#include 
#include 
using namespace std;
int main()
{
	vector<char> v;
	// 插入abcdefg
	v.push_back('a');
	v.push_back('b');
	v.push_back('c');
	v.push_back('d');
	v.push_back('e');
	v.push_back('f');
	v.push_back('g');
	
	// 范围for
	for (auto x : v)
	{
		cout << x << ' ';
	}

	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第11张图片

五、空间增长问题

5.1 size

功能:获取数据的有效个数

#include 
#include 
using namespace std;

int main()
{
	vector<int> v{ 1,2,3,4 };
	cout << "个数为:" << v.size() << endl;

	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第12张图片

5.2 empty

功能:判断容器是否为空。如果为空返回1,否则返回0

#include 
#include 
using namespace std;

int main()
{
	// 默认构造默认有效个数size为0
	vector<int> vi;
	cout << "vi:" << vi.empty() << endl;

	vector<char> vc{ 'h', 'e', 'l', 'l','o' };
	cout << "vc:" << vc.empty() << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第13张图片

5.3 resize

【函数原型1】

resize(int num);

功能:重新指定容器的长度为num

情况1:若有效数据size > num,则会保留前num个,剩下的删除

#include 
#include 
using namespace std;

int main()
{
	vector<int> v{ 1,2,3,4,5,6 };
	cout << "改变之前的长度:" << v.size() << endl;
	
	// 重新指定容器长度
	// 保留前3个
	v.resize(3);
	
	cout << "改变后的长度:" << v.size() << endl;
	cout << "改变后的内容为:";
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << ' ';
	}
	cout << endl;

	return 0;
}

【程序结果】

【C++初阶】vector容器_第14张图片

【函数原型2】

resize(int num, elem);

情况2:若num > size,则会增加有效长度。如果不指定第二个参数,默认增加的内容是0

#include 
#include 
using namespace std;

int main()
{
	vector<int> v1{ 1,2,3,4 };
	v1.resize(6);
	// 增加了2个,不指定第二个参数默认是0
	for (auto x : v1)
	{
		cout << x << ' ';
	}
	cout << endl;

	vector<char> v2{ 'h','i' };
	v2.resize(6, 'x');
	for (auto x : v2)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第15张图片

5.4 reserve

功能:改变vector容器的容量。一般都是扩容。

#include 
#include 
using namespace std;

int main()
{
	vector<int> v;
	int capacity = v.capacity();

	for (int i = 0; i < 100; i++)
	{
		// 插入100个数据
		v.push_back(i);
		// v.capacity是输出当前容量的
		if (capacity != v.capacity())
		{
			capacity = v.capacity();
			cout << "容量改变:" << capacity << '\n';
		}
	}
	return 0;
}

【输出结果】

【C++初阶】vector容器_第16张图片

通过以上代码我们发现:vs下的容量是按1.5倍增长的g++是按2倍增长的。

然后我们再把以上代码拿到Linux环境下测试:

【C++初阶】vector容器_第17张图片

我们发现:Linux下使用的vector是按照2倍方式扩容。

因此,如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够就可以避免边插入边扩容导致效率低下的问题了。

#include 
#include 
using namespace std;

int main()
{
	vector<int> v;
	int capacity = v.capacity();

	// 提前开好大小为100的容量
	v.reserve(100);

	for (int i = 0; i < 100; i++)
	{
		// 插入100个数据
		v.push_back(i);
		// v.capacity是输出当前容量的
		if (capacity != v.capacity())
		{
			capacity = v.capacity();
			cout << "容量改变:" << capacity << '\n';
		}
	}
	return 0;
}

【输出结果】

【C++初阶】vector容器_第18张图片

或者还能这样开空间:

【C++初阶】vector容器_第19张图片

5.5 swap

【函数原型】

swap(vec); // 将vec与本身的元素互换

【代码示例】

#include 
#include 
using namespace std;

int main()
{
	vector<int> v1{1,2,3,4,5,6};
	v1.reserve(100);

	cout << "交换前:" << endl;
	cout << "v1:";
	for (auto x : v1)
	{
		cout << x << ' ';
	}
	cout << endl;

	vector<int> v2{ 7,8,9,10,11, 12 };
	cout << "v2:";
	for (auto x : v1)
	{
		cout << x << ' ';
	}
	cout << endl;

	cout << "v1的容量" << v1.capacity() << endl;
	cout << "v2的容量" << v2.capacity() << endl;

	// 交换
	v1.swap(v2);
	cout << "交换后:" << endl;

	cout << "v1:";
	for (auto x : v1)
	{
		cout << x << ' ';
	}

	cout << endl;

	cout << "v2:";
	for (auto x : v1)
	{
		cout << x << ' ';
	}
	cout << endl;

	cout << "v1的容量" << v1.capacity() << endl;
	cout << "v2的容量" << v2.capacity() << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第20张图片

通过以上发现:swap不仅可以交换容器内容,同时还达到实用的收缩内存效果

六、vector的插入与删除

6.1 push_back - 尾插

#include 
#include 
using namespace std;

int main()
{
	vector<int> v;
	// 尾插1、2、3、4
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第21张图片

6.2 pop_back - 尾删

#include 
#include 
using namespace std;

int main()
{
	vector<int> v;
	// 尾插1、2、3、4
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 删掉4
	v.pop_back();

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第22张图片

6.3 insert - 插入

【函数原型1】

iterator insert (iterator position, const value_type& val);

注意:insert是要配合迭代器使用的!

#include 
#include 
using namespace std;

int main()
{
	vector<int> v;
	// 尾插1、2、3、4
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 头插一个6
	v.insert(v.begin(), 6);

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第23张图片

【函数原型2】

void insert (iterator position, size_type n, const value_type& val);

【代码样例】

#include 
#include 
using namespace std;

int main()
{
	vector<int> v;
	// 尾插1、2、3、4
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 尾插4个6
	v.insert(v.end(), 4, 6);

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第24张图片

6.4 erase - 删除pos位置的数据

【函数原型1】

erase(const iterator pos);

注意:erase也是要配合迭代器使用的

#include 
#include 
using namespace std;

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

	// 删除最后一个数据
	v.erase(v.end() - 1);
	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第25张图片

【函数原型2】

erase(const iterator start,const iterator end)

功能:删除某个区间的数据

【代码示例】

#include 
#include 
using namespace std;

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

	// 清空数据
	v.erase(v.begin(), v.end());

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

【C++初阶】vector容器_第26张图片

除了以上方式可以清空数据,clear同样也行

6.5 clear - 清空所有数据

#include 
#include 
using namespace std;

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

	v.clear();

	for (auto x : v)
	{
		cout << x << ' ';
	}
	cout << endl;
	return 0;
}

【输出结果】

【C++初阶】vector容器_第27张图片

七、几个常用算法

注意:使用库里的算法需要加上头文件#include

7.1 sort - 排序

#include 
#include 
using namespace std;
#include

int main()
{
	vector<int> a{ 4,7,1,0,5,3, 2 };
	
	// 从小到大
	sort(a.begin(), a.end());
	for (auto x : a)
	{
		cout << x << ' ';
	}
	cout << endl;
	
	// 从大到小
	sort(a.rbegin(), a.rend());
	for (auto x : a)
	{
		cout << x << ' ';
	}
	cout << endl;
	
	return 0;
}

【输出结果】

【C++初阶】vector容器_第28张图片

7.2 reverse

#include 
#include 
using namespace std;
#include

int main()
{
	vector<int> a{ 1,2,3,4,5,6 };
	
	//逆置reverse
	reverse(a.begin(), a.end());
	for (auto x : a)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【输出结果】

【C++初阶】vector容器_第29张图片

7.3 find

注意:vector是没有提供find接口的。而我们知道vector是一个类似于数组的容器,因此如果想找一个数据,直接遍历即可。但是算法库提供了find

#include 
#include 
using namespace std;
#include

int main()
{
	vector<int> a{ 1,2,3,4,5,6 };

	// 查找4
	vector<int>::iterator pos = find(a.begin(), a.end(), 4);
	if (pos != a.end())
	{
		// 找到4就删掉
		a.erase(pos);
	}
	for (auto x : a)
	{
		cout << x << ' ';
	}
	cout << endl;
	
	return 0;
}

【输出结果】

【C++初阶】vector容器_第30张图片

八、迭代器失效问题

8.1 什么是迭代器失效

迭代器失效实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃。即如果继续使用已经失效的迭代器,程序可能会崩溃。

8.2 为什么string不存在迭代器失效问题

string是一个特殊的容器,它是由字符组成的连续序列,类似于C语言的字符串。string类会动态地管理内部存储区域,确保足够的容量容纳字符串。当我们向string中插入或删除字符时,并不会导致整个字符串被复制到新的内存位置,因此迭代器不会失效。

对比vector,它是一个动态数组,它使用连续的内存存储元素。当我们向vector中插入元素时,如果导致当前内存不足以容纳所有元素,vector会重新分配更大的内存空间,并将所有元素复制到新的内存中。这样一来,原来指向旧内存中的元素的迭代器就会失效,因为它们指向的位置已经改变了。

8.3 几个常见的迭代器失效样例

当涉及到插入或删除操作时,我们需要注意vector迭代器的失效问题

  • 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resizereserveinsertpush_back
#include 
#include 
using namespace std;

int main()
{
	vector<int> v{ 1, 2, 3 };
	
	// 1. 将有效元素个数增加到100个
	//   多出的位置使用6填充,操作期间底层会扩容
	v.resize(100, 6);
	
	// 2. reserve的作用就是改变扩容大小但不改变有效元素个数,
	// 操作期间可能会引起底层容量改变
	v.reserve(100);
	
	// 3. insert和尾插期间
	// 可能会引起扩容,而导致原空间被释放
	v.insert(v.begin(), 0);
	v.push_back(10);
	
	return 0;
}

以上操作可能会导致vector扩容,扩容就会导致旧空间被释放掉,而返回的迭代器是指向被释放的空间,如果再对迭代器进行使用,会引起代码崩溃。

  • 指定位置元素的删除操作
#include 
#include 
using namespace std;

int main()
{
	int a[] = { 1, 2, 3, 4, 5 };
	vector<int> v(a, a + sizeof(a) / sizeof(int));

	// 使用find查找4所在位置
	auto pos = find(v.begin(), v.end(), 4);
	// 删除pos位置的数据,
	v.erase(pos);
	// 预测打印5
	cout << *pos << endl;

	return 0;
}

【输出结果】

理论上删除了4*pos应该是5,可是为什么没有打印出来呢?

我们可以通过调试来观察:

【C++初阶】vector容器_第31张图片

当我再按F10,发现pos的地址变了:

【C++初阶】vector容器_第32张图片

理论上删除4后,5应该占据4的空间,然而地址却变了。因此导致了迭代器失效了。

那如果是删除最后一个数据,那么结果更加明显:

【C++初阶】vector容器_第33张图片

8.4 如何解决迭代器失效问题

在使用前,对迭代器重新赋值即可。

#include 
#include 
using namespace std;
int main()
{
	vector<int> v{ 1,2,3,4,5,6,7,8,9,10};

	// 保留1 2 3
	// 剩下全删除
	vector<int>::iterator it = v.begin() + 3;

	while (it != v.end())
	{
		// 在使用之前重新赋值
		it = v.erase(it);
	}
	
	for (auto& x : v)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

【C++初阶】vector容器_第34张图片

【输出结果】

【C++初阶】vector容器_第35张图片

你可能感兴趣的:(C++,c++,开发语言,数据库,c语言,visualstudio,学习)