C++ STL | vector

目录

vector容器介绍

cplusplus关于vector的介绍与使用:

构造函数

c++14支持的构造函数如下

1、构造一个某类型的空容器。

[2、 构造一个含有n个val的某类型容器。](#2、 构造一个含有n个val的某类型容器。)

3、拷贝构造某类型容器的复制品。

4、使用迭代器拷贝构造某一段内容。

vector的空间相关函数

size函数与capacity函数

resize函数与reserve函数

empty函数

vector迭代器

​编辑

begin函数和end函数

rbegin函数和rend函数

vector的增删查改

find函数

push_back函数和pop_back函数

insert函数和erase函数

swap函数

vector元素访问

[]操作符访问

迭代器访问

迭代器失效问题

扩容导致的迭代器失效

erase删除指定位置导致的迭代器失效

Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

迭代器失效解决办法

vector有类似string的SSO吗?


vector容器介绍

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

  2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。

  3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。

  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

  5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

  6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。


cplusplus关于vector的介绍与使用:

cplusplus.com/reference/vector/vector/

member_types


构造函数

c++14支持的构造函数如下

这里挑几个常用的来讲

1、构造一个某类型的空容器。

cpp 复制代码
vector<int> v1; //构造int类型的空容器

2、 构造一个含有n个val的某类型容器。

cpp 复制代码
vector<int> v2(10, 2); //构造含有10个2的int类型容器

3、拷贝构造某类型容器的复制品。

cpp 复制代码
vector<int> v3(v2); //拷贝构造int类型的v2容器的复制品

4、使用迭代器拷贝构造某一段内容。

注意,该方式支持拷贝同一元素类型的其他容器

cpp 复制代码
//使用迭代器拷贝构造v2容器的某一段内容
vector<int> v4(v2.begin(), v2.end()); 

// 拷贝其他容器的某一段内容。
string s("hello world");
vector<char> v5(s.begin(), s.end()); //拷贝构造string对象的某一段内容

vector的空间相关函数

size函数与capacity函数

size函数获取当前容器中有效元素的个数,capacity函数获取当前容器的容量

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 2);
	cout << v.size() << endl; //获取当前容器中的有效元素个数
	cout << v.capacity() << endl; //获取当前容器的最大容量
	return 0;
}

resize函数与reserve函数

与string类类似,resize函数更改容器中有效元素的个数,reserve函数更改容器的最大容量

resize规则:

1、当所给值大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0。

2、当所给值小于容器当前的size时,将size缩小到该值。

reserve规则:

1、当所给值大于容器当前的capacity时,将capacity扩大到该值。

2、当所给值小于容器当前的capacity时,什么也不做。

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 2);
	cout << v.size() << endl; //10
	cout << v.capacity() << endl; //10
	v.reserve(20); //改变容器的capacity为20,size不变
	cout << v.size() << endl; //10
	cout << v.capacity() << endl; //20
	v.resize(15); //改变容器的size为15
	cout << v.size() << endl; //15
	cout << v.capacity() << endl; //20
	return 0;
}

empty函数

empty函数判断当前容器是否为空

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 2);
	cout << v.empty() << endl;  // 0,说面不为空
	return 0;
}

vector迭代器

和string类类似,首先需要知道的是,end和rend指向的位置是不可使用的,不止是string和vector,c++的全部容器都是如此,甚至大部分编程语言都如此,有关迭代器的范围都是[begin_iterator, end_iterator),左边是闭区间,右边是开区间

begin函数和end函数

begin函数会返回容器的第一个元素的正向迭代器,end函数会返回最后一个元素的下一个位置的迭代器

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 2);
	//正向迭代器遍历容器
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
    // 2 2 2 2 2 2 2 2 2 2
	cout << endl;
	return 0;
}

rbegin函数和rend函数

rbegin函数会返回容器的最后一个元素的反向迭代器,rend函数会返回第一个有效元素的前一个位置的迭代器

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 2);
	//反向迭代器遍历容器
	vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit << " ";
		rit++;
	}
    // 2 2 2 2 2 2 2 2 2 2
	cout << endl;
	return 0;
}

vector的增删查改

find函数

find不属于vector容器的成员函数,属于算法库。find支持迭代器查找,共三个参数,前两个参数确定一个迭代器区间(左闭右开),第三个参数确定所要寻找的值。

find函数在所给迭代器区间寻找第一个匹配的元素,并返回它的迭代器,若未找到,则返回所给的第二个参数。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器
	
	v.insert(pos, 10); //在2的位置插入10

	pos = find(v.begin(), v.end(), 3); //获取值为3的元素的迭代器
	
	v.erase(pos); //删除3

	return 0;
}

push_back函数和pop_back函数

push_back,即尾部插入,pop_back,即尾部删除

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	v.push_back(1); //尾插元素1
	v.push_back(2); //尾插元素2
	v.push_back(3); //尾插元素3
	v.push_back(4); //尾插元素4
    
	v.pop_back(); //尾删元素
	v.pop_back(); //尾删元素
	v.pop_back(); //尾删元素
	v.pop_back(); //尾删元素
	return 0;
}

insert函数和erase函数

insert函数可以在迭代器所指向的位置插入一个或多个元素,erase函数可以删除迭代器所指向位置的元素,或迭代器区间内的元素(区间同样遵循左闭右开)

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.insert(v.begin(), 0); //在容器开头插入0
	
	v.insert(v.begin(), 5, -1); //在容器开头插入5个-1

	v.erase(v.begin()); //删除容器中的第一个元素

	v.erase(v.begin(), v.begin() + 5); //删除在该迭代器区间内的元素(左闭右开)
	
	return 0;
}

swap函数

swap函数可以交换两个容器

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v1(10, 1);
	vector<int> v2(10, 2);

	v1.swap(v2); //交换v1,v2的数据空间

	return 0;
}

vector元素访问

[]操作符访问

vector实现了对[]操作符的重载,可以通过下标+[]的方式对容器中的元素进行访问

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 1);
	//使用"下标+[]"的方式遍历容器
	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;
	return 0;
}

迭代器访问

vector是支持迭代器的,也可以用范围for对vector容器进行遍历。(支持迭代器就支持范围for,因为在编译时编译器会自动将范围for替换为迭代器的形式)

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(10, 1);
	//范围for
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

迭代器失效问题

迭代器的主要作用就是让我们在使用各个容器时不用关心其底层的数据结构,而vector的迭代器在底层实际上就是一个指针。迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向的是一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。

扩容导致的迭代器失效

resize、reserve、insert、assign、push_back等操作引起底层空间改变的操作,可能会导致迭代器失效

cpp 复制代码
#include <iostream>
using namespace std;
#include <vector>
int main()
{
 vector<int> v{1,2,3,4,5,6};
 
 auto it = v.begin();
 
 // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
 // v.resize(100, 8);
 
 // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
 // v.reserve(100);
 
 // 插入元素期间,可能会引起扩容,而导致原空间被释放
 // v.insert(v.begin(), 0);
 // v.push_back(8);
 
 // 给vector重新赋值,可能会引起底层容量改变
 v.assign(100, 8);
 
 /*
 出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
空间,而引起代码运行时崩溃。
 解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
赋值即可。
 */
 while(it != v.end())
 {
 cout<< *it << " " ;
 ++it;
 }
 cout<<endl;
 return 0;
}

erase删除指定位置导致的迭代器失效

cpp 复制代码
#include <iostream>
using namespace std;
#include <vector>
int main()
{
 int a[] = { 1, 2, 3, 4 };
 vector<int> v(a, a + sizeof(a) / sizeof(int));
 // 使用find查找3所在位置的iterator
 vector<int>::iterator pos = find(v.begin(), v.end(), 3);
 // 删除pos位置的数据,导致pos迭代器失效。
 v.erase(pos);
 cout << *pos << endl; // 此处会导致非法访问
 return 0;
}

erase 删除 pos 位置元素后, pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是,如果pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是没有元素的,那么pos 就失效了。因此删除 vector 中任意位置上元素时, vs 就认为该位置迭代器失效了。

Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

cpp 复制代码
// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
    vector<int> v{1,2,3,4,5};
    for(size_t i = 0; i < v.size(); ++i)
    cout << v[i] << " ";
    cout << endl;
    auto it = v.begin();
    cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
    // 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效 
    v.reserve(100);
    cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
 
    // 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
    // 虽然可能运行,但是输出的结果是不对的
    while(it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

/*
程序输出:
1 2 3 4 5
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5 409 1 2 3 4 5
*/

// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{
    vector<int> v{1,2,3,4,5};
    vector<int>::iterator it = find(v.begin(), v.end(), 3);
    v.erase(it);
    cout << *it << endl;
    while(it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

程序可以正常运行,并打印:
4
4 5
 
// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
    vector<int> v{1,2,3,4,5};  // 程序可以运行示例
    // vector<int> v{1,2,3,4,5,6};  // 程序运行会崩溃示例
    auto it = v.begin();
    while(it != v.end())
    {
        if(*it % 2 == 0)
        v.erase(it);
        ++it;
    }
    for(auto e : v)
        cout << e << " ";
    cout << endl;
    return 0;
}

从上述三个例子中可以看到: SGI STL 中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it 不在 begin 和 end 范围内,肯定会崩溃的。

迭代器失效解决办法

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

一般有两种方式,一种是直接再次调用find、begin等函数获取迭代器,另一种是接收erase函数的返回值来获取迭代器

例如下面这个迭代器失效场景:

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	//v: 1 2 3 4 5
	vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器
	v.insert(pos, 10); //在值为2的元素的位置插入10
	//v: 1 10 2 3 4 5
	v.erase(pos); //理应删除元素2,结果删除元素10 ???error(迭代器失效)
	//v: 1 2 3 4 5
	return 0;
}

解决方式(再次调用find函数获取迭代器):

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	//v: 1 2 3 4 5
	vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器
	v.insert(pos, 10); //在值为2的元素的位置插入10
	//v: 1 10 2 3 4 5
	pos = find(v.begin(), v.end(), 2); //重新获取值为2的元素的迭代器
	v.erase(pos); //删除元素2
	//v: 1 10 3 4 5
	return 0;
}

再例如这个迭代器失效场景:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 6; 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函数的返回值来获取迭代器):

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 6; 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;
}

vector有类似string的SSO吗?

string有SSO,即短字符优化,是c++标准规定的,vector没有类似的优化,但是不排除编译器自身有优化行为

如果有大量的小容量vector对象的话,可以手动魔改vector,以支持类似SSO的效果

相关推荐
无限进步_5 小时前
【C语言】堆排序:从堆构建到高效排序的完整解析
c语言·开发语言·数据结构·c++·后端·算法·visual studio
雾岛听蓝5 小时前
STL 容器适配器:stack、queue 与 priority_queue
开发语言·c++
CSDN_RTKLIB5 小时前
【One Definition Rule】多编译单元定义同名全局变量
开发语言·c++
lang201509286 小时前
AQS共享锁的传播机制精髓
java·开发语言
云栖梦泽6 小时前
变量与数据类型:从“默认不可变”说起
开发语言
努力变大白6 小时前
物流路径优化系统的算法设计与实现:从理论到实践的完整探索
算法
趁月色小酌***6 小时前
Java知识点概要2
java·开发语言
superman超哥6 小时前
Rust 可变借用的独占性要求:排他访问的编译期保证
开发语言·后端·rust·rust可变借用·独占性要求·排他访问·编译期保证
fengfuyao9856 小时前
基于C#实现的支持五笔和拼音输入的输入法
开发语言·c#
黛色正浓6 小时前
leetCode-热题100-二叉树合集(JavaScript)
javascript·算法·leetcode