c++ STL---vector使用

本文对STL---中的vector的使用是参考网站 vector - C++ Reference 的内容,该网站是外文的哦,大家可以尝试打开看看更深刻的理解。本文是抽取其中常使用,然后加上自己的是理解哦。

前言

vector其实是顺序表类模板的封装啊! 数据结构学的不好,那简单来说就是数组的封装。

if 你想懂一个人,你先得了解它的心啊,同样的你想了解vetor,你得从它的成员变量和成员函数开始了解,这里的c++网站做了一个分类。

成员变量

我们后面模拟实现, 如下成员变量

成员函数:

构造

迭代器:

容量相关:

修改:

一.构造

这里涉及到了空间配置器的我们都忽略,本文不讲 就是上面的allocator,

1.无参构造

2. vector( size大小,val 初始化的值)

你细心可以发现这里使用的值是value_type()这是因为你可能啊写了一个vector内部嵌套了一个自定义类型,比如vector嵌套vector这就很正常啊 。

3. 用迭代器进行构造赋值

4. 拷贝构造

vector<int> v4(str.begin(), str.end());

vector<int> v5(v4);

自行可以用下面这个函数进行学习相关构造。

cpp 复制代码
void test_vector1()
{
	// 构造
	vector<int> v1;// 无参构造
	vector<int> v2(10, 0);// size_t size T& T()  一个是个数 一个是初始值 

	vector<int> v3(v2.begin(), v2.end());// 用迭代器去初始化

	string str("hello world");
	vector<int> v4(str.begin(), str.end());

	vector<int> v5(v4);
	
	// 使用下标访问内容
	for (size_t i = 0; i < v3.size(); i++)
	{
		cout << v3[i] << " ";
	}
	cout << endl;
	// 创建一个迭代器 
	//vector<int>::iterator it = v4.begin();
	auto it = v4.begin();
	while (it != v4.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v5)
	{
		cout << e << " ";
	}
	cout << endl;
}

二.迭代器

2.1 迭代器-- begin()和end()

我们这里的迭代器使用主要是正向迭代器,其实也没有很多内容需要说了,后面我们模拟实现的话,会有一些地方比如考虑到迭代器失效的原因啊。

我们主要用迭代器进行一个变量,访问得到vector中的值。

这里的vector正向的主要介绍 verctor中的 end() 返回迭代器指向的末尾元素的下一个内容,注意这里是一个内容哦而不是末尾元素。所以遍历的时候的遍历条件参考下面代码。 至于begin()当然指向的是第一个元素啦。

对于vector中,你可以认为这里的iterator底层实现其实是用的指针。这样你就会更深刻的理解了

2.2 获取迭代器 以及遍历

获取迭代器,从一个实例化中的vector中获取一个迭代器,参考如下代码,主要是注意写好类型吧;

cpp 复制代码
vector<int> v1(10,2);
vector<int>::iterator it = v1.begin();
auto it = v1.begin();// 有时候感觉变量类型有点长 可以直接用auto 
 
// 遍历 
while (it != v1.end())
{
	cout << *it << endl; // 你看你理解它是一个指针的话 那 *it访问内容
	  // 下一个元素遍历就要进行it++
	it++;


  }

三.容量

3. 1 size()

size----》返回当前容器元素个数,注意返回值是无符号整形 unsigned integral type。

3.2 max_size()

个人认为,不喜勿喷,其实max_size()没什么用,至少对我们日常使用来说,但是它的内容值得我们去了解。

max_size()主要是访问当前vector能达到理论大小的个数你看我这里写了一个v1 然后调用它的max_size() 这么大,对于它的解释这么说,因为它实际大小其实受到系统和库的约束等等,所以这个值是不一定精准的。哈哈哈哈。

为什么要实例化模板还要用对象来调用它

其实我有一个疑问的但是刚刚问了ai解决了, 我想的是一个vector的容器个数大小,那顶多跟他实例化模板的类型有关就比如vector<int> 跟

vector<double> 他们大小不一样对应的个数肯定不同了。 但是不然

不知道你怎么想,就是因为,这个max_size()的计算还受到当前容器的指针啊,以及内存啊,等等都受到约束。

3.3 resize()

  • 若 n 小于当前容器大小,则内容缩减至前 n 个元素,删除超出部分(并销毁它们)。
  • 若 n 大于当前容器大小,则通过在末尾插入所需元素扩展内容至 n 个。若指定 val,新元素初始化为 val 的副本;否则执行值初始化。
  • 若 n 同时大于当前容器容量,将自动重新分配存储空间。

注意:该函数通过插入或删除元素直接修改容器的实际内容

代码学习参考下面

我这里搞了一个小聪明,转牛角尖,但是我们深挖我们的底层跟定义,说的很清楚了,如果啊你n

<size我这个val几乎无用我只会保留前n个

cpp 复制代码
vector<int> v1;
for (int i = 0;i < 10 ;i++)
{
	v1.push_back(i*10);
}
// resize(n) 比他小 我们就 缩短到前n个
// m大于当前 size 就 插入 如果指定了 val从末尾插入n个val
// 大于容器大小了 会基于上一步 并且 重新指定内存
for (auto e : v1)
{
	cout << e << " ";
}
cout << endl;

v1.resize(2);// 变小相当于删除 
for (auto e : v1)
{
	cout << e << " ";
}
cout << endl;

v1.resize(15);// 变小相当于删除  不指定  对插入的值都初始化0 否则 初始化为val
for (auto e : v1)
{
	cout << e << " ";
}

 // 我想搞鬼 你看看这个 
/*v1.resize(2,2);
for (auto e : v1)
{
	cout << e << " ";
}*/

3.4 reserve() 与resize() 一对欢喜冤家啊

reserve 本义是保存 和更新的意思,它在这里做到底是上面目的呢,很简单啊,就是更新它的内存。它主要跟容量有关,正常情况不会跟resize一样去影响size,比容量大的话它就主要改变容量大小,然后重分配内存。

  • 若 n 大于当前向量容量,该函数会使容器重新分配存储空间,将容量增加至 n(或更大)。
  • 其他情况下,函数调用不会导致重新分配,vector容量不受影响。

此函数对vector大小无影响,也不会修改其元素。

n 小于当前 size()reserve 不会触发任何操作 ,因为条件 if (n > capacity()) 不成立。这符合标准库行为:reserve 仅在请求容量大于当前 capacity() 时重新分配内存,从不缩减容量。若。

区分reserve 和resize()

  1. resize(n) 针对 size(n) 会改变容器内size的个数甚至是大小 通过使用插入删除真正的改变其中的内容, 特别的区别就是 当n大于size的时候相当于插入啊它可以根据你补充的val去初始化值。 说白了,n>capacity 会进行插入值 且重分配内存。 n<size了 会调用easer()对容量进行删减size但是capacity不会变,保持缩减只缩小size的原则。

2.resever 它其实初衷是为了更新内存扩大内存,从来不缩减内存。

3.5 shrink_to_fit 注意这是 11以后的版本

其实它是为了改变capacity来的,还记得我们的resize和reserve吗 resize,缩小的时候不改变capcity啊 reserve只用于扩容啊。 那谁来调节容量呢 所以 11以后就搞了一个这个。

但是它是请求容器改变容量大小哈然后适应它的size。

cpp 复制代码
// vector::shrink_to_fit
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector (100);
  std::cout << "1. capacity of myvector: " << myvector.capacity() << '\n';

  myvector.resize(10);
  std::cout << "2. capacity of myvector: " << myvector.capacity() << '\n';

  myvector.shrink_to_fit();
  std::cout << "3. capacity of myvector: " << myvector.capacity() << '\n';

  return 0;
}

3.6 empty()

返回当前容器元素是不是为空 bool类型。

四. 元素获取

4.1 operator[ ]

作用,通过下标返回指定元素的引用

这里其实用起来就让你感觉跟数组一样非常方便哈 , 值得一提的是,它跟std::vector at 的功能非常类似,也是通过下标来访问内容,但是呢,at更好吧,因为它会有边界检测,并且抛异常检测,防止你运行时发生越界从而照成段错误使得访问越界的问题。

4.2 at()

使用举个例子吧。

注意: 段错误是程序运行时操作系统层面的错误,属于崩溃级问题,会直接终止程序并可能丢失数据。而边界检查抛异常(如out_of_range)是程序可控的错误处理机制,允许开发者通过try-catch捕获异常并执行恢复逻辑(如提示用户、回滚操作等),相比段错误更具鲁棒性,能让程序在异常情况下保持可控,而非直接崩溃。

用 [] 访问会报段错误,

at对应的情况

这就是抛异常的好处啊。

4.3 front()与back()

返回容器中第一个元素的引用。

翻译: 它不向begin(),它返回的是一个迭代器啊,(这里的迭代器我们底层可以认为它其实就是一个指针啊), 这个函数是返回一个之间的引用,注意得到了引用我们可以修改它对应的内容。

back()与它类似。

参考学习代码如下:

cpp 复制代码
// vector::front
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector;

  myvector.push_back(78);
  myvector.push_back(16);

  // now front equals 78, and back 16

  myvector.front() -= myvector.back();

  std::cout << "myvector.front() is now " << myvector.front() << '\n';

  return 0;
}

4.4 data()

返回一个指向内存数组的指针,这个内存数组是vector存放元素的。

为什么呢? 因为元素在vector中必然顺序存储的,所以我们可以通过指针在利用偏移量访问在数组中任意的一个元素。

五 修改

5.1 assign

1. assign:在调用之前会把之前容器中的元素全部销毁掉,并且会分配新的内容,以及根据实际的size 更改当前的容量。

  1. 当且仅当新的的vector的size 大于原来的capacity才会重新分配内存。

使用

cpp 复制代码
void myvector_modify()
{
	
	vector<int> v1(5, 1);
	// assign(n,val()) 一个是 n个 和 val值 
	v1.assign(10,99);
	for (auto e : v1)
	{
		cout << e << " ";
	}

	cout << endl;
	vector<int> v2;
	// 用迭代器去 重分配其中的内容
	v2.assign(v1.begin()+1,v1.end());

	for (auto e : v2)
	{
		cout << e << " ";
	}


}

5.2 push_back() 和 pop_back()

尾插和尾删,使用很简单的。

5.3 insert

使用方式

  1. 指定位置插入 元素 (pos,val())

  2. 指定位置开始 插入n个val元素 (pos,size(),val)

  3. 范围for插入 (pos,first,last)

大意:1.vector是通过在指定pos位置插入进行拓展的,有效的增加了容器的size。

  1. 当且仅当这个新的vector的size大于当前容器的capacity会进行内存的重分配

3.因为vector的底层是数组,插入位置,出来插入末尾元素效率很高外,插入其他位置比如中间位置的效率是很低的(同样的操作比起其他的容器比如list(链表)),因为它需要把pos后面的元素一个一个进行移动啊。

使用实例代码:

cpp 复制代码
	vector<int> v1;
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	
	vector<int> v2;
	v2.push_back(10);
	v2.push_back(9);
	v2.push_back(10);
	v2.push_back(10);
	v2.push_back(10);
	vector<int>::iterator it = v2.begin();
	
	// 指定位置插入一个值 
	v2.insert(it+2,999);
	// 指定位置插入 而且 插入了3个 val值
	v2.insert(it+2,3,999);
	for (auto e : v2)
	{
		cout << e << " ";
	}
	// 迭代器插入 
	cout << endl;

	v2.insert(v2.begin(),v1.begin()+2,v1.end());
	for (auto e : v2)
	{
		cout << e << " ";
	}
	  //从这个begin位置开始插入 也就是从头部开始 

5.4 eraser

  1. 移除指定位置的元素

  2. 通过迭代器移除一个range范围的元素

  3. 这可以有效的删除容器的指定元素,减小size 同时呢销毁指定位置的元素不仅仅是被覆盖还会调用它的析构(后期底层实现我们说一下)

  4. 再次提及效率问题,底层是数组嘛,所以删除中间位置的自然效率挺低的。

5.5 clear()

  1. 它这么说 这里的clear会把vector的全部元素移除掉,让size变成0但是capacity是不一定,以及内存的重分配也不一定发生。所以它建议用下面那个访问来代替,

写了一个匿名对象 vector<T>() 然后把他跟原本的vector互换这样,互换以后,新的内存指向给我们的vector,原本的内存被匿名对象结束后调用它的析构被销毁。 这个想法是很经典的哈 后面模拟底层实现的时候我们会提及的哦。

5.6 swap()

将当前容器的内容与另一个同类型vector对象 x 的内容进行交换,两者的大小可以不同。

调用此成员函数后,当前容器中的元素变为调用前 x 中的元素,而 x 中的元素则变为调用前当前容器中的元素。所有迭代器、引用和指针对于被交换的对象仍然有效。

注意,存在一个同名的非成员函数 swap,其对该算法进行了重载,优化后的行为与本成员函数一致。

显然这是把他们vector指向内存的指针进行了互换 参考如下代码:

cpp 复制代码
int main()
{
	std::vector<int> foo(3, 100);   // three ints with a value of 100
	std::vector<int> bar(5, 200);   // five ints with a value of 200

	foo.swap(bar);

	std::cout << "foo contains:";
	for (unsigned i = 0; i < foo.size(); i++)
		std::cout << ' ' << foo[i];
	std::cout << '\n';

	std::cout << "bar contains:";
	for (unsigned i = 0; i < bar.size(); i++)
		std::cout << ' ' << bar[i];
	std::cout << '\n';

	return 0;
}

六 关于插入 capacity的变化 以及eraser涉及到的迭代器失效问题

6.1 capacity

我这是 vs2020编译器下的情况:

这个扩容的情况 前面几个都是很小的扩容的哈,导论后面差不多一倍作用,不同的编译器下这个扩容是不一样的。gcc就是 2倍扩容的。

6.2 eraser涉及到的迭代器失效问题

这是 一个很经典的问题

删除指定元素 val 在std库中的 vector的迭代器它会帮你 对应一律用过的iterator它默认为失效了不准你再用哦。 后期我们实现我们也会发现其中更深的逻辑。

cpp 复制代码
void eraser_spevar()
{
	int key = 2;
	vector<int> v1;
	v1.push_back(2);
	v1.push_back(2);
	v1.push_back(1);
	v1.push_back(3);
	v1.push_back(3);
	v1.push_back(3);
	v1.push_back(3);
	v1.push_back(2);
	v1.push_back(123);
	v1.push_back(14);
	v1.push_back(15);
	auto it = v1.begin();
	while (it != v1.end())
	{
		if (*it == key)
			v1.erase(it);
		it++;

	}



}