c++ STL编程:vector容器

http://blog.youkuaiyun.com/pipisorry/article/details/39231949

vector动态数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间的目的。
用法:
#include<vector>

using namespace std;
变量声明:
      声明一个int向量以替代一维的数组:vector <int> a;(等于声明了一个int数组a[],大小没有指定,可以动态的向里面添加删除)。

      用vector代替二维数组。其实只要声明一个一维数组向量即可,而一个数组的名字其实代表的是它的首地址,所以只要声明一个地址的向量即可,即:vector <int *> a.

        同理想用向量代替三维数组也是一样,vector <int**>a;再往上面依此类推。

vector使用

初始化

初始化空vector对象:

        vector<string> list1;    //默认初始化,vector为空, size为0,表明容器中没有元素,而且 capacity 也返回 0,意味着还没有分配内存空间。

        vector<string> list1 = {};

        Note: 使用 std::vector vec = {} 实际上是触发了初始化列表的初始化,也就是将一个空的初始化列表{}分配给vec,vec会被初始化为空的vector。当使用 std::vector vec 这种方式时,实际上是调用了std::vector的默认构造函数,也就是创建一个空的vector对象。所以两者虽然都会得到一个空的vector对象,但是实际上初始化的方式是不一样的。[chatgpt]

        临时变量可写成:std::vector<std::string> {},或者直接{}

初始化非空vector对象:

vector<int> vect{ 10, 20, 30 }; 

vector<string> list1 = vector<string>({"0", "1","2", "3","4", "5"});

vector<int> ilist4(7); //默认值初始化,包含7个元素,缺省值对于int赋值为0,即初始化为7个0

vector<int> ilist5(7,3);    //指定值初始化,ilist5被初始化为包含7个值为3的int


vector<int> vect2(vect.begin(), vect.end()); 

vector<string> list2(list1); // 类型必须相同
vector<string> list3(list1.begin()+2,list1.end()-1); //初始化为两个迭代器指定范围中元素的拷贝,这里是{"2", "3","4"}

[vector 的六种 创建和初始化方法_c++ vector 初始化-优快云博客]

[Initialize a vector in C++ (7 different ways) - GeeksforGeeks]

赋值

vector<string> list2  = list1;   // 效果等价于vector<string> list2(list1); 其中list1不能是list<string>,否则出错error: no viable overloaded '='

查找

vector中是否存在特定元素(find会在查找到指定值后立即返回,所以它一般比count更快(因为count总是要遍历整个容器))

if (std::find(v.begin(), v.end(), key) != v.end())

[c++判断vector中是否存在特定元素的方法_c++ vector count-优快云博客]

遍历输出

for (auto i : vec)
  std::cout << i << ' ';

[C++ 打印 vector 的几种方法 - 简书]

最大值或最小值

最大值:int maxValue = *max_element(v.begin(),v.end()); 

最小值:int minValue = *min_element(v.begin(),v.end());

取最大元素或最小值所有索引位置

max_element(vec.begin(),vec.end()) - vec.begin()

minPosition = min_element(v.begin(),v.end()) - v.begin()

[C++中找数组最大值和对应的下标]

常用函数

1.push_back   在数组的最后添加一个数据

示例1:一维数组

vector <int > a;

int  b = 5;

a.push_back(b);

cout<<a[0];       //输出结果为5

此时若对b另外赋值时不会影响a[0]的值

示例2:二维数组

vector <int*> a;
int *b;
b= new int[4];
b[0]=0;
b[1]=1;
b[2]=2;
a.push_back(b);
delete b; //释放b的地址空间
for(int i=0 ; i <3 ; i++){
cout<<a[0][i]<<endl;
}

此时输出的值并不是一开始b数组初始化的值,而是一些无法预计的值.

分析:把一个地址(指针)压入向量a,即a[0]=b,因此释放了b的地址也就释放了a[0]的地址,因此a[0]数组中存放的数值也就不得而知了。

2.pop_back    去掉数组的最后一个数据 

示例:c.pop_back()     删除最后一个数据
3.at                得到编号位置的数据:如果idx越界,抛出out_of_range

c.at(idx)
4.begin           得到数组头的指针

示例:c.begin()           返回指向容器第一个元素的迭代器
5.end             得到数组的最后一个单元+1的指针

示例:c.end()             返回指向容器最后一个元素的迭代器
6.front        得到数组头的引用

示例:c.front()         传回第一个数据
7.back            得到数组的最后一个单元的引用

c.back():传回最后一个数据,不检查这个数据是否存在。
8.max_size     得到vector最大可以是多大
9.capacity       当前vector分配的大小:
容器在它已经分配的内存中可以容纳多少元素。那是容器在那块内存中总共可以容纳多少元素,而不是还可以容纳多少元素。如果你想知道一个vector或string中有多少没有被占用的内存,你必须从capacity()中减去size()。
10.size           当前使用数据的大小:size()告诉你容器中有多少元素。它没有告诉你容器为它容纳的元素分配了多少内存。 

示例:c.size()         回容器中实际数据的个数
11.resize    调整容器的大小,使其包含n个元素。

如果n小于当前容器的大小,则将内容减少到其前n个元素,并删除超出范围的元素(并销毁它们)。
如果n大于当前容器的大小,则通过在末尾插入所需数量的元素来扩展内容,以达到n的大小。如果指定了val,则将新元素初始化为val的副本,否则将对它们进行值初始化。
如果n也大于当前容器容量,将自动重新分配已分配的存储空间。
请注意,此函数通过插入或擦除容器中的元素来更改容器的实际内容。

示例:c.resize(num)     重新设置该容器的大小

[C++ std::vector::resize() 方法解析(菜鸟看了秒懂)-腾讯云开发者社区-腾讯云]

12.reserve      改变当前vecotr所分配空间的大小
13.erase         删除指针指向的数据项

示例:c.erase(pos)        删除pos位置的数据

c.erase(beg,end) 删除[beg,end)区间的数据
14.clear          清空当前的vector
15.rbegin        将vector反转后的开始指针返回(其实就是原来的end-1)

c.rbegin():传回一个逆向队列的第一个数据
16.rend          将vector反转构的结束指针返回(其实就是原来的begin-1)

c.rend():传回一个逆向队列的最后一个数据的下一个位置
17.empty        判断vector是否为空
18.swap         与另一个vector交换数据

19.insert

c.insert(pos,elem)  在pos位置插入一个elem拷贝

20.assign

c.assign(beg,end):将[beg; end)区间中的数据赋值给c。
c.assign(n,elem):将n个elem的拷贝赋值给c。 
21. get_allocator:使用构造函数返回一个拷贝。 
22. c.~ vector <Elem>():销毁所有数据,释放内存。
  

示例:取参数

template <typename T>
std::vector<T> get_vector_conf_with_entranceid(
    const SortRequestContext& ctx, const std::string& key1,
    const std::string& key2, std::vector<T> default_value) {
  std::vector<T> value = default_value;

  if (ctx.params->has({key1, fmt::to_string(ctx.entranceid), key2})) {
    value = ctx.params->get_array<T>({key1, fmt::to_string(ctx.entranceid), key2});
  } else if (ctx.params->has({key1, key2})) {
    value = ctx.params->get_array<T>({key1, key2});
  }

  return value;
}

std::vector <std::string> boost_vec = get_vector_conf_with_entranceid<std::string>(ctx, "sort", "boost_vec", {});

皮皮blog

vector内存管理与效率

      1》使用reserve()函数提前设定容量大小,避免多次容量扩充操作导致效率低下。

        关于STL容器,最令人称赞的特性之一就是是只要不超过它们的最大大小,它们就可以自动增长到足以容纳你放进去的数据。(要知道这个最大值,只要调用名叫max_size的成员函数。)对于vector和string,如果需要更多空间,就以类似realloc的思想来增长大小。vector容器支持随机访问,因此为了提高效率,它内部使用动态数组的方式实现的。在通过 reserve() 来申请特定大小的时候总是按指数边界来增大其内部缓冲区。当进行insert或push_back等增加元素的操作时,如果此时动态数组的内存不够用,就要动态的重新分配当前大小的1.5~2倍的新内存区,再把原数组的内容复制过去。所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降。正如上面的代码告诉你的那样。而进行pop_back操作时,capacity并不会因为vector容器里的元素减少而有所下降,还会维持操作之前的大小。对于vector容器来说,如果有大量的数据需要进行push_back,应当使用reserve()函数提前设定其容量大小,否则会出现许多次容量扩充操作,导致效率低下。

      reserve成员函数允许你最小化必须进行的重新分配的次数,因而可以避免真分配的开销和迭代器/指针/引用失效。但在我解释reserve为什么可以那么做之前,让我简要介绍有时候令人困惑的四个相关成员函数。在标准容器中,只有vector和string提供了所有这些函数。
(2) capacity()告诉你容器在它已经分配的内存中可以容纳多少元素。那是容器在那块内存中总共可以容纳多少元素,而不是还可以容纳多少元素。如果你想知道一个vector或string中有多少没有被占用的内存,你必须从capacity()中减去size()。如果size和capacity返回同样的值,容器中就没有剩余空间了,而下一次插入(通过insert或push_back等)会引发上面的重新分配步骤。
(3) resize(Container::size_type n)强制把容器改为容纳n个元素。调用resize之后,size将会返回n。如果n小于当前大小,容器尾部的元素会被销毁。如果n大于当前大小,新默认构造的元素会添加到容器尾部。如果n大于当前容量,在元素加入之前会发生重新分配。
(4) reserve(Container::size_type n)强制容器把它的容量改为至少n,提供的n不小于当前大小。这一般强迫进行一次重新分配,因为容量需要增加。(如果n小于当前容量,vector忽略它,这个调用什么都不做,string可能把它的容量减少为size()和n中大的数,但string的大小没有改变。在我的经验中,使用reserve来从一个string中修整多余容量一般不如使用“交换技巧”,那是条款17的主题。)

示例

假定你想建立一个容纳1-1000值的vector<int>。没有使用reserve,你可以像这样来做:

vector<int> v;
for (int i = 1; i <= 1000; ++i) v.push_back(i);
在大多数STL实现中,这段代码在循环过程中将会导致2到10次重新分配。

把代码改为使用reserve:

vector<int> v;
v.reserve(1000);
for (int i = 1; i <= 1000; ++i) v.push_back(i);
这在循环中不会发生重新分配。

在大小和容量之间的关系让我们可以预言什么时候插入将引起vector或string执行重新分配,而且,可以预言什么时候插入会使指向容器中的迭代器、指针和引用失效。例如,给出这段代码,

string s;
...
if (s.size() < s.capacity()) {
s.push_back('x');
}
push_back的调用不会使指向这个string中的迭代器、指针或引用失效,因为string的容量保证大于它的大小。如果不是执行push_back,代码在string的任意位置进行一个insert,我们仍然可以保证在插入期间没有发生重新分配,但是,与伴随string插入时迭代器失效的一般规则一致,所有从插入位置到string结尾的迭代器/指针/引用将失效。

回到本条款的主旨,通常有两情况使用reserve来避免不必要的重新分配。

第一个可用的情况是当你确切或者大约知道有多少元素将最后出现在容器中。那样的话,就像上面的vector代码,你只是提前reserve适当数量的空间。

第二种情况是保留你可能需要的最大的空间,然后,一旦你添加完全部数据,修整掉任何多余的容量。

示例2:使用“交换技巧”来修整vector过剩空间/内存

      有一种方法来把它从曾经最大的容量减少到它现在需要的容量。这样减少容量的方法常常被称为“收缩到合适(shrink to fit)”。该方法只需一条语句:vector<int>(ivec).swap(ivec);
表达式vector<int>(ivec)建立一个临时vector,它是ivec的一份拷贝,但是,vector的拷贝构造函数只分配拷贝的元素需要的内存,所以这个临时vector没有多余的容量。然后我们让临时vector和ivec交换数据,这时ivec只有临时变量的修整过的容量,而这个临时变量则持有了曾经在ivec中的没用到的过剩容量。在这个语句结尾,临时vector被销毁,释放了以前ivec使用的内存,收缩到合适。

示例3:用swap方法强行释放Vector所占内存

template < class T> void ClearVector( vector<T>& v ){ 
    vector<T>vtTemp;
    vtTemp.swap( v );

    vector<int> v ;
    nums.push_back(1);
    nums.push_back(2);
    vector<int>().swap(v);

/* 或者v.swap(vector<int>()); */

/*或者{ std::vector<int> tmp = v;   v.swap(tmp);   }; //加大括号{ }是让tmp退出{ }时自动析构*/

示例4:Vector 内存管理成员函数的行为测试

vector用实例代码测试来了解其内存管理方式:

#include <iostream>
#include <vector>
using namespace std;

int main(){
vector<int> iVec;
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //1个元素, 容器容量为1

iVec.push_back(1);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //2个元素, 容器容量为2

iVec.push_back(2);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //3个元素, 容器容量为4

iVec.push_back(3);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //4个元素, 容器容量为4

iVec.push_back(4);
iVec.push_back(5);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //5个元素, 容器容量为8

iVec.push_back(6);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //6个元素, 容器容量为8

iVec.push_back(7);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //7个元素, 容器容量为8

iVec.push_back(8);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //8个元素, 容器容量为8

iVec.push_back(9);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //9个元素, 容器容量为16
/* vs2005/8 容量增长不是翻倍的,如 9个元素   容量9 10个元素 容量13 */

/*测试effective stl中的特殊的交换 swap()*/

cout << "当前vector 的大小为: " << iVec.size() << endl;
cout << "当前vector 的容量为: " << iVec.capacity() << endl;
vector<int>(iVec).swap(iVec);

cout << "临时的vector<int>对象 的大小为: " << (vector<int>(iVec)).size() << endl;
cout << "临时的vector<int>对象 的容量为: " << (vector<int>(iVec)).capacity() << endl;
cout << "交换后,当前vector 的大小为: " << iVec.size() << endl;
cout << "交换后,当前vector 的容量为: " << iVec.capacity() << endl;

return 0;}

复杂度分析

vector 插入时间复杂度

将N个元素使用push_back插入到vector中, 求push_back操作的复杂度?

考虑vector每次内存扩充两倍的情况。如果我们插入N个元素, 则会引发lgN次的内存扩充,而每次扩充引起的元素拷贝次数为2^0,  2^1, 2^2, ..., 2^lgN. 把所有的拷贝次数相加得到2^0 + 2^1 + 2^2 + ... + 2^lgN = 2 * 2^lgN - 1 约为 2N次,共拷贝了N次最后一个元素, 所以总的操作大概为3N。所以, 每个push_back操作分摊3次, 是O(1) 的复杂度。

即插入数据个数和拷贝次数为

1 2 3 4 5 6 7 8 9 ... 15 16 17 ... 31 32 33 ... 63 64 65 ...

0 1 1 2 2        4 8 ...  8   8   16...  16 16 32 ... 32 32 33 ....

即总共拷贝了2^0,  2^1, 2^2, ...,2^([logN]-1) = 2^[logN]-1次(这里[]代表向上取整,注意向上取整-1 != 向下取整)。

[map vector 插入时间复杂度 ]

from: -柚子皮-

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值