C++ vector用法详解

vector 是什么

学习vector,首先得需要清晰得知道其本质是什么,是一个关键字?是一种数据类型?是一个类?

vector是C++标准模板库中的部分内容,中文偶尔译作“容器”,但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。

vector有如下特征:
1.vector是表示可变大小数组的序列容器。
2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3.本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好。

vector 是C++ STL的一个重要成员,使用它时需要包含头文件:

#include<vector>

同时声明命名空间:

using namespace std;

vector 定义

单独定义一个vector:

vector<typename> name;

上面这个定义其实相当于是一维数组name[size],只不过其size可以根据需要进行变化,这就是“变长数组”的名字的由来。

这里的typename可以是任何基本类型,例如int、double、char、结构体等,也可以是STL标准容器,例如set、queue、vector等。
需要注意的是,如果typename也是一个STL容器,定义的时候要记得在 >> 符号之间加上空格,因为一些使用C++ 11之前标准的编译器会把它视为移位操作,导致编译错误。
如果typename 是vector,就是下面这样定义:

vector<vector<int> > name;//  >>之间要加空格,前面<和里面的vector之间可加可不加空格

名为name的vector容器里,每个元素都是vector容器,这就组成了二维vector数组。
初学者可以把二维vector 数组当作两个维都可变长的二维数组理解。

另一种定义二维vector数组的方法:

vector<typename> Arrayname[size]; 

其中,Arrayname[0] ~ Arrayname[ arraySize - 1 ] 中的每一个元素都是一个vector容器。
vector<vector<int> > name 不同的是,这种写法的一维长度已经固定为arraySize,另一维才是"变长"的。

vector 基本操作

(1) vector 初始化

vector<int> vec;        //定义一个元素为int型的向量 
vector<int> vec(5);     //定义了初始大小为5个整型元素的向量(尖括号中为元素类型名,它可以是任何合法的数据类型),但没有给出初值,其值是不确定的。
vector<int> vec(10, 1); //定义初始大小为10个整型元素的向量,且给出每个元素的初值为1
vector<int> vec(tmp);   //定义vec向量,用tmp向量初始化vec向量,整体复制性赋值
vector<int> tmp(vec.begin(), vec.begin() + 3);  //用向量vec的第0个到第2个值初始化tmp向量
int arr[5] = {1, 2, 3, 4, 5};   
vector<int> vec(arr, arr + 5);      //将arr数组的元素用于初始化vec向量
//说明:当然不包括arr[4]元素,末尾指针都是指结束元素的下一个元素,
//这个主要是为了和vec.end()指针统一。
vector<int> vec(&arr[1], &arr[4]); //将arr[1]~arr[4]范围内的元素作为vec的初始值

(2) 容量

vec.size();//返回vec中元素的个数
vec.resize(10);//将a的现有元素个数调至10个,多则删,少则补,其值随机
vec.resize(10,2);//将a的现有元素个数调至10个,多则删,少则补,其值为2
vec.capacity();//返回vec在内存中总共可以容纳的元素个数
vec.empty();//当元素个数为0时返回true,否则为false

(3) 元素修改

多个元素赋值: vec.assign(); //类似于初始化时用数组进行赋值
	a.assign(b.begin(), b.begin()+3); //b为向量,将b的0~2个元素构成的向量赋给a
	a.assign(4,2); //是a只含4个元素,且每个元素为2
末尾添加元素: vec.push_back(a); //尾部插入数字a
末尾删除元素: vec.pop_back(); //删除向量的最后一个元素 无参数
任意位置插入元素: vec.insert();
	a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
	a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
	a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8,插入元素后为1,4,5,9,2,3,4,5,9,8
任意位置删除元素: vec.erase();
	vec.erase(vec.begin()+2);删除第3个元素
	vec.erase(vec.begin()+i,vec.end()-j);删除区间[i,j-1];区间从0开始
	a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+3(不包括它)
	vec.erase(vec.begin(),vec.end());删除所有元素,也可使用clear()
交换两个向量的元素: a.swap(b);//将a中的元素和b中的元素进行整体性交换
清空向量元素: vec.clear();

(4) 元素访问

下标访问: vec[0]; //并不会检查是否越界,下标是从0开始的。
at方法访问: vec.at(1); //以上两者的区别就是at会检查是否越界,是则抛出out of range异常
访问第一个元素: vec.front();
访问最后一个元素: vec.back();
返回一个指针: int* p = vec.data(); //可行的原因在于vector在内存中就是一个连续存储的数组,所以可以返回一个指针指向这个数组。这是C++11的特性。
使用迭代器访问元素:
vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
    cout<<*it<<endl;
或者:
for (size_t i = 0; i < vec.size(); i++) {
    cout << vec.at(i) << endl;
}

不同访问方式的测试例程:

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char const* argv[]) {
    int arr[5] = { 1, 2, 3, 4, 5 };
    vector<int> vecClass(arr, arr + 5);      //将arr数组的元素用于初始化vec向量
    int nSize = vecClass.size();
    //方法一(下标方式)打印vecClass 
    for (int i = 0; i < nSize; i++)
    {
        cout << vecClass[i] << " ";
    }
    cout << endl;

    //方法二(at方式)打印vecClass     
    for (int i = 0; i < nSize; i++)
    {
        cout << vecClass.at(i) << " ";
    }
    cout << endl;
    
    //方法三(遍历器方式)打印vecClass:输出某一指定的数值时不方便
    for (vector<int>::iterator it = vecClass.begin(); it != vecClass.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}

(5) 迭代器相关

迭代器( iterator ) 可以理解为一种类似指针的东西,迭代器是一种检查容器内元素并遍历元素的数据类型。其定义是:

vector<typename>::iterator it;  //可以认为迭代器就是定义一个 vector类型的指针

这样it 就是一个vector:: iterator型的变量,其中 typename 就是定义vector时填写的类型。
下面是typename为int和 double 类型的举例:

vector<int> ::iterator it;
vector<double> ::iterator it;

这样就得到了迭代器it,并且可以通过 *it 来访问vector里的元素
例如:

vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
    cout<<*it<<endl;

注意:由于vec的元素是int类型,因此定义迭代器时typename也要定为int,否则不可以it=vec.begin()将报错。这点也跟指针非常类似。

其中的begin()和end()是如下含义:

开始指针:vec.begin();
末尾指针:vec.end(); //指向最后一个元素的下一个位置
指向常量的开始指针: vec.cbegin(); //意思就是不能通过这个指针来修改所指的内容,但还是可以通过其他方式修改的,而且指针也是可以移动的。
指向常量的末尾指针: vec.cend();

vector 常用算法

注意:进行如下操作需要加头文件

#include<algorithm>

(1) 使用reverse将元素翻转:

reverse(array.begin(),array.end()); //将元素翻转,即逆序排列

注:在vector中,如果一个函数中需要两个迭代器,一般后一个都不包含.

(2) 使用sort排序:
默认升序:sort(array.begin(),array.end());
降序则:从大到小,可以在升序之后再使用翻转函数reverse()
也可以通过重写排序比较函数按照降序比较,如下:
定义排序比较函数:

bool Comp(const int &a,const int &b)
{
    return a>b;
}

调用时:sort(vec.begin(),vec.end(),Comp),这样就降序排序。
(3) 复制向量的元素:

copy(a.begin(),a.end(),b.begin()+1); 
//把a中的从a.begin()(包括它)到a.end()(不包括它)的元素复制到b中,从b.begin()+1的位置(包括它)开始复制,覆盖掉原有元素

(4) 查找元素的位置:

find(a.begin(),a.end(),10); 
//在a中的从a.begin()(包括它)到a.end()(不包括它)的元素中查找10,若存在返回其在向量中的位置

使用 vector 的注意点

使用vector需要注意以下几点:
1、如果你要表示的向量长度较长(需要为向量内部保存很多数),容易导致内存泄漏,而且效率会很低;
为何? 前面说到,当新元素插入时候,这个动态数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。如果一个向量越长,那么当新元素插入时分配一个新的数组,然后将全部元素移到这个数组这个操作将效率越低。

2、vector作为函数的参数或者返回值时,有&代表传地址,改变其值会把原来参数的数据也改变;而如果没有&传的就是值,改变的只是传进去的局部变量,对函数外面的源数据毫无影响。
例如:

double Distance(vector<int>&a, vector<int>&b) 

3、列表初始化是C++11中新定义的一种为vector对象的元素赋值,此时,用花括号括起来的0个或多个初始元素值被赋给vector对象:

vector<string> articles={“a”,“an”,“the”};

初始化的几点注意:

使用拷贝初始化时,只能提供一个初始值;

如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号初始化;

如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号中:

vector<string> articles={“a”,“an”,“the”};       //列表初始化
vector<string> articles=(“a”,“an”,“the”);       //错误 不能放在圆括号中

4、下标操作不添加元素
不能使用vector 的下标操作添加元素,下标只能用于获取已存在的元素

vector<int> ivec; // empty vector
for (vector<int>::size_type ix = 0; ix != 10; ++ix)
	ivec[ix] = ix;       //错误,不能使用下标操作添加元素

这个循环的正确写法应该是:

for (vector<int>::size_type ix = 0; ix != 10; ++ix)
	ivec.push_back(ix); // ok: adds new element with value ix

参考鸣谢:
https://blog.csdn.net/qq_46527915/article/details/115124891
https://blog.csdn.net/wkq0825/article/details/82255984
https://blog.csdn.net/weixin_44607113/article/details/123738389
https://blog.csdn.net/ayqy42602/article/details/91043725
https://www.cnblogs.com/xiaojianliu/articles/8966073.html

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾爱技术圈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值