STL01 - vector动态数组

vector存在于STL模板库中,名为动态数组,其主要是可以自主调整空间、进行一些自由的删除和添加插入等操作,还可以用algorithm算法库中的各种函数,可谓非常好用,这也是它远近闻名的原因。

STL是什么

标准模板库(Standard Template Library,STL)是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。虽说它主要出现到C++中,但在被引入C++之前该技术就已经存在了很长时间。STL的代码从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。

(很好用就是了)

接下来开始介绍vector的各种功能和用法。

头文件

头文件一般是<vector> ,一些编译器里也有<stl_vector.h><vector.h>的头文件,但第一种更为常用。在<iostream>里也有包含。


#include<vector>
#include<vector.h>
#include<stl_vector.h>
#include<iostream>

定义及初始化

vector提供了许多构造方法,可以指定长度、内容等,也可以使用默认构造。

构造函数构造法


vector<int> v1; //默认初始化,没有元素,访问会报错
vector<int> v2(10); //长度为10的数组
vector<int> v3(5,6); //长度为5,数组元素都是6
vector<int> v4(v3); //定义v4,和v3完全一样

列表构造法

C++11中提供了initializer_list类模板,可以用于列表初始化,这样构造vector的时候就不用疯狂push_back函数了。


vector<int> v { { 1,2,3,4,5,6 } };    //C++11要套两层花括号,C++14起不再要求
vector<int> v ( { 1,2,3,4,5,6 } );    //同上

vector<int> v = { 1,2,3,4,5,6 };    //等号后C++标准绝不做限制

访问元素

vector本质也是一个数组,所以和普通数组一样提供元素的常数时间访问。

下标法

vector也可以和数组一样用括号 '[ ]' 来访问下标(从零开始),但是注意,这时返回的是引用对象,可以改变元素的值,要注意数据的安全。


vector<int> v(5);
v[1] = 1;
v[2] = -1;
v[3] = 2;
v[4] = 3;
cout << v[4] << endl; //输出3

at函数

和下标一样,常数时间访问元素,但它是一个const函数,更加的安全。


vector<int> v = { 0,1,-1,2,3 };
cout << v[4] << endl; //输出3
cout << v.at(4) << endl; //也输出3

访问首尾元素

和双向队列(deque)和链表(list)一样,有front()和back()函数,获取首尾元素,常数时间复杂度。


vector<float> v(3);
v[0] = 1.89;
v[1] = 4.1234;
v[2] = 5.141598;
cout << v.front() << endl; //输出1.89
cout << v.back() << endl; //输出5.121598

尾部操作

vector在尾部存取元素的效率是所有容器中最快的。

尾部插入

在尾部插入元素,数组长度加一,不够自动扩容,返回void类型。


vector<int> v; //v:
v.push_back(1); //v:1
v.push_back(4); //v:1 4
v.push_back(90); //v:1 4 90

一般用emplace_back(),这样是直接构造,更加省时。


vector<int> v; //v:
v.emplace_back(1); //v:1
v.emplace_back(4); //v:1 4
v.emplace_back(90); //v:1 4 90

尾部删除

在尾部删除元素,数组长度减一,返回void类型。


vector<int> v; //v:
v.push_back(1); //v:1
v.push_back(4); //v:1 4
v.push_back(90); //v:1 4 90
v.pop_back(); //v:1 4
v.pop_back(); //v:1

常用操作

push_back 常用于数组的输入,这样的输入可以控制长度,使用size函数(后文介绍)。

数组输入和倒序输出


#include<vector>
#include<iostream>
vector<int> v;
int main()
{
    int n;
    cin >> n;
    for(int i=1;i<=n;i++)
    {
        int temp;
        cin >> temp;
        v.push_back(temp);
    }
    for(int i=1;i<=n;i++) //仅为演示使用,请勿模仿
    {
        cout << v.back() << ' ' ;
        v.pop_back();
    }
    return 0;
}

获取长度

使用size函数来获取数组长度,在全部使用push_back插入下返回元素的准确个数(如果指定长度会有多余的空间)。


vector<char> v; //v:
v.push_back('l'); //v:l
v.push_back('p'); //v:l p
v.push_back('c'); //v:l p c
cout << v.size() << endl; //输出3

交换

用swap函数,可以是数组,也可以是元素。


#include<algorithm> //swap函数
vector<string> v1(2),v2(3);
v1[0] = "1234";
v1[1] = "popadsf";
v2[0] = "asdgafg";
v2[1] = "9097";
swap(v1,v2); //交换两个数组
v1.swap(v2); //同上
swap(v1[0],v2[1]); //交换元素

迭代器

迭代器是STL的重要部分,可以指向对象的某一个元素,输出它的值,很多algorithm中的函数也依赖他。其与指针类似。其功能强大到可以为任何同模板的对象服务。

定义

定义模板::iterator 迭代器名;


#include<iterator> //迭代器头文件

vector<long> v;
vector<long>::iterator it; //定义迭代器,它可以为任何一个vector<long>的对象服务

首尾迭代器

begin()和end()函数返回数组的头尾指针,其可以用来表示下标或者判断是否查找到(find函数),也可以当作迭代器的范围。


v.begin();
v.end();

v.cbegin(); //begin()的常量形式,返回常量对象
v.cend(); //同上

迭代器遍历


#include <iostream>
#include <vector>
#include <iterator>
using namespace std;
vector<int> v;
int main()
{
    int n,x;
    cin >> n;
    for(int i=0;i<n;i++)
    {
        cin >> x;
        v.push_back(x);
    }
    vector<int>::iterator it; //迭代器
    for(it = v.begin(); it!=v.end(); it++)
    {
        cout << *it << ' '; //iterator本质上也是一个指针,要用*号获取值,否则将返回地址
    }
    return 0;
}

C++11中提供了auto类型和基于范围的for循环,所以也可以利用这两点来简化遍历。

auto遍历


vector<double> v;
//...
auto it = v.begin(); //auto推导为[vector<double>::iterator]
for(; it!=v.end(); it++)
{ cout<<*it<<" "; }
//...

要注意使用auto或decltype的话必须要赋初始值,这样才能进行类型推导。

范围for


#include<bits/stdc++.h>
using namespace std;
struct node{ int x,y; };
vector<node> v; //vector的类型也可以是自定义类型
int main(){
    int n;
    cin >> n;
    for(int i=1;i<=n;i++)
    {
        int a,b;
        cin >> a >> b;
        v.push_back(node{a,b});
    }
    for(auto it:v) //无须取地址,auto推导为[node]
    {
        cout<<it.x<<' '<<it.y<<endl;
    }
    return 0;
}

此处因为auto判定为node普通对象,所以可以直接用.运算符访问,但如果是迭代器的指针的话就要用->运算符了。


#include<bits/stdc++.h>
using namespace std;
struct node{ int x,y; };
vector<node> v; //vector的类型也可以是自定义类型
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int a,b;
        cin >> a >> b;
        v.push_back(node{a,b});
    }
    auto it = v.begin(); //auto推导为[*node]
    for(; it!=v.end(); it++)
    {
        cout<<it->x<<' '<<it->y<<endl; //因为是对象指针,所以要用->运算符
    }
    return 0;
}

查找元素

查找元素可以用algorithm库里的find函数,但是注意,vector没有对find函数进行虚函数重写(没有v.find()这一操作),只能用algorithm库中的原生find函数,函数原型如下(可能有出入)。


template<class T>
vector<T>::iterator find(vector<T>::iterator start,vector<T>::iterator tail,T value);

由于返回的是迭代器,所以值要用迭代器去接收(auto也可以),如果找到了,返回该元素的迭代器,否则返回v.end()的迭代器。


vector<string> v;
v.push_back("1923049");
v.push_back("YLCH");
v.push_back("中国");
vector<string>::iterator it = find(v.begin(),v.end(),"ylch");
cout<<(bool)(it!=v.end())<<endl; //输出'0'
auto it2 = find(v.begin(),v.end(),"中国");
cout<<it2<<endl; //输出'中国'

二分查找左值右值

STL库中提供了二分查找的函数,复杂度O(logn),但要求序列必须是有序的。

lower_bound

查找最左值(第一个等于查找值的位置),返回指向答案的迭代器。


vector<int> v = { 1,1,3,4,4,5 }; //v: 1 1 3 4 4 5
cout << *lower_bound(v.begin(),v.end(),4) << endl; //用*输出值

upper_bound

查找最右值(最后一个等于查找值的位置),返回指向答案的迭代器。


vector<int> v = { 1,1,3,4,4,5 }; //v: 1 1 3 4 4 5
cout << *upper_bound(v.begin(),v.end(),4) << endl; //用*输出值

其实这两个函数用于普通数组更好用一些,因为普通数组的数组指针可以隐式转换成整数,即我们可以知道查找值的下标。


int a[10] = {1,1,3,4,5,5,5,5,7,8};
cout<<lower_bound(a,a+10,5)-a<<endl; //减去头指针,输出4
cout<<upper_bound(a,a+10,5)-a<<endl; //输出7

在vector中,这个函数更多是用来二分的有序插入,因为insert第一个参数是迭代器。


vector<int> v;
int n;
cin >> n;
for(int i=1;i<=n;i++)
{
    int x;    cin >> x;
    v.insert( lower_bound(v.begin(),v.end(),x),x ); //二分有序插入
}
for(auto& it:v)    cout<<it<<' ';

定位插入

插入一般用insert函数来实现,函数原型如下。

函数原型


template<typename _InputIterator,
           typename = std::_RequireInputIter<_InputIterator> >
    iterator
    insert(const_iterator __position, _InputIterator __first,
           _InputIterator __last)
    {
      difference_type __offset = __position - cbegin();
      _M_insert_dispatch(begin() + __offset,
                 __first, __last, __false_type());
      return begin() + __offset;
    }

template<typename _InputIterator>
    void
    insert(iterator __position, _InputIterator __first,
           _InputIterator __last)
    {
      // Check whether it's an integral type.  If so, it's not an iterator.
      typedef typename std::__is_integer<_InputIterator>::__type _Integral;
      _M_insert_dispatch(__position, __first, __last, _Integral());
    }

调用的话有两种形式:

insert 1

第一个是单迭代器,用一个迭代器确定位置,插入数据,返回插入后的迭代器。


vector<int> v(10);
v.insert(v.begin()+3,56);

v.begin()+3 是插入到第三个元素的位置

insert 2

可以插入一个范围内的多个数。


vector<int> v(20);
vector<int> a(40);
//...
v.insert(v.begin()+5,a.begin(),a.begin()+3);

在v的第五个元素前插入a数组的前三个元素

定位删除

vector中的定位删除一般用erase函数实现。

函数原型


#if __cplusplus >= 201103L
      erase(const_iterator __position)
      { return _M_erase(begin() + (__position - cbegin())); }
#else
      erase(iterator __position)
      { return _M_erase(__position); }
#endif

#if __cplusplus >= 201103L
      erase(const_iterator __first, const_iterator __last)
      {
    const auto __beg = begin();
    const auto __cbeg = cbegin();
    return _M_erase(__beg + (__first - __cbeg), __beg + (__last - __cbeg));
      }
#else
      erase(iterator __first, iterator __last)
      { return _M_erase(__first, __last); }
#endif

erase 1

可以定位删除,传入迭代器。


vector<float> v(10);
v.erase(v.begin()+4);

删除v的第四个元素

erase 2

也可以范围删除。


vector<int> v(90);

v.erase(v.begin(),v.begin()+50);

删除v的前50个元素

提示:不建议频繁使用insert和erase函数,因为这样需要频繁的改变数组长度,会增加时间复杂度。

排序

排序一般用STL中的sort函数,这样不用手写排序了,复杂度

普通数组的排序


int a[50];

sort(a,a+50); //sort要传地址,默认从小到大排

bool cmp(int x,int y){
    return x>y;
}
sort(a,a+50,cmp); //加上了排序规则函数,从大到小,结构体也可以自定义函数

cmp函数

cmp函数是排序函数,形参应该与容器的类型对应(名字不一定叫cmp)。


vector<int> v;

bool cmp(int x,int y){
    return x>y;
}
sort(v.begin(),v.end());

也可以用C++11中的lambda表达式来写,更加简洁。


sort(v.begin(),v.end(),[](int x,int y) ->bool{
    return x>y;
});

C++官方还给出了一种结构体重载空运算符的方式,不过这样可能会出现遍地都是类的情况,lambda表达式就是为了解决以上情况。


struct cmp{
    bool operator()(int x,int y){
        return x > y;
    }
};

sort(v.begin(),v.end(),cmp);

数组操作

reverse翻转

用于将vector数组的元素进行反转,语法格式:


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

vector<int> v;
v.push_back(1);
v.push_back(5);
v.push_back(7);
v.push_back(90);
v.push_back(13);
v.push_back(45);

reverse(v.begin(), v.end());
for (auto it : v)
    cout << it << ' ';
//输出: 45 13 90 7 5 1

连接数组(C++20)

C++20种提供了marge函数,用来合并两个容器。

语法格式:


//以默认的升序排序作为排序规则
OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result);
//以自定义的 comp 规则作为排序规则
OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result, Compare comp);

注意的是,marge函数不但可以合并数组或容器,还可以自动排序,默认为升序,可以自定义cmp函数。


#include <iostream>     // std::cout
#include <algorithm>    // std::merge
#include <vector>       // std::vector
using namespace std;
int main() {
    //first 和 second 数组中各存有 1 个有序序列
    vector<int> first = { 5,10,15,20,25 };
    vector<int> second = { 7,17,27,37,47,57 };
    //用于存储新的有序序列
    vector<int> myvector(11);
    //将 [first,first+5) 和 [second,second+6) 合并为 1 个有序序列,并存储到 myvector 容器中。
    merge(first.begin(), first.begin() + 5, second.begin(), second.begin() + 6, myvector.begin());
    //输出 myvector 容器中存储的元素
    for (vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        cout << *it << ' ';
    }   
    return 0;
}

程序输出:


5 7 10 15 17 20 25 27 37 47 57

设置类函数

设置长度

可以用resize函数来 设置/调整 数组的长度。


vector<int> v(90);

v.resize(50); //v长度变为50

替换内容

要把数组设置成另一个数组的内容,可以用assign函数。


vector<int> b(40);
vector<int> a(b);

这是vector的构造函数替换法。


vector<int> a(89),b(67);
a.assign(b.begin(),b.end());

这里要注意,替换后数组长度也会改变,如果a长度小于b会报异常。

总结

vector 的讲解就到这里啦!相信你一定涨知识了吧,希望你把掌握的知识带到实践中去,应用到算法开发中。

非常感谢您耐心地看完了本篇文章,有错误恳请指出!


THE END 掰掰ヾ(•ω•`)o

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值