标准库 vector 类型

本文深入探讨C++标准库中的Vector容器,介绍了Vector的基本操作、构造函数、动态增长机制及内部实现原理,帮助读者掌握Vector的有效使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前在看别人代码的时候,总因为vecor的一些参数产生疑问,于是,仔细看了一遍《C++Prime》第四版关于vector的介绍;结合博客:http://www.cnblogs.com/wang7/archive/2012/04/27/2474138.html
在这里做一个总结:
基本操作

(1)头文件#include.

(2)创建vector对象,vector vec;

(3)尾部插入数字:vec.push_back(a);

(4)使用下标访问元素,cout<

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

(6)插入元素: vec.insert(vec.begin()+i,a);在第i+1个元素前面插入a;

(7)删除元素: vec.erase(vec.begin()+2);删除第3个元素

vec.erase(vec.begin()+i,vec.end()+j);删除区间[i,j-1];区间从0开始

(8)向量大小:vec.size();

(9)清空:vec.clear();

算法

(1) 使用reverse将元素翻转:需要头文件#include

reverse(vec.begin(),vec.end());将元素翻转(在vector中,如果一个函数中需要两个迭代器,

一般后一个都不包含.)

(2)使用sort排序:需要头文件#include,

sort(vec.begin(),vec.end());(默认是按升序排列,即从小到大).

可以通过重写排序比较函数按照降序比较,如下:

定义排序比较函数:

bool Comp(const int &a,const int &b)
{
return a>b;
}
调用时:sort(vec.begin(),vec.end(),Comp),这样就降序排序。

vector 是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。和 string 对象一样,标准库将负责管理与存储元素相关的内存。我们把 vector 称为容器,是因为它可以包含其他对象。一个容器中的所有对象都必须是同一种类型的。
使用 vector 之前,必须包含相应的头文件。本书给出的例子,都是假设已作了相应的 using 声明:
 #include <vector>
 using std::vector;

vector 是一个类模板(class template)。使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型。因此,我们可以定义保存 string 对象的 vector,或保存 int 值的 vector,又或是保存自定义的类类型对象(如 Sales_items 对象)的 vector。
声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以 vector 为例,必须说明 vector 保存何种对象的类型,通过将类型放在类型放在类模板名称后面的尖括号中来指定类型:

  vector<int> ivec;  
  vector<Sales_item> Sales_vec;   

和其他变量定义一样,定义 vector 对象要指定类型和一个变量的列表。上面的第一个定义,类型是 vector,该类型即是含有若干 int 类型对象的 vector,变量名为 ivec。第二个定义的变量名是 Sales_vec,它所保存的元素是 Sales_item 类型的对象。
vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector 类型的每一种都指定了其保存元素的类型。因此,vector 和 vector 都是数据类型。

1.1. vector 对象的定义和初始化

vector 类定义了好几种构造函数,用来定义和初始化 vector 对象。表 3.4 列出了这些构造函数。
vector v1: vector 保存类型为 T 对象。默认构造函数 v1 为空。
vector v2(v1); v2 是 v1 的一个副本。
vector v3(n, i); v3 包含 n 个值为 i 的元素。
vector v4(n); v4 含有值初始化的元素的 n 个副本。

创建确定个数的元素
若要创建非空的 vector 对象,必须给出初始化元素的值。当把一个 vector 对象复制到另一个 vector 对象时,新复制的 vector 中每一个元素都初始化为原 vectors 中相应元素的副本。但这两个 vector 对象必须保存同一种元素类型:

vector<int> ivec1;       // ivec1 holds objects of type int
vector<int> ivec2(ivec1); // ok: copy elements of ivec1 into ivec2
vector<string> svec(ivec1); // error: svec holds strings, not ints

可以用元素个数和元素值对 vector 对象进行初始化。构造函数用元素个数来决定 vector 对象保存元素的个数,元素值指定每个元素的初始值:

vector<int> ivec4(10, -1);  // 10 elements, each initialized to -1
vector<string> svec(10, "hi!");  // 10 strings, each initialized to "hi!"

关键概念:vector 对象动态增长
vector 对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。因为 vector 增长的效率高,在元素值已知的情况下,最好是动态地添加元素。
这种增长方式不同于 C 语言中的内置数据类型,也不同于大多数其他编程语言的数据类型。具体而言,如果读者习惯了 C 或 Java 的风格,由于 vector 元素连续存储,可能希望最好是预先分配合适的空间。但事实上,为了达到连续性,C++ 的做法恰好相反,虽然可以对给定元素个数的 vector 对象预先分配内存,但更有效的方法是先初始化一个空 vector 对象,然后再动态地增加元素(我们随后将学习如何进行这样的操作)。
值初始化:
如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值初始化。这个由库生成的初始值将用来初始化容器中的每个元素,具体值为何,取决于存储在 vector 中元素的数据类型。
如果 vector 保存内置类型(如 int 类型)的元素,那么标准库将用 0 值创建元素初始化式:

vector<string> fvec(10); // 10 elements, each initialized to 0

如果 vector 保存的是含有构造函数的类类型(如 string)的元素,标准库将用该类型的默认构造函数创建元素初始化式:

vector<string> svec(10); // 10 elements, each an empty string

还有第三种可能性:元素类型可能是没有定义任何构造函数的类类型。这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。

1.2. vector 对象的操作
vector 标准库提供了许多类似于 string 对象的操作,表 3.5 列出了几种最重要的 vector 操作。
v.empty();如果 v 为空,则返回 true,否则返回 false。
v.size();返回 v 中元素的个数。
v.push_back(t); 在 v 的末尾增加一个值为 t 的元素。
v[n];返回 v 中位置为 n 的元素。
v1 = v2;把 v1 的元素替换为 v2 中元素的副本。
v1 == v2;如果 v1 与 v2 相等,则返回 true。

vector 对象的 size
empty 和 size 操作类似于 string 的相关操作(3.2.3 节)。成员函数 size 返回相应 vector 类定义的 size_type 的值。使用 size_type 类型时,必须指出该类型是在哪里定义的。vector 类型总是包括总是包括 vector 的元素类型:

 vector<int>::size_type        // ok
     vector::size_type            // error

向 vector 添加元素
push_back 操作接受一个元素值,并将它作为一个新的元素添加到 vector 对象的后面,也就是“插入(push)”到 vector 对象的“后面(back)”:

  vector<string> text;    // empty vector
     while (cin >> word) {
         text.push_back(word);     // append word to text
     }

该循环从标准输入读取一系列 string 对象,逐一追加到 vector 对象的后面。首先定义一个空的 vector 对象 text。每循环一次就添加一个新元素到 vector 对象,并将从输入读取的 word 值赋予该元素。当循环结束时,text 就包含了所有读入的元素。
vector 的下标操作
vector 中的对象是没有命名的,可以按 vector 中对象的位置来访问它们。通常使用下标操作符来获取元素。vector 的下标操作类似于 string 类型的下标操作;
vector 的下标操作符接受一个值,并返回 vector 中该对应位置的元素。vector 元素的位置从 0 开始。下例使用 for 循环把 vector 中的每个元素值都重置为 0:

   // reset the elements in the vector to zero
    for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
         ivec[ix] = 0;

和 string 类型的下标操作符一样,vector下标操作的结果为左值,因此可以像循环体中所做的那样实现写入。另外,和string 对象的下标操作类似,这里用 size_type 类型作为 vector 下标的类型。
下标操作不添加元素
初学 C++ 的程序员可能会认为 vector 的下标操作可以添加元素,其实不然:

 vector<int> ivec;   // empty vector
 for(vector<int>::size_type ix = 0; ix != 10; ++ix)
      ivec[ix] = ix; // disaster: ivec has no elements

关键概念:安全的泛型编程
调用 size 成员函数而不保存它返回的值,在这个例子中同样不是必需的,但这反映了一种良好的编程习惯。在 C++ 中,有些数据结构(如 vector)可以动态增长。上例中循环仅需要读取元素,而不需要增加新的元素。但是,循环可以容易地增加新元素,如果确实增加了新元素的话,那么测试已保存的 size 值作为循环的结束条件就会有问题,因为没有将新加入的元素计算在内。所以我们倾向于在每次循环中测试 size 的当前值,而不是在进入循环前,存储 size 值的副本。
C++ 程序员习惯于优先选用 != 而不是 < 来编写循环判断条件。在上例中,选用或不用某种操作符并没有特别的取舍理由;习惯于 C 或 Java 编程的 C++ 程序员可能会觉得难以理解,for 循环的判断条件用 != 而不是用 < 来测试 vector 下标值是否越界。C 程序员难以理解的还有,上例中没有在 for 循环之前就调用 size 成员函数并保存其返回的值,而是在 for 语句头中调用 size 成员函数。
C++ 中有些函数可以声明为内联(inline)函数。编译器遇到内联函数时就会直接扩展相应代码,而不是进行实际的函数调用。像 size 这样的小库函数几乎都定义为内联函数,所以每次循环过程中调用它的运行时代价是比较小的。
上述程序试图在 ivec 中插入 10 个新元素,元素值依次为 0 到 9 的整数。但是,这里 ivec 是空的 vector 对象,而且下标只能用于获取已存在的元素。
这个循环的正确写法应该是:

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

必须是已存在的元素才能用下标操作符进行索引。通过下标操作进行赋值时,不会添加任何元素。
警告:仅能对确知已存在的元素进行下标操作
对于下标操作符([] 操作符)的使用有一点非常重要,就是仅能提取确实已存在的元素,例如:

     vector<int> ivec;      // empty vector
     cout << ivec[0];       // Error: ivec has no elements!
     vector<int> ivec2(10); // vector with 10 elements
     cout << ivec[10];      // Error: ivec has elements 0...9

试图获取不存在的元素必须产生运行时错误。和大多数同类错误一样,不能确保执行过程可以捕捉到这类错误,运行程序的结果是不确定的。由于取不存在的元素的结果标准没有定义,因而不同的编译器实现会导致不同的结果,但程序运行时几乎肯定会以某种有趣的方式失败。
本警告适用于任何使用下标操作的时候,如 string 类型的下标操作,以及将要简要介绍的内置数组的下标操作。
不幸的是,试图对不存在的元素进行下标操作是程序设计过程中经常会犯的严重错误。所谓的“缓冲区溢出”错误就是对不存在的元素进行下标操作的结果。这样的缺陷往往导致 PC 机和其他应用中最常见的安全问题。

vector 容器的自增长

在容器对象中 insert 或压入一个元素时,该对象的大小增加 1。类似地,如果 resize 容器以扩充其容量,则必须在容器中添加额外的元素。标准库处理存储这些新元素的内存分配问题。
一般来说,我们不应该关心标准库类型是如何实现的:我们只需要关心如何使用这些标准库类型就可以了。 然而,对于 vector 容器,有一些实现也与其接口相关。为了支持快速的随机访问,vector 容器的元素以连续的方式存放——每一个元素都紧挨着前一个元素存储。
已知元素是连续存储的,当我们在容器内添加一个元素时,想想会发生什么事情:如果容器中已经没有空间容纳新的元素,此时,由于元素必须连续存储以便索引访问,所以不能在内存中随便找个地方存储这个新元素。于是,vector 必须重新分配存储空间,用来存放原来的元素以及新添加的元素:存放在旧存储空间中的元素被复制到新存储空间里,接着插入新元素,最后撤销旧的存储空间。如果 vector 容器在在每次添加新元素时,都要这么分配和撤销内存空间,其性能将会非常慢,简直无法接受。
由此可以推论:一般而言,使用 list 容器优于 vector 容器。但是,通常出现的反而是以下情况:对于大部分应用,使用 vector 容器是最好的。原因在于,标准库的实现者使用这样内存分配策略:以最小的代价连续存储元素。由此而带来的访问元素的便利弥补了其存储代价。
为了使 vector 容器实现快速的内存分配,其实际分配的容量要比当前所需的空间多一些。vector 容器预留了这些额外的存储区,用于存放新添加的元素。于是,不必为每个新元素重新分配容器。所分配的额外内存容量的确切数目因库的实现不同而不同。比起每添加一个新元素就必须重新分配一次容器,这个分配策略带来显著的效率。事实上,其性能非常好,因此在实际应用中,比起 list 和 deque 容器,vector 的增长效率通常会更高。

capacity 和 reserve 成员
vector 容器处理内存分配的细节是其实现的一部分。然而,该实现部分是由 vector 的接口支持的。vector 类提供了两个成员函数:capacity 和 reserve 使程序员可与 vector 容器内存分配的实现部分交互工作。capacity 操作获取在容器需要分配更多的存储空间之前能够存储的元素总数,而 reserve 操作则告诉 vector 容器应该预留多少个元素的存储空间。

注意:弄清楚容器的 capacity(容量)与 size(长度)的区别非常重要。size 指容器当前拥有的元素个数;而 capacity 则指容器在必须分配新存储空间之前可以存储的元素总数。

     vector<int> ivec;

     // size should be zero; capacity is implementation defined
     cout << "ivec: size: " << ivec.size()
          << " capacity: "  << ivec.capacity() << endl;

     // give ivec 24 elements
     for (vector<int>::size_type ix = 0; ix != 24; ++ix)
          ivec.push_back(ix);

     // size should be 24; capacity will be >= 24 and is implementation defined
     cout << "ivec: size: " << ivec.size()
          << " capacity: "  << ivec.capacity() << endl;

在我们的系统上运行该程序时,得到以下输出结果:
ivec: size: 0 capacity: 0
ivec: size: 24 capacity: 32
注意:每当 vector 容器不得不分配新的存储空间时,以加倍当前容量的分配策略实现重新分配。

vector 的每种实现都可自由地选择自己的内存分配策略。然而,它们都必须提供 vector 和 capacity 函数,而且必须是到必要时才分配新的内存空间。分配多少内存取决于其实现方式。不同的库采用不同的策略实现。
此外,每种实现都要求遵循以下原则:确保 push_back 操作高效地在 vector 中添加元素。从技术上来说,在原来为空的 vector 容器上 n 次调用 push_back 函数,从而创建拥有 n 个元素的 vector 容器,其执行时间永远不能超过 n 的常量倍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值