C++标准库(STL)之vector容器的使用(包括特点、初始化、遍历与常用操作)

来源:互联网 发布:ug编程实例教程ppt 编辑:程序博客网 时间:2024/06/07 02:33

1. vector容器的性质

在C++中,vector容器具有如下性质:

  1. 元素连续存储。就如C/C++中的数组一样,具有随机访问的特性,寻址极快。

  2. 自动扩容。其内部存储空间由分配器自动管理,随着其存储元素的增加,当目前存储空间不够时,会自动地申请更大的内存空间并完成原有数据的拷贝,无需外界干预。

  3. 采用模板编程思想。vector本身是一个模板类,其模板参数可以传入任意C++的原始数据类型或类与结构或其指针、引用等。

  4. 通用性极强。vector是STL中的标准容器,几乎所有正规编译器附带的库中都对其进行了支持。

  5. 在非尾部进行插入、删除操作效率相对较低。由于vector中元素连续存储的特性,在其中间插入或删除元素,需要对其后的所有元素进行移动,因此效率比较低下。因此,在需要频繁插入、删除元素的情况下,不宜用vector作为容器。

2. vector中数据类型的绑定

使用vector时,至少需要包含vector头文件,其所有定义皆在std命名空间中。正如介绍中所述,C++中的vector类是一个模板类,因此在使用时,要指定其中存储的元素类型。先来看个简单的,将C++中的原始数据类型作为元素类型:

vector<double> vecPrice;

此外,C++中的类、类的指针等也都可以使用vector进行存储。如下面定义了一个存储CShape类型指针的向量:

class CShape{private:    int m_nID;public:    virtual void draw() = 0;}int main(){    vector<CShape *> vecShape;}

3. vector的初始化

下面先介绍vector类型的初始化:

  • 初始化一个空向量

    vector<int> a1;   //声明一个空的int类型的向量,名为a1cout << "size:" << a1.size() << " capcity:" << a1.capacity() << endl; // 0 0
  • 初始化一个包含20个元素的向量,其中的元素被初始化为0

    vector<int> a2(20);   //声明一个名为a2的向量,内部包含20个元素,且元素没有进行人工初始化(VC默认初始化为全0)cout << "size:" << a2.size() << " capcity:" << a2.capacity() << endl; // 20 20 
  • 初始化一个包含10个元素,每个元素都为0的向量

    vector<int> a3(10, 3);    //声明一个大小为10,且元素全部初始化为3的向量cout << "size:" << a3.size() << " capcity:" << a3.capacity() << endl; // 10 10 
  • 用数组初始化向量

    vector<int> a4({10, 3, 2, 4, 666, 23333, 88888888});  //用数组初始化向量cout << "size:" << a4.size() << " capcity:" << a3.capacity() << endl; // 7 10
  • 用一个向量初始化另一个向量

    vector<int> a6(a4);   //用一个向量初始化另一个向量cout << "size:" << a6.size() << " capcity:" << a6.capacity() << endl; // 7 10
  • 用首尾指针部分初始化向量

    int arr1[] = { 1010, 2222, 3333, 233333, 6666666, 2828 };vector<int> a7(arr1, arr1 + 4);cout << "size:" << a7.size() << " capcity:" << a7.capacity() << endl; // 4 4

    需要说明的是,由于迭代器(这个概念在后文中阐述,这里大家先简单知道一下这是一种访问C++容器中存储的元素的方式就可以了)中重载了[]运算符和->运算符,因此其行为和C/C++中的指针很类似(因此在某些外文书籍中,迭代器和智能指针都被称为pointer-like classes),于是用迭代器对向量进行初始化也是合法的。

    vector<int> b1(a4.begin(), a4.begin() + 4);   //将a4中的前4个元素作为向量b1的初始值cout << "size:" << b1.size() << " capcity:" << b1.capacity() << endl; // 4 4

4. vector的遍历方法

vector容器有许多的遍历方法,现在假设有一个名为a的int类型向量:

vector<int> a({ 3, 5, 2, 33, 666, 1113, 77, 222, 999 });

下面逐一进行介绍。大家可以根据实际情况灵活选择遍历方式。

  • 使用下标在循环中逐一访问

    for (int i = 0; i < a.size(); i++){cout << a[i] << " ";}cout << endl;a[1] = 233333;cout << a[1] << endl; //值为233333

    由于vector类中重载了[]运算符,因此使得这种方式和数组中的下标访问方式几乎一样。并且在[]运算符重载函数中,返回的是对应下标元素的引用,故对其进行修改就是对容器中的数据进行修改。在上例中,修改1号下标的元素值后,再次遍历后即会发现容器中的对应值也发生了变化。

  • 使用 at(int) 函数访问指定下标的元素,配合循环逐一访问

    for (int i = 0; i < a.size(); i++){cout << a.at(i) << " ";}a.at(2) = 9090;cout << a.at(2) << endl;  //9090

    这种方式其实和上一种方式很类似,只不过是用vector的名为at的成员函数进行操作的。同样返回的是容器中元素的引用,因此对其进行修改就是对容器中对应数据进行修改。

  • 使用迭代器访问

    迭代器是C++标准模板库(STL)中抽象出来的一种类型,专门用于对容器的访问。由于迭代器类中重载了++、–、[]和->等运算符,因此其行为和传统的指针基本一致,可以将其视为指针进行操作。正是由于其重载了++、–等操作符,因此迭代器中一定要知道容器中存储的数据类型,这样在执行自增、自减等运算时才知道真正要加减的内存大小。要说明的是:不同容器类型有各自的迭代器,同一容器中存储的不同类型元素也有各自的迭代器。哪怕就算是同种容器、同种元素类型,迭代器也还需要进行区分。

    以vector类型存储int元素为例,一共有如下四种迭代器可用:

    • 使用正向迭代器
    for (vector<int>::iterator itr = a.begin(); itr != a.end(); itr++){    cout << *itr << " ";    if (*itr == 233333)    {        *itr = 5;    }}cout << endl;

    通过上面的代码不难看出,vector类型中int的正向迭代器类型为vector<int>::iterator。举一反三一下,如果需要vector类型中CShape*类型的迭代器,则其类型应该为vector<CShape*>::iterator。

    长此以往,大家都发现这样很不方便,写的很复杂。于是C++ 11标准中,加入了auto关键字,意思就是“让编译器自动推断出该变量的类型”。于是在支持C++ 11标注的编译器上,就可以这么写了:

    for (auto itr = a.begin(); itr != a.end(); itr++){    cout << *itr << " ";}cout << endl;

    这样看上去就简洁多了,后面咱们都这么用了,但是建议大家还是应该知道auto真正指代的类型,不然一旦换到不支持C++ 11标准的编译器环境下工作,不能偷懒就会原形毕露了。

    此外,每一个向量对象中都可以通过其begin()和end()方法返回指向其首尾元素的迭代器。又由于前面说过,迭代器类重载了++、–、==、!=、<、<=、>、>=、[]和->等运算符,所以其最终行为和指针很类似。所以这个for循环中的内容大家就不足为奇了,包括为什么可以将容器中的值233333改为5。

    在了解了上述内容后,再告诉你尾元素迭代器其实是指向当前容器中尾元素后的一个存储单元的,大概你也就不会感到吃惊了。毕竟只是借助这个位置进行计算,在for循环中判断的条件也是不等于或小于,根本不会去访问这个内存空间。

    • 使用正向常量迭代器

    有时候我们在遍历容器时,只是想获取而并不想修改其值,这时候使用正向常量迭代器正好就能满足我们的需要。如果在使用正向常量迭代器时,试图对容器中的值进行改写,编译时就会产生错误。

    for (vector<int>::const_iterator citr = a.cbegin(); citr != a.cend(); citr++){    cout << *citr << " ";}cout << endl;

    需要说明的是,vector容器的int类型的正向常量迭代器类型为vector::const_iterator,而对于一个具体的vector对象,其首尾元素的正向常量迭代器通过cbegin()与cend()获取。当然,也可以使用auto关键字:

    for (auto citr = a.cbegin(); citr != a.cend(); citr++){    cout << *citr << " ";}cout << endl;
    • 使用反向迭代器

    考虑到反向遍历容器也是一种常见的需求,因此STL中提供了反向迭代器,用于逆向遍历容器。在本例中,反向迭代器类型为vector<int>::reverse_iterator。其首尾元素的反向迭代器通过rbegin()与rend()获取,值得注意的是,依然是从rbegin()获取的迭代器开始,到rend()获取的迭代器之前停止,每一步操作也依然用++运算符。(毕竟++运算符是由迭代器设计者进行的,为了统一,不要对此感到奇怪。)

    for (auto itr = a.rbegin(); itr != a.rend(); itr++){    cout << *itr << " ";}cout << endl;
    • 使用反向常量迭代器

    这一段相信不用多解释了吧,大家肯定都有举三反一的能力了。在本例中,反向常量迭代器类型为vector<int>::const_reverse_iterator,首尾迭代器分别通过crbegin()和crend()方法获取。

    for (auto itr = a.crbegin(); itr != a.crend(); itr++){    cout << *itr << " ";}cout << endl;
  • 通过标准算法库中的for_each函数访问

    for_each函数一共需要三个参数,分别是起始位置,终止位置和找到后的处理函数地址。这里使用lambda表达式作为处理函数地址:

    for_each(a.cbegin(), a.cend(), [](const int& val)->void {cout << val << endl; });
  • 通过“基于范围的for循环访问”(C++11 新增)

    这又是C++ 11引入的语法糖,极大地简化了for循环的操作。具体方法如下:

    for (auto elem : a){cout << elem << " ";if (elem = 2333333){    elem = 666666;          // 改变失败}}cout << endl;for (auto& elem : a){cout << elem << " ";if (elem = 2333333){    elem = 666666;          // 改变成功}}cout << endl;

    唯一需要提醒的是,当不加引用时,传出来的elem是值拷贝后的产物,而只有在加上引用以后,传出来的elem才是对容器中元素的引用,这时候对其进行修改才会影响到容器中对应的元素。

5. vector的常用函数

其实在上面的介绍中,我们已经不知不觉地使用了很大一部分vector中的函数了。下面再列举一些常用的,说明其用途。

  1. 容量相关的:

    • size():返回vector容器中当前元素个数

    • resize(int n [, int val]):改变vector容器中的元素个数(有可能插入新的值,也有可能删除一部分值)

      • 如果当前元素个数大于n,则对容器进行截断,只保留前n个元素
      • 如果当前元素个数小于n,且n小于容器的当前容量,则按照val的值对size-n之间的区域进行拷贝构造。没指定val则填0;
      • 如果当前元素个数小于n,而n大于容器的当前容量,则先重新分配足够大的空间,完成拷贝后,再执行上述动作。
    • capacity():容器中目前可以存储的元素数量,超过后会进行扩容。

    • empty():判断当前容器是否为空(没有元素),是则返回true。

    • reserve(size_t n):为当前vector容器扩容:

      • 如果当前容器的capacity值小于n,则将容器动态扩容到n(分配、拷贝、释放旧空间)

      • 其他情况下无动作

    • void shrink_to_fit():调整当前容器的大小,使其与元素个数一致(在VS2017中测试是将vector容器的大小调整为能存放当前元素个数+1)

  2. 访问相关的:

    • push_back(value):在向量末尾加入新元素(推荐,效率高)

    • pop_back():删除末尾元素(推荐,效率高)

    • at(int index):返回指定下标的元素引用

    • 下标访问,即[index]:返回指定下标的元素引用

    • front():返回首元素的引用

    • back():返回末尾元素的引用

    • insert():插入新元素(不推荐,效率较低),该函数有以下三种用法:

      • iterator insert(iterator loc, const TYPE &val ):在指定位置loc前插入值为val的元素,返回指向该元素的迭代器
      • void insert(iterator loc, size_type num, const TYPE &val ): 在指定位置loc前插入num个值为val的元素
      • void insert(iterator loc, input_iterator start, input_iterator end ):在指定位置loc前插入区间[start, end)的所有元素 .
    • earse():删除元素(不推荐,效率较低)。该函数有以下两种用法:

      • iterator erase (iterator position):删除指定位置的元素
      • iterator erase (iterator start, iterator end):删除[start, end)区间内的所有元素
    • clear():清空本容器,清空后容器size为0,empty为true。但容器的容量变不变化并没有强制规定。

    • swap(vector& x):互换两个vector容器中存储的值。

  3. 迭代器相关的:

    共8个函数,已经在遍历——迭代器那里介绍过了,这里就不再列举了。

20171204注:之前上传的文章由于采用了Markdown语法,而其中迭代器部分有频繁使用了<和>两个符号,当时没注意对其进行转义,从而导致迭代器介绍部分的内容没能完全显示,现在已对其进行修正。

阅读全文
0 0