C++标准库笔记(三)

关于标准库类型vector

Posted by YD Blog on February 28, 2022

C++标准库笔记(三)

什么是vector

标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中每个对象都有一个与之对应的索引,索引用于访问对象。因为vector”容纳着”别的对象,所以它也常被称作容器。

想要使用vector,必须包含适当的头文件。在后续的示例中都假定做了如下using声明:

#include <vector>
using std::vector;

C++既有类模板也有函数模板,其中vector是一个类模板。模板本身不是类或函数,相反可以将模板看作为编译器生成类或者函数编写的一份说明。编译器根据模板创建类或函数的过程称作实例化,当使用模板时,需要指定编译器需要把类或函数实例化为何种类型。

对于类模板来说,我们通过提供一些额外的信息来指定模板到底要实例化成什么样的类,需要提供那些信息由模板决定。提供信息的方式总是这样:在模板名字后面跟一对尖括号,在括号内放上信息。

以vector为例,如下:

vector<int> ivec;               //ivec保存int类型的对象
vector<string> file;            //file保存string类型的对象

vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector。除此之外,其他大多数(非引用)内置类型和类类型都可以构成vector对象,甚至组成vector的元素也可以是vector。

定义和初始化vector对象

常用方法如下:

vector<T> v1;                   //v1是一个空vector,它潜在的元素是T类型的,执行默认初始化
vector<T> v2(v1);               //v2中包含有v1所有元素的副本
vector<T> v2 = v1;              //等价于v2(v1),v2中包含有v1所有元素的副本
vector<T> v3(n, val);           //v3中包含有n个重复的元素,每个值都是val
vector<T> v4(n);                //v4中包含n个重复的执行了值初始化的对象
vector<T> v5{a,b,c...};         //v5包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v5 = {a,b,c...};      //等价于v5{a,b,c...}

值初始化一般是三种处理方式: ~ 如果T有用户定义的缺省构造函数,直接调用; 如果T有编译器生成的缺省构造函数,先0值初始化再调用; 如果T根本不是类,直接0值初始化。

向vector对象添加元素

一般来说,使用vectori时是先初始化一个空vector,然后使用push_back函数向其中添加元素。push_back函数是vector的成员函数之一,负责把一个值当作vector的尾部元素”压入”到vector对象的尾端。

注意:vector对象在运行时能高效快速的添加元素,因此在定义vector对象时设定其大小也就没什么必要了。

其他vector操作

vector提供的操作大多数都和string的相关操作类似,如下:

v.empty();              //如果v不含有任何元素,返回true,否则返回false
v.size():               //返回v中元素的个数
v.puth_back(t);         //向v的尾端添加一个值为t的元素
v[n];                   //返回v中第n个位置上元素的引用
v1 = v2;                //用v2中的元素的拷贝替换v1中的元素
v1 = {a,b,c...};        //用列表中元素的字面值替换v1中的元素
v1 == v2;               //v1和v2相等当且仅当它们的元素数量相同且对应位置的元素值都相同时
v1 != v2;               //v1和v2的元素数量不相同或对应位置的元素值有不相同
<,<=,>,>=;              //以字典顺序进行比较

访问vector对象中元素的方法也和string对象一样,如:

for (auto &i : v)
{
        i *= i;
}

不能通过下标来添加元素。

只能对已经存在的元素执行下标操作。

关于迭代器

所有的标准库容器都支持迭代器,但是只有其中的少数几种才同时支持下标操作。

类似于i指针类型,迭代器也提供了对象的间接访问,但和指针不同的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都有名为begin和end的成员,其中begin成员负责返回指向第一个元素的迭代器,而end成员负责返回指向容器”尾元素的下一个位置”的迭代器。

迭代器的运算

如下是一些关于迭代器的运算:

*iter;                  //返回迭代器iter所指向的元素
iter -> men;            //解引用iter并获取该元素名为men的成员,等价于(*iter).men
++iter;                 //令iter指向容器的下一个元素
--iter;                 //令iter指向容器的上一个元素
iter1 == iter2;         //判断两个迭代器i是否相等,若两个迭代器指向的同一个元素或为同一个容器的尾后迭代器,则为true,反之为false
iter1 != iter2;         //判断两个迭代器i是否不相等,若两个迭代器不指向指向的同一个元素且不为同一个容器的尾后迭代器,则为true,反之为false
iter + n;               //迭代器加上一个迭代器仍得一个迭代器,迭代器向前移动了n个元素,结果迭代器指向容器内的一个元素或者容器尾的下一个位置
iter - n;               //迭代器减去一个迭代器仍得一个迭代器,迭代器向后移动了n个元素,结果迭代器指向容器内的一个元素或者容器尾的下一个位置
iter1 += n;             //迭代器加法的复合赋值语句,将iter1加n的结果赋给iter1
iter1 -= n;             //迭代器减法的复合赋值语句,将iter1减n的结果赋给iter1
iter1 - iter2;          //两个迭代器相减的结果是它们之间的距离,也就是说,也就是说将运算符右侧的迭代器向前移动几个元素得到运算符左侧的迭代器。两个迭代器必须指向同一个容器。
>,>=,<,<=;              //关系运算符,如果某迭代器指向的元素位置在另一个迭代器之前,则说前者小于后者。两个迭代器必须指向同一个容器。

如何定义一个迭代器

实际上,那些能够使用迭代器的标准库类型使用iterator或者const_iterater来表示迭代器的类型,如下:

vector<int>::iterater it;               //it能读写vector<int>的元素
string::iterator it2;                   //it2能读写string的字符

vector<int>::const_iterator it3;        //it3只能读取元素,不能写入
string::const_iterator i4;              //it4只能读取字符,不能写入

关于iterator和const_iterater,在使用begin和end返回迭代器时,返回的迭代器类型与容器本身的类型有关,若容器本身是const常量,则返回const_iterater类型,否则返回iterator类型。

任何一种可能会改变vecter对象容量的操作都会使该对象的迭代器失效。