vector的简单的用法介绍
对于我们库里面的vector。被包含在一个<vector>的头文件中
vector 是 C++ 标准模板库(Standard Template Library, STL)中的一个非常重要和常用的容器。它提供了一种动态数组的实现,允许你在运行时动态地增加或减少元素的数量,同时保留元素的顺序。vector 内部通过连续的内存空间来存储元素,这意呀着它可以快速地访问任何位置的元素(通过索引),但向vector中插入或删除元素(尤其是在非末尾位置)时可能会相对较慢,因为可能需要重新分配内存并移动元素以保持连续性。
提供了一个模版和一个内存池。 模版是用来解决我们的vector里面能存的不止一种数据类型。
基本特性
- 动态数组:vector 能够根据需要自动调整其大小以存储新元素。
- 连续内存:vector 中的元素在内存中连续存储,这使得访问元素非常高效(时间复杂度为 O(1))。
- 随机访问:支持通过索引直接访问任意位置的元素。
- 自动内存管理:自动处理内存分配和释放,减少了内存泄漏的风险。
- 迭代器:提供了迭代器来遍历容器中的元素。
迭代器:
begin 和end 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置 的iterator/const_iterator
rbegin和rend :获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的 reverse_iterator
提供的接口:
基础的构造函数:
我们按照顺序依次举例:
cpp
std::vector<int> first; // empty vector of ints
std::vector<int> second (4,100); // four ints with value 100
std::vector<int> third (second.begin(),second.end()); // iterating through second
std::vector<int> fourth (third); // a copy of third
第一个是不带参数的初始化。第二个是 存四个100 的值进入数组
第三个是迭代器初始化,第四个是一个拷贝构造
一些常用的接口
size()
size这个函数 会返回我们数的大小,有多少个有效数据元素。
用法:vector<int> s ; int size=s.size();
resize:
这个函数是用来调整数组的大小的,这个时候就分几种情况了
如果 n 小于当前容器大小,则内容将减少到前 n 个元素,删除超出的元素(并销毁它们)。 如果 n 大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val,则新元素将被初始化为 val 的副本,否则,它们将被值初始化。 如果 n 也大于当前容器容量,则会自动重新分配已分配的存储空间。 请注意,此函数通过插入或删除容器中的元素来更改容器的实际内容。
用法:
cpp
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
// set some initial content:
for (int i=1;i<10;i++) myvector.push_back(i);
myvector.resize(5);
myvector.resize(8,100);
myvector.resize(12);
std::cout << "myvector contains:";
for (int i=0;i<myvector.size();i++)
std::cout << ' ' << myvector[i];
std::cout << '\n';
return 0;
}
capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。 这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义 的。vs是PJ版本STL,g++是SGI版本STL。
cpp
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
// set some content in the vector:
for (int i=0; i<100; i++) myvector.push_back(i);
std::cout << "size: " << (int) myvector.size() << '\n';
std::cout << "capacity: " << (int) myvector.capacity() << '\n';
std::cout << "max_size: " << (int) myvector.max_size() << '\n';
return 0;
}
判断是否是是空。
用法:
cpp
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
int sum (0);
for (int i=1;i<=10;i++) myvector.push_back(i);
while (!myvector.empty())
{
sum += myvector.back();
myvector.pop_back();
}
std::cout << "total: " << sum << '\n';
return 0;
}
push_back
这个接口的功能是在vector的末尾插入我们的新元素,尾插的功能。
例子:
cpp
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
int myint;
std::cout << "Please enter some integers (enter 0 to end):\n";
do {
std::cin >> myint;
myvector.push_back (myint);
} while (myint);
std::cout << "myvector stores " << int(myvector.size()) << " numbers.\n";
return 0;
}
pop_back
是将我们的vector的末尾的元素弹出,尾删的功能。
用法:
cpp
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
int sum (0);
myvector.push_back (100);
myvector.push_back (200);
myvector.push_back (300);
while (!myvector.empty())
{
sum+=myvector.back();
myvector.pop_back();
}
std::cout << "The elements of myvector add up to " << sum << '\n';
return 0;
}
pop_back和push_back 是这两个接口的功能相对,对我们的数据进行出入。
reserve
reserve这个单词有存储的意思,而我们的这个就是进行空间的开辟的,我们可以提前开辟我们的数组vector的空间 。
要求vector的容量至少足以包含 n 个元素。
如果 n 大于当前的容量,该函数会导致容器重新分配其存储空间,将其容量增加到 n(或更大)。 在所有其他情况下,函数调用不会导致重新分配,并且向量容量不受影响。 此函数对向量大小没有影响,也不能改变其元素。
对于我们的n大于当前的容量时我们的这里并没有给出肯定的规定,所以对于不同平台,得到的结果是不一样的,对于 :
vs:按照1.5倍方式扩容。
linux:按照2倍方式扩容。
shrink_to_fit
是进行缩容,将我们的容量转换成我们的n,
该请求是不具有约束力的,并且容器实现可以自由地进行优化,并使向量的容量大于其大小。 这可能会导致重新分配,但对向量大小没有影响,也不能更改其元素。
shrink_to_fit是一个非强制性请求,标准库的实现可以选择忽略这个请求。
因此,在VS和Linux下,std::vector的shrink_to_fit是否实现缩容取决于具体的标准库实现和当前的内存状态。如果你需要精确控制内存使用,可能需要考虑其他数据结构或内存管理策略。
对于我们的find他不是我们的vector的成员函数,而是包含在我们的头文件《algorithm》里面;
用法:
insert和erase分别是进行我们的在指定位置插入和删除。
cpp
// inserting into a vector
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector (3,100);
std::vector<int>::iterator it;
it = myvector.begin();
it = myvector.insert ( it , 200 );
myvector.insert (it,2,300);
// "it" no longer valid, get a new one:
it = myvector.begin();
std::vector<int> anothervector (2,400);
myvector.insert (it+2,anothervector.begin(),anothervector.end());
int myarray [] = { 501,502,503 };
myvector.insert (myvector.begin(), myarray, myarray+3);
std::cout << "myvector contains:";
for (it=myvector.begin(); it<myvector.end(); it++)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
cpp
// erasing from vector
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
// set some values (from 1 to 10)
for (int i=1; i<=10; i++) myvector.push_back(i);
// erase the 6th element
myvector.erase (myvector.begin()+5);
// erase the first 3 elements:
myvector.erase (myvector.begin(),myvector.begin()+3);
std::cout << "myvector contains:";
for (unsigned i=0; i<myvector.size(); ++i)
std::cout << ' ' << myvector[i];
std::cout << '\n';
return 0;
}
swap函数,我们的vector提供了一个交换函数,但是我们的库里面也提供了一个交换的swap函数。
vector的成员函数swap用于交换两个std::vector实例的内容。
- 它比使用std::swap可能更高效,因为它可以直接访问std::vector的内部数据结构和资源,而无需通过函数模板的间接性。
- 在C++11及更高版本中,std::vector的swap成员函数通常被优化以利用移动语义(如果可能),这可以显著提高交换操作的效率。
- 重要的是要注意,std::vector的成员函数swap是公开的,并且可以被直接调用,但标准库中的std::swap也设计为在可能的情况下调用它。
std::swap是一个非成员函数模板,定义在头文件<algorithm>中。
- 它能够对任何支持赋值和复制构造的类型进行交换。
- 对于自定义类型,如果标准库的std::swap实现不够高效(例如,没有使用移动语义),则可以提供特化的swap函数(作为友元函数或自由函数)来优化交换过程。
- 当用于std::vector时,std::swap会调用std::vector的成员函数swap(如果存在并可见),因为对于容器类型,标准库中的std::swap通常只是转发调用到容器类自己的swap成员函数。
区别
- 调用方式:std::swap是一个非成员函数模板,而std::vector::swap是std::vector的一个成员函数。
- 效率:对于std::vector,两者在效率上通常没有区别,因为std::swap会调用std::vector::swap。然而,对于其他类型,特别是自定义类型,提供优化的swap成员函数或特化的std::swap函数可以显著提高交换操作的效率。
- 可见性和可访问性:std::vector::swap是std::vector的公有成员函数,可以直接调用。而std::swap是一个通用工具,可以用于任何支持交换的类型。
clear
clear()函数的作用是移除std::vector中的所有元素,这意味着这些元素会被销毁(如果有必要的话,比如它们是动态分配的对象)。
执行clear()后,std::vector的大小(即其中元素的数量)变为0,但容器本身仍然存在。
调用clear()时,并不保证std::vector的容量会发生变化。也就是说,虽然所有元素都被移除了,但std::vector仍然可能保留其原有的存储空间,以便将来添加新元素时不需要立即重新分配。
cpp
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 假设此时vec的容量大于其大小
vec.clear(); // 移除所有元素,但容量可能不变
// 使用swap技巧来减少容量
std::vector<int>().swap(vec); // 创建一个空的vector并与其进行swap
// 现在vec的大小和容量都应该是0
}
operate[]和at
我们的vector 之所以能随机访问我们的数据元素,也是因为我们的vector它重载了我们的[]。
vector 是一种非常常用的容器,它提供了动态数组的功能。std::vector 提供了两种主要的元素访问方式:operator[] 和 at()。尽管它们都可以用来访问 vector 中的元素,但它们在错误处理和性能上存在一些关键的区别
-
operator[]
-
访问方式:operator[] 通过索引(位置)直接访问 vector 中的元素。索引类型通常是 size_t(无符号整数),表示元素的位置。
-
错误处理:operator[] 不进行边界检查。如果索引超出 vector 的当前范围(即,小于0或大于等于 vector 的 size()),则行为是未定义的(通常会导致程序崩溃或数据损坏)。
-
性能:由于不进行边界检查,operator[] 的性能通常比 at() 要好。
-
at()
-
访问方式:at() 也是通过索引来访问 vector 中的元素,但其索引类型与 operator[] 相同(通常是 size_t)。
-
错误处理:at() 函数进行边界检查。如果索引超出 vector 的当前范围,它会抛出一个 std::out_of_range 异常。
-
性能:由于需要进行边界检查,at() 的性能通常比 operator[] 要差一些。但在需要确保索引有效性的场合,使用 at() 是更安全的选择。
vector的一个问题-----迭代器失效
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了 封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的 空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃)。
插入(insert)或删除(erase)操作:
- 当在vector的迭代器指向的元素之前或之后插入新元素时,由于vector可能需要增加容量(即重新分配内存并复制现有元素),这会导致所有迭代器、指针和引用失效,因为它们可能不再指向正确的内存位置。
- 使用erase删除元素时,指向被删除元素的迭代器以及指向被删除元素之后所有元素的迭代器都会失效。但是,指向被删除元素之前元素的迭代器仍然有效。
cpp
#include <iostream>
using namespace std;
#include <vector>
int main()
{
int a[] = { 1, 2, 3, 4 };
vector<int> v(a, a + sizeof(a) / sizeof(int));
// 使用find查找3所在位置的iterator
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
// 删除pos位置的数据,导致pos迭代器失效。
v.erase(pos);
cout << *pos << endl; // 此处会导致非法访问
return 0;
}
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代 器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效 了。
重新分配(reserve和resize)
:
- reserve(size_t n)操作增加容器的容量但不改变其大小,因此不会导致迭代器失效(除非之前已经是最大容量且需要重新分配)。
- resize(size_t n, T val = T())操作可以改变容器的大小。如果新的大小大于当前大小,并且容器需要增加容量来容纳更多的元素,则所有迭代器、指针和引用都可能失效。如果新的大小小于当前大小,则指向被删除元素的迭代器以及指向这些元素之后所有元素的迭代器都会失效。
- 完全重新分配:在某些情况下,如vector的大小增长到超过其当前容量的某个阈值时,vector可能会选择重新分配其内部数组以容纳更多的元素。这通常发生在插入操作之后,但也可能发生在其他导致容量增加的操作中。重新分配后,所有指向旧内存的迭代器、指针和引用都会失效。
使用swap或assign:
- 使用swap成员函数交换两个vector的内容时,指向这两个vector元素的迭代器、指针和引用都会失效,因为它们的内存位置已经发生了交换。
- 使用assign成员函数重新分配容器中的元素时,所有迭代器、指针和引用都会失效,因为容器的内部状态已经发生了彻底改变。
cpp
#include <iostream>
using namespace std;
#include <vector>
int main()
{
vector<int> v{1,2,3,4,5,6};
auto it = v.begin();
// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
// v.resize(100, 8);
// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
// v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
// v.insert(v.begin(), 0);
// v.push_back(8);
// 给vector重新赋值,可能会引起底层容量改变
v.assign(100, 8);
/*
出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
空间,而引起代码运行时崩溃。
解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
赋值即可。
*/
while(it != v.end())
{
cout<< *it << " " ;
++it;
}
cout<<endl;
return 0;
}
为了避免迭代器失效带来的问题,你可以采取以下策略:
- 在进行可能导致迭代器失效的操作之前,先记录下你需要的信息(如索引或值)。
- 在修改容器后,重新获取或计算所需的迭代器。
- 考虑使用支持动态数组但不涉及迭代器失效的容器,如std::deque(双端队列),它在插入和删除操作时通常不会导致迭代器失效(尽管在某些极端情况下也可能失效)。
- 在可能的情况下,使用基于索引的访问(如operator[]或at()),因为索引在重新分配后仍然有效(只要索引值在新的容器大小范围内)。然而,这要求你知道要访问的元素的索引,并且通常不适用于需要遍历容器的场景。