前言
前几节我们学习了string类,那么本节我们来学习一下vector类,vector正式属于STL的一员。有了string类的学习基础,vector类的学习会更加容易上手一些,那么废话不多说,我们正式进入今天的学习
使用vector我们需要包含头文件<vector>
vector是一个标准的模板,第一个模板参数表示要存放的数据类型;第二个参数的类型是一个空间配置器和内存池,可以提高内存使用的效率。目前我们不需要管,不需要去传参
1.vector的构造、析构、赋值
vector的英文意思是向量,但vector的本质其实是一个可以进行扩容的顺序表。vector的接口设计得非常简洁,不像string类那么的冗余,那么我们还是首先来看一下vector的构造:
(这里需要注意:allocator是空间配置器,我们暂时不需要管)
(1):这里表示的是一个无参数的构造
(2):这里表示使用n个value的值去完成构造
(3):这里表示用迭代器区间完成构造
(4):这里表示的是拷贝构造
void test_vector1()
{
vector<int> v1;
vector<int> v2(3, 6);
vector<int> v3(++v2.begin(), v2.end());
vector<int> v4(v3);
}
下面我们来看一下vector的析构:
vector的析构函数也是直接自动调用的,这里不做过多讲解了
我们最后来看一下vector的赋值:
这一块也没什么好说的,就是用一个vector赋值给另外一个vector
假设我们在这里想要遍历一下vector该怎么办呢?
和之前学习string一样,我们有三种方式来遍历vector:
1.下标+[] 遍历
vector<int> v1;
vector<int> v2(3, 6);
vector<int> v3(++v2.begin(), v2.end());
vector<int> v4(v3);
for (size_t i = 0; i < v2.size(); i++)
{
cout << v2[i] << " ";
}
2.使用迭代器遍历
这里可以用正向迭代器、反向迭代器、const迭代器等来遍历,不一一演示了,这里只选取正向迭代器为例子进行讲解:
因为迭代器都是定义在类域中的,所以在使用迭代器的时候需要指定类域:
while (it != v2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
3.使用范围for遍历
范围for的底层就是使用的迭代器,所以也没有什么好说的:
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
2.vector中与容量有关的接口
vector中与容量有关的接口如下所示:
1.size
该接口可以返回vector中存在的元素的个数
void test_vector2()
{
vector<int> v1(3,6);
cout << v1.size() << endl;
}
2.max_size
该接口可以返回vector最大可以存放的元素个数(演示代码为32位下的情况)
vector<int> v1(3,6);
cout << v1.max_size() << endl;
3.resize
该接口可以调整vector容量的大小
参数n的意思是:调整容器的大小,使其包含 n 个元素
如果 n 小于当前容器大小(size),则内容将减少到其前 n 个元素,删除超出此部分的元素
如果 n 大于当前容器大小(size),则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val ,则新元素将初始化为 val 的副本,否则,它们将进行值初始化
如果 n 同时大于当前容器容量(capacity),则会自动重新分配空间
vector<int> v1(3,6);
v1.resize(4, 1);
v1.resize(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
4.capacity
该接口可以返回当前为 vector 分配的存储空间的大小
capacity不一定等于size,,它可以相等或更大
当capacity耗尽并需要更多容量时,vector会自动扩展(重新分配存储空间)
vector<int> v1(3, 6);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
v1.resize(4);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
5.empty
该接口可以判断容器是否为空,如果为空,则返回true,否则返回false
vector<int> v1;
vector<int> v2(3, 6);
cout << v1.empty() << endl;
cout << v2.empty() << endl;
6.reserve
该接口可以自行改变容量的大小(capacity)
在学习reserve之前我们先来看一下编译器自动扩容的规则:
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making v grow:\n";
for (size_t i = 0; i < 100; i++)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
我们看数据可以知道自动扩容是1.5倍扩容,并且做了向上取整
**************************************************************************************************************
g++环境下是两倍扩容:
**************************************************************************************************************
我们接着来看看reserve的定义:
reserve接口可以请求一个足够的且最少的空间,这也就意味着当我们给出n大小,真实开辟空间的大小不一定是n,但是开辟空间的大小一定能够容纳下n个数据
如果我们知道了要扩容的大小的话,我们就可以直接用reserve,这样接可以减少扩容的次数,提高程序运行的效率:
size_t sz;
vector<int> v;
v.reserve(100);
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making v grow:\n";
for (size_t i = 0; i < 100; i++)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
我们将vector中的reserve和string中的reserve比较一下:
通过比较我们可以发现:string中的reserve在特殊的情况下会有不具有约束力的缩容的现象(具体情况取决于代码实现的过程),但是缩容绝对不会删除已经存在的数据。也就是说如果存在缩容的现象,那么缩容最多缩到size的大小
而vector中的reserve不会存在缩容的现象
我们来验证一下:
void test_vector3()
{
vector<int> v(10, 1);
v.reserve(20);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.reserve(15);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.reserve(5);
cout << v.size() << endl;
cout << v.capacity() << endl;
}
可以看到没有存在缩容的现象
**************************************************************************************************************
GCC下也不会缩容
**************************************************************************************************************
7.shrink_to_fit函数
该接口可以减少capacity的大小,让其大小去适应size的大小。
需要注意的是,该接口的请求不是固定的,由编译器实现自由优化
vector<int> v(10, 1);
v.reserve(20);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.shrink_to_fit();
cout << v.size() << endl;
cout << v.capacity() << endl;
3.vector中与修改有关的接口
vector中与修改有关的接口如下所示:
1.assign
该接口可以为vector指定新的内容,同时删除之前已经存在的数据
vector<int> v(3, 6);
v.assign(6, 8);
for (auto ch : v)
{
cout << ch << " ";
}
2.push_back和pop_back
这个接口就很常规了,就是尾插和尾删,就不做过多的解释了
注意:vector不支持头插和头删,而string是支持的
3.insert
该接口可以实现在指定位置插入数据。需要注意vector中的insert不支持下标访问了,只支持迭代器访问
vector<int> v(10,1);
v.insert(v.begin(), 0);
for (auto ch : v)
{
cout << ch << " ";
}
cout << endl;
v.insert(v.begin() + 1, 6);
for (auto ch : v)
{
cout << ch << " ";
}
cout << endl;
注意:我们要尽量减少使用insert和erase,因为每次使用都需要移动数据,这会大大降低程序的运行效率
4.erase
该接口可以实现删除指定位置的数据。该接口的使用方式和insert差不多,就不做过多的解释了
5.swap
该接口可以实现交换数据的功能,也无须多言
6.clear
该接口可以删除vector中的所有数据,将size改为0。但是clear不会改变capacity
vector<int> v(3, 6);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.clear();
cout << v.size() << endl;
cout << v.capacity() << endl;
emplace系列暂时不需要管,后面才会学习到,它的功能和insert类似
我们需要注意:vector是不支持流插入和流提取的
string中支持流插入和流提取的原因是它是比较固定的,字符都是一个接一个的打印,直到遇到
\0才中止。
而vector的情况就比较特殊,所以不支持。我们要在vector中插入一个数据其实也很简单:
vector<int> v(3, 6);
v.clear();
int x;
cin >> x;
v.push_back(x);
输入多个数据也很简单:
vector<int> v(10, 0);
for (size_t i = 0; i < 10; i++)
{
cin >> v[i];
}
我们再来思考一下 vector<char> v 能否替代 string s 呢?
答案是不能的,string后面会有\0,而vector后面是没有\0的。因为string里面包含\0,它就能够很好的兼容C语言
vector中加\0是不能的,因为vector的类型不止有char,各个类型中的\0含义是不一样的
而且只有字符能实现连贯的赋值和初始化,如"abcdefg";而整型连贯赋值就会导致含义不明了,假设我们要给五个变量1,2,3,4,5,连续输入结果为:"12345"。此时就会混淆
结尾
那么有关vector使用的基础内容到此就差不多结束了,更多的细节可以去官网查阅vector的文档。希望该文章可以给你带来帮助,谢谢您的浏览!!!