C++STL容器系列(二)vector的详细用法和底层原理

一:介绍

  • vector是STL容器中的一种常用的容器,和数组类似,由于其大小(size)可变,常用于数组大小不可知的情况下来替代数组。
  • vector也是一种顺序容器,在内存中连续排列,因此可以通过下标快速访问,时间复杂度为O(1)。然而,连续排列也意味着大小固定,数据超过vector的预定值时vector将自动扩容。

二:vector的创建和方法

首先,使用vector时需包含头文件:

  • #include <vector>

创建vector

vector本质是类模板,可以存储任何类型的数据。数组在声明前需要加上数据类型,而vector则通过模板参量设定类型。

比如,声明一个int型的vector数组。

vector<int> arr1;								//一个空数组

// 这里使用了c++11的新特性 initializer_list<int> 常量数组的写法
vector<int> arr2 = {
    
    1, 2, 3, 4, 5};				//包含1、2、3、4、5五个变量
vector<int> arr3(4);							//开辟4个空间,值默认为0
vector<int> arr4(5, 3);							//5个值为3的数组

//拷贝构造
vector<int> arr5(arr4);							//将arr4的所有值复制进去,和arr4一样

//迭代器版本的拷贝构造
vector<int> arr6(arr4.begin(), arr4.end());		//将arr4的值从头开始到尾复制
vector<int> arr7(arr4.rbegin(), arr4.rend());	//将arr4的值从尾到头复制

方法

✨iterators(迭代器) \colorbox{pink}{✨iterators(迭代器)} ✨iterators(迭代器)

名字 描述
begin 返回指向容器中第一个元素的迭代器。
end 返回指向容器最后一个元素所在位置后一个位置的迭代器
rbegin 返回容器逆序的第一个元素的迭代器
rend 返回容器逆序的最后一个元素的前一个位置的迭代器
cbegin 和begin()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
cend 和end()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
crbegin 和rbegin()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
crend 和rend()功能相同,在其基础上增加了 const 属性,不能用于修改元素。

✨Capacity(容量) \colorbox{pink}{✨Capacity(容量)} ✨Capacity(容量)

名字 描述
size 返回实际元素的个数
capacity 返回总共可以容纳的元素个数
max_size 返回元素个数的最大值。这个值非常大,一般是2^32-1
empty 判断vector是否为空,为空返回true否则false
resize 改变实际元素的个数,对应于size
reserve 增加容器的容量,控制vector的预留空间, 不会初始化不能直接访问后面的元素
shrink_to_fit 减少capacity到size的大小

✨Element access(元素访问) \colorbox{pink}{✨Element access(元素访问)} ✨Element access(元素访问)

名字 描述
operator[] vector可以和数组一样用[]访问元素
at vector.at(i)等同于vector[i],访问数组下表的元素
front 返回第一个元素
back 返回最后一个元素
data 返回指向容器中第一个元素的指针

✨Modifiers(修改器) \colorbox{pink}{✨Modifiers(修改器)} ✨Modifiers(修改器)

名字 描述
push_back 在容器的尾部插入元素
pop_back 删除最后一个元素
insert 插入元素
erase 删除元素
clear 清除容器内容,size=0,存储空间不变
swap 交换两个元素的所有内容
assign 用新元素替换原有内容。
emplace 插入元素,和insert实现原理不同,速度更快
emplace_back 在容器的尾部插入元素,和push_back不同, 速度更快

三:vector的具体用法

3.1 push_back、pop_back 和 emplace_back

vector<int> arr;
for (int i = 0; i < 5; i++)
{
    
    
    arr.push_back(i);
}
for (int i = 0; i < 5; i++)
{
    
    
    arr.pop_back();
}

  • emplace_back的效果和push_back一样,都是尾部插入元素
arr.emplace(10);

两者的差别在于底层实现的机制不同:push_back将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。所以emplace_back的速度更快。

3.2 insert、emplace

注意:这里存在迭代器失效的问题
如果在某一位置例如:iterator pos = arr.begin() + 3 后续就尽量不要再使用pos这个迭代器
原因:insert可能导致扩容 原空间可能发生了变化,pos就相当于野指针了

扫描二维码关注公众号,回复: 17516506 查看本文章

insert有三种用法:

  • 在指定位置插入值为val的元素。(传入的位置都是迭代器)
//在arr的头部插入值为10的元素
vector<int> arr;
arr.insert(arr.begin(), 10);
  • 在指定位置插入n个值为val的元素
//从arr的头部开始,连续插入3个值为10的元素
vector<int> arr;
arr.insert(arr.begin(), 3, 10);
  • 在指定位置插入区间[start, end]的所有元素
//从arr的头部开始,连续插入arrs区间[begin, end]的所有元素
vector<int> arr;
vector<int> arrs = {
    
     1, 2, 3, 4, 5 };
arr.insert(arr.begin(), arrs.begin(), arrs.end());

emplace和insert同为插入元素,不过emplace只能插入一个元素:

//在arr的头部插入值为10的元素
vector<int> arr;
arr.emplace(arr.begin(), 10);
//insert和emplace的区别和上面类似,就是一个是拷贝和复制的过程,而另一个则是直接创建一个新元素。

3.3 erase

erase通过迭代器删除某个或某个范围的元素,并返回下一个元素的迭代器。
注意:这里存在迭代器失效的问题,和 insert 一样, 原空间发生了变化
---- vector的删除操作只会导致指向被删除元素及后面的迭代器失效 而list只是当前位置的迭代器失效

vector<int> arr{
    
    1, 2, 3, 4, 5};
// 删除arr开头往后偏移两个位置的元素,即arr的第三个元素,3
arr.erase(arr.begin() + 2);
// 删除arr.begin()到arr.begin()+2之间的元素,删除两个;即删除arr.begin()而不到arr.begin()+2的元素
arr.erase(arr.begin(), arr.begin() + 2);

3.4 assign

assign修改vector,和insert操作类似,不过insert是从尾部插入,而assign则将整个vector改变

---------------------------------------------------------------------------注意会影响size的大小---------------------------------------------------------------------------

  • 将整个vector修改为n个值为val的容器
//将arr修改为3个值为5的vector。
vector<int> arr = {
    
    5, 4, 3, 2, 1};
arr.assign(3, 5);
  • 将整个vector修改为某个容器[start, end]范围内的元素
//将arr修改为范围[arrs.begin, arrs.end]内的元素
vector<int> arr1 = {
    
    5, 4, 3, 2, 1};
vector<int> arr2 = {
    
     1, 2, 3, 4, 5 };
arr1.assign(arr2.begin(), arr2.end());
  • 用数组的值进行范围修改
//将arr替换为数组arrs
vector<int> arr = {
    
    5, 4, 3, 2, 1};
int arrs[5] = {
    
     1, 2, 3, 4, 5 };
arr.assign(arrs, arrs + 5);

四:vector 元素的操作(sort、reverse等)

需要用到头文件 中的算法

1、排序 sort()

使用到的函数为 sort() :按输入序列的字典序升序排序,原位操作,无返回值

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
int main(void)
{
    
    
    vector<int> a{
    
    2, 0, 2, 2, 0, 3, 0, 9};
    sort(a.begin(), a.end());  //原位操作
    // sort(a.begin(), a.end(), greater<int>) 升序操作
    for(auto i : a)
        cout << i << " ";
    return 0;
}
// 输出结果 //
0 0 0 2 2 2 3 9 

2、消除相邻的重复元素 unique()

使用到的函数为 unique() :将输入序列相邻的重复项“消除”,返回一个指向不重复值范围末尾的迭代器,一般配合 sort() 使用

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
int main(void)
{
    
    
    vector<int> a{
    
    2, 0, 2, 2, 0, 3, 0, 9};
    sort(a.begin(), a.end());  // 先排序
    for(int i:a)   cout << i << " "; // 输出
    cout << endl;
    auto end_unique = unique(a.begin(), a.end());  //将输入序列相邻的重复项“消除”,返回一个指向不重复值范围末尾的迭代器
    a.erase(end_unique, a.end()); // 删除末尾元素
    for(int i:a)   cout << i << " "; // 输出
    return 0;
}
// 运行结果 //
0 0 0 2 2 2 3 9 
0 2 3 9

3、逆序 reverse()

方法1:使用到的函数为 reverse() :将输入序列按照下标逆序排列,原位操作,无返回值

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
 
int main(void)
{
    
    
    vector<int> a{
    
    2, 0, 2, 2, 0, 3, 0, 9};
    reverse(a.begin(), a.end());  // 原位逆序排列
    for(int i:a)   cout << i << " "; // 输出
    return 0;
}
// 运行结果 //
9 0 3 0 2 2 0 2 

五:vector 中找最值

同样需要用到头文件 中的算法。
1、最大值auto it = max_element(v.begin, v,end()),返回最大值的迭代器

2、最小值 auto it = min_element(v.begin, v,end()),返回最小值的迭代器

3、相对位置大小 auto b = distance(x, y),x、y 是迭代器类型,返回 x、y 之间的距离,可以用来获取最大/小值的索引

六:vector扩容

注意:每次扩容新空间不能太大,也不能太小,太大容易造成空间浪费,太小则会导致频繁扩容而影响程序效率。

1.如何避免扩容导致效率低

  • 1.如果要避免扩容而导致程序效率过低问题,其实非常简单:如果在插入之前,可以预估vector存储元素的个数,提前将底层容量开辟好即可。如果插入之前进行reserve,只要空间给足,则插入时不会扩容,如果没有reserve,则会边插入边扩容,效率极其低下。
  • 2.以倍数的方式扩容比以等长个数的扩容方式效率高。
  • vector在push_back以成倍增长可以在均摊后达到O(1)的事件复杂度,相对于增长指定大小的O(n)时间复杂度更好。
  • 为了防止申请内存的浪费,现在使用较多的有2倍与1.5倍的增长方式,而1.5倍的增长方式可以更好的实现对内存的重复利用

总结

vector在项目或者刷题中有大量的运用,熟练掌握他们的用法是必不可少的一步, 在面试时vector的扩容问题也会经常被问到, 希望我们共同进步, 有错误还请在评论区指正!

猜你喜欢

转载自blog.csdn.net/weixin_50776420/article/details/139087742