【C++基础】系列博客为参考《C++ Primer中文版(第5版)》(C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。
1.前言
标准库类型vector
表示对象的集合,其中所有对象的类型都相同。
集合中的每个对象都有一个与之对应的索引,索引用于访问对象。
vector
也常被称作容器(container)。
要想使用vector
,必须包含适当的头文件。在后续的例子中,都将假定做了如下using
声明:
1
2
#include <vector>
using std::vector;
C++语言既有类模版,也有函数模版,其中vector
是一个类模版。编译器根据模版创建类或函数的过程称为实例化,当使用模版时,需要指出编译器应把类或函数实例化成何种类型。
对于类模版来说,我们通过提供一些额外信息来指定模版到底实例化成什么样的类,需要提供哪些信息由模版决定。提供信息的方式总是这样:即在模版名字后面跟一对尖括号,在括号内放上信息。
以vector
为例,提供的额外信息是vector
内所存放对象的类型:
1
2
3
vector<int> ivec;
vector<Sales_item> Sales_vec;
vector<vector<string>> file;
⚠️vector
能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector
。
在早期版本的C++标准中如果
vector
的元素还是vector
(或着其他模版类型),必须在外层vector
对象的右尖括号和其元素类型之间添加一个空格,如应该写成vector<vector<int> >
。但是在C++11新标准中,可直接写成vector<vector<int>>
。
2.定义和初始化vector
对象
以下为定义vector
对象的常用方法:
可以默认初始化vector
对象,从而创建一个指定类型的空vector
:
1
vector<string> svec;//默认初始化,svec不含任何元素
此外,还允许把一个vector
对象的元素拷贝给另外一个vector
对象,注意两个vector
对象的类型必须相同:
1
2
3
4
vector<int> ivec;//初始状态为空
vector<int> ivec2(ivec);//把ivec的元素拷贝给ivec2
vector<int> ivec3=ivec;//把ivec的元素拷贝给ivec3
vector<string> svec(ivec2);//错误:svec的元素是string对象,不是int
2.1.列表初始化vector
对象
在第2部分一开始,我们介绍了很多初始化方式,都是C++中常用的初始化方式。在大多数情况下这些初始化方式可以相互等价地使用,但是在某些情况下会有限制:
- 使用拷贝初始化(即使用
=
时),只能提供一个初始值。 - 如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号的形式初始化。
- 如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里:
1
2
vector<string> v1{"a","an","the"};//列表初始化
vector<string> v2("a","an","the");//错误
2.2.创建指定数量的元素
还可以用vector
对象容纳的元素数量和所有元素的统一初始值来初始化vector
对象:
1
2
vector<int> ivec(10,-1);//10个int类型的元素,每个都被初始化为-1
vector<string> svec(10,"hi !");//10个string类型的元素,每个都被初始化为"hi !"
2.3.值初始化
通常情况下,可以只提供vector
对象容纳的元素数量而不提供初始值。此时库会创建一个值初始化的元素初值,并把它赋给容器中的所有元素。这个初值由vector
对象中元素的类型决定。
1
2
vector<int> ivec(10);//10个元素,每个都初始化为0
vector<string> svec(10);//10个元素,每个都是空string对象
⚠️特殊情况:如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector
对象了:
1
2
vector<string> v7{10};//v7有10个默认初始化的元素
vector<string> v8{10,"hi"};//v8有10个值为"hi"的元素
❗️要想使用列表初始化vector
对象,花括号里的值必须与元素类型相同。
3.向vector
对象中添加元素
可以利用vector
的成员函数push_back
向其中添加元素。push_back
负责把一个值当成vector
对象的尾元素“压到(push)”vector
对象的“尾端(back)”。例如:
1
2
3
4
vector<int> v2;
for(int i=0;i!=100;++i)
v2.push_back(i);//依次把整数值放到v2尾端
//循环结束后v2有100个元素,值从0到99
⚠️如果循环体内部包含有向vector
对象添加元素的语句,则不能使用范围for循环。范围for语句体内不应改变其所遍历序列的大小。
4.其他vector
操作
除了push_back
之外,vector
还提供了几种其他操作,大多数都和string
的相关操作类似,下表中列出了其中比较重要的一些:
举个例子:
1
2
3
4
5
6
vector<int> v{1,2,3,4,5,6,7,8,9};
for(auto &i : v)
i*=i;
for(auto i : v)
cout<<i<<" ";
cout<<endl;
⚠️第一个循环把控制变量i
定义成引用类型,这样就能通过i
给v
的元素赋值。输出结果见下:
vector
的empty
和size
两个成员与string
的同名成员功能完全一致:empty
检查vector
对象是否包含元素然后返回一个布尔值;size
则返回vector
对象中元素的个数,返回值的类型是由vector
定义的size_type
类型。
⚠️要使用size_type
,需首先指定它是由哪种类型定义的。vector
对象的类型总是包含着元素的类型:
1
2
vector<int>::size_type //正确
vector::size_type //错误
4.1.计算vector
内对象的索引
举个简单的例子,假设有一组成绩的集合,其中成绩的取值是从0到100。以10分为一个分数段,要求统计各个分数段各有多少个成绩。
1
2
3
4
5
6
7
8
//以10分为一个分数段统计成绩的数量:0~9、10~19、...、90~99、100
vector<unsigned> scores(11,0);
unsigned grade;
while(cin>>grade)
{
if(grade<=100)
++scores[grade/10]
}
⚠️两个整数相除,结果还是整数,余数部分被自动忽略掉了。例如,42/10=4、65/10=6、100/10=10等。
‼️不能用下标形式添加元素。vector
对象(以及string
对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。例如下面这个例子:
1
2
3
vector<int> ivec;//空vector对象
for(decltype(ivec.size()) ix=0;ix!=10;++ix)
ivec[ix]=ix;//严重错误:ivec不包含任何元素
正确做法应该是使用push_back
添加新元素:
1
2
for(decltype(ivec.size()) ix=0;ix!=10;++ix)
ivec.push_back(ix);
此外,试图用下标的形式去访问一个不存在的元素将引发错误,不过这种错误不会被编译器发现,而是在运行时产生一个不可预知的值。例如:
1
2
vector<int> ivec(10);
cout<<ivec[10];//错误:ivec元素的合法索引是从0到9
不幸的是,这种通过下标访问不存在的元素的行为非常常见,而且会产生很严重的后果。所谓的缓冲区溢出(buffer overflow)指的就是这类错误。
确保下标合法的一种有效手段就是尽可能使用范围for语句。