题图:NoCopy
字数:2492 | 3分钟读完我3小时的思考
C++のvector类
上一篇文章C++のstring类,我们讲了一下C++中的string类,简单梳理了string对象的创建方式,数据操作等,今天我们继续介绍C++中另外一个概念vector。
那么,什么是vector呢?简单来讲,它就是一个可以用来装东西的容器。我们首先来看一下怎么用vector这个类吧。
如果想要使用vector,我们需要包含以下头文件
#include<vector>
由于vector属于std命名空间范围的类,因此还需要指定命名空间,如下:
using std::vector;
或者在需要的地方直接在vector前指定命名空间:
std::vector...
vector为什么是容器呢?因为它能装其他的对象,这有点像数组,但是远比数组强大。vector基本可以装所有类型的对象,而数组大多数情况下只能存数字或字符。
关于容器,我们后面详细介绍,以后我们还会学习很多种类型的容器,大体上分为两类,序列型容器和关联型容器。而我们今天所说的vector即是序列型容器。所谓序列型就是指vector这个东西存数据的时候按照先后顺序一个一个存,可以把它想象成数据结构中的栈这个概念。
除此之外,vector还是一个模板类,模板这个概念是泛型编程里面的概念,这我们后面学习泛型编程的时候再详细说(暂时不了解模板,丝毫不影响我们学习vector的使用)。现在我们可以把它理解为用vector创建对象时,是有一个模板的,可以根据传入参数的数据类型创建对象,这也是vector能够存储基本所有内置类型对象和类类型对象的原因。
但是有一点,vector并不能存引用型变量,引用仅仅是一个变量的别名,而不带表实体。
我们先来看一下怎么用vector创建对象吧。
代码如下所示:
...
class A
...
vector<int> vec_int; // 创建整型的vector
vector<A> a_vec; // 创建一个用于存A的对象的vector
vector<vector<string>> file; //创建一个用于存储vector的vector,前者中又存着string对象
我们需要注意的是,vector并不是像int, string等具体的类型,vector并不是类型,所以不能直接使用:
vector vec;
这种方式创建对象,而应该指明vector中所存元素的类型。
下面我们来看一下怎么样初始化一个vector吧。由于C++中vector属于类模板,因此其初始化也需要用类模板的形式。如下:
vector<T> v1 初始化一个空的vector v1,其中的元素类型为T
vector<T> v2(v1) 使用v1中的所有元素初始化一个vector v2
vector<T> v3 = v1 使用v1中的所有元素初始化一个vector v3
vector<T> v4(n) 定义vector v4,并用n个T类型的元素初始化
vector<T> v5{a, b, c} 使用{a, b, c}三个元素初始化v5
vector<T> v6={a, b, c} 使用{a, b, c}三个元素初始化v6
下面我们简单用几行示例代码看一下怎么样定义和初始化vector对象吧:
//Cpp_Vector参考代码
#include <iostream>
#include <vector>
using std::vector;
int main() {
vector<int> v1; //创建一个int型vector v1
vector<int> v2(10); //v2中存有10个0
vector<int> v3(10, 1); //v2中存有10个1
vector <std::string > v4(10); //v3中存有10个""
vector <std::string > v5(10, "Hello"); //v3中存有10个"Hello"
vector <std::string> v6{ "Hello", "World", "!" }; //v6中存有"Hello"、"World"、"!"
return 0;
}
既然,vector是用来存对象的,那么我们怎么来访问它里面存储的对象呢?C++11中提供了5种方式,分别是:
at 访问vector中特定的元素
operator[] 通过索引访问指定的元素
front 访问vector中第一个元素
back 访问vector中最后一个元素
data 获取vector首地址
我们看一下代码:
std::cout << v6.at(0) << std::endl;
std::cout << v6.front() << std::endl;
std::cout << v6.back() << std::endl;
std::cout << v6[0] << std::endl;
std::cout << v6.data() << std::endl;
std::cout << *v6.data() << std::endl;
std::cout << v6.data()[1] << std::endl;
v6上面初始化时,值为{ "Hello", "World", "!" },执行上面的代码后输出
Hello
Hello
!
Hello
000002789C905620
Hello
World
注意上面的data()方法是在C++11标准才出现的,C++98标准是没有的。
对于一个vector对象,既然它是容器,便有一些跟容量有关的操作,C++11标准中提供了7个操作:
empty 判断vector对象是否为空
size 获取vector对象的大小
max_size vector可以存的最大元素个数,与内存大小有关
reserve 改变capacity的大小
resize 改变size的大小
capacity 当前vector的容量
shrink_to_fit 释放vector中没有使用的内存
我们看一下示例代码:
std::cout << v6.empty() << std::endl;
std::cout << v6.size() << std::endl;
std::cout << v6.max_size() << std::endl;
std::cout << v6.capacity() << std::endl;
v6.reserve(100);
std::cout << v6.capacity() << std::endl;
std::cout << v6.size() << std::endl;
v6.resize(200);
std::cout << v6.size() << std::endl;
v6.shrink_to_fit();
std::cout << v6.capacity() << std::endl;
运行结果如下:
0
3
461168601842738790
3
100
3
200
200
可以看出,vector的size与capacity并不是一个概念。size指当前vector中所有元素实际占用的空间大小,而capacity指当前vector的容量,可能vector中根本没有存这么多的元素。只是代表代码申请了多少内存。所以,才会有以上的输出结果。
下面,我们说一下有关vector对象的修改相关方法。C++11中有以下几种:
clear 清空vector中的内容
insert 在某个位置插入元素
emplace 在某个位置插入元素
erase 擦除元素
push_back 向vector中存入元素,size增加
emplace_back 在vector最后一个元素后面插入一个新元素
pop_back 从vector推出一个元素,size减小
resize 改变size的大小
swap 交换两个vector的内容
我们写一段代码演示一下:
vector <int> v_int1{ 1, 2, 3 };
vector <int> v_int2{ 4, 5, 6 };
v_int1.insert(v_int1.begin(), 8);
v_int1.emplace(v_int1.begin(), 9);
v_int1.push_back(2);
v_int1.emplace_back(12);
v_int1.pop_back();
v_int1.swap(v_int2);
v_int1.erase(v_int1.begin());
上面的代码中我们看见几处v_int1.begin(), 这里其含义就是获取v_int1的起始迭代器。关于什么是迭代器,我们下面来说。
vector中有迭代器的概念,基本所有集合类都有。那么迭代器是什么呢?它给我们提供了访问vector元素的方法,类似于指针,可以通过迭代器访问和修改vector中的元素。那么,怎么样使用迭代器呢?
首先,我们需要获取vector中的迭代器,如下:
v_int1.begin() 获取v_int1中的起始迭代器
v_int1.end() 获取v_int1中的结尾迭代器
可以通过上面的代码获取vector中的起始和结尾迭代器。
那么怎么样通过迭代器更改vector中的元素呢?如下:
*(v_int1.begin() + 1) = 9;
说到这里,可能有的朋友就要担心了,这样直接获取到一个可以随便修改vector的迭代器会不会不安全呢?万一代码中我们错误地修改了迭代器索引到的值,而实际上并不是我们想要的怎么办呢?
是的,这样的确不安全,所以vector也提供了只读迭代器的获取方式cbegin()和cend(), c的含义就是const。
所以,下面的代码,编译器是会报错的:
*(v_int1.cbegin() + 2) = 8;
一般来说,我们在代码中我们不会直接指定迭代器的类型,根据第一篇的C++自动类型推导及其他,我们一般使用auto获取迭代器的类型,示例代码如下:
for (auto it = v_int1.cbegin();it != v_int1.cend() && !it->empty(); ++it)
cout << *it << endl;
这里,我们先列一下有关迭代器的运算吧(iter为迭代器,上面的it):
*iter 对元素解引用,得到元素的值,此处为第一个元素的值
++iter 迭代器自增,等价与itr+=1
--iter 迭代器自减,等价于iter-=1
iter1 == iter2 判断两个迭代器是否相等
iter1 != iter2 判断两个迭代器是否不等
iter1 > iter2 判断iter1是否大于iter2,此外还有<,<=,>=
今天的vector我们就介绍到这里,仅仅是容器的一个小开始,后面的序列化容器还有deque、list、array、string等,你没看错,string也是序列化容器。以后讲到容器的时候详细做个对比。如果你对这篇文章有什么疑问,欢迎评论区留言,我会一一解答,如果有什么错误之处,还请指正。下篇文章,我们简单说一下array。