vector 是 C++ 标准库中的一个动态数组容器(Sequence Container),它可以自动管理内存大小,可以在运行时根据需要动态增长或缩小。它是一个非常常用且强大的容器,用于存储一系列元素。可以简单的认为,vector是一个能够放任意类型的动态数组。
下面详细介绍 vector
的使用方法,并提供相应的代码案例。
1,基本函数实现
1.1 构造函数
构造函数是初始化一个数组。vector本质是类模板,可以存储任何类型的数据。vector在声明前需要加上数据类型,而vector则通过模板参量设定类型。
vector():创建一个空vector
vector(int nSize):创建一个vector,元素个数为nSize
vector(int nSize,const t& t):创建一个vector,元素个数为nSize,且值均为t
vector(const vector&):复制构造函数
vector(begin,end):复制[begin,end)区间内另一个数组的元素到vector中
示例:
vector<int> vec; // 创建一个空数组vec
vector<int> vec(1, 2, 3, 4, 5, 6);//vec中的内容为1, 2, 3, 4, 5, 6
vector<int> vec(4); // 开辟四个空间,值默认为0
vector<int> vec(5, 4); //创建5个值为4的数组
vector<int> vec(a);//声明并用a向量初始化vec向量
vector<int> vec(a.begin(), a.end()); // 将a的值从头开始到尾复制
vector<int> vec(a.rbegin(), a.rend()); // 将a的值从尾到头复制
int a[5]={1,2,3,4,5};
vector<int> vec(a,a+5);//将a数组的元素用来初始化vector向量
vector<int> vec(&a[1],&a[4]);//将a[1]-a[4]范围内的元素作为vec的初始值
1.2 增加函数
即向vector中插入元素。其中emplace_back的效果和push_back一样,都是尾部插入元素。二者的差别在于底部实现的机制不同;push_back是将这个元素拷贝或移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而emplace_back在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程,所以emplace_back的速度更快。
void push_back(const T& x):向量尾部增加一个元素X
iterator insert(iterator it,const T& x):向量中迭代器指向元素前增加一个元素x
iterator insert(iterator it,int n,const T& x):向量中迭代器指向元素前增加n个相同的元素x
iterator insert(iterator it,const_iterator first,const_iterator last):向量中迭代器指向元素前插入另一个相同类型向量的[first,last)间的数据
示例:
//在vector的末尾插入新元素
vec.push_back(1);
//在迭代器的前面插入新元素
vector<int>::iterator it;
it=vec.begin();
vec.insert(it,5);//在第一个元素前面插入5
//在vector中加入3个1元素,同时清除掉以前的元素
vec.assign(3,1);//现在vector中只有3个1
assgin修改vector,和insert操作类似,不过insert是从尾部插入,而assign则将整个vector改变。
1.3 删除函数
erase是通过迭代器删除某个或某个范围的元素,并返回下一个元素的迭代器。
iterator erase(iterator it):删除向量中迭代器指向元素
iterator erase(iterator first,iterator last):删除向量中[first,last)中元素
void pop_back():删除向量中最后一个元素
void clear():清空向量中所有元素
示例:
//删除最后一个元素
vec.pop_back();
//删除指定位置的元素
vec.erase(vec.begin());//删除第一个位置的元素值
//清除所有元素
vec.clear();
//判断该数组是否为空
vec.empty();
1.4 遍历函数
reference at(int pos):返回pos位置元素的引用
reference front():返回首元素的引用
reference back():返回尾元素的引用
iterator begin():返回向量头指针,指向第一个元素
iterator end():返回向量尾指针,指向向量最后一个元素的下一个位置
reverse_iterator rbegin():反向迭代器,指向最后一个元素
reverse_iterator rend():反向迭代器,指向第一个元素之前的位置
遍历数组示例:
//向数组一样利用下标进行访问
vector<int> a;
for(int i=0;i<a.size();i++){
cout<<a[i];
}
//利用迭代器进行访问
vector<int>::iterator it;
for(it=a.begin();it!=a.end();it++){
cout<<*it;
}
1.5 vector容量和大小
顾名思义,size表示当前有多少个元素,capacity是可容纳的大小,因为vector是顺序存储的,那么和数组一样,有一个初始容量,在vector里就是capacity。capacity必然大于等于size,每次扩容时会改变,具体大小和vector底层实现机制有关。
max_size 是可存储的最大容量,和实际的编译器,系统有关,使用的比较少。
empty就是判断vector是否为空,其实就是判断size是否等于0。定义vector时设定了大小,resize修改大小等操作,vector都不为空,clear后,size=0,那么empty判断就为空。
bool empty() const:判断向量是否为空,若为空,则向量中无元素
int size() const:返回向量中元素的个数
int capacity() const:返回当前向量所能容纳的最大元素值
int max_size() const:返回最大可允许的vector元素数量值
2,容器特性和属性
2.1 容器的特性
2.1.1,顺序序列(Sequential Container)
顺序容器中的元素按照严格的线性顺序排序。可以通过元素在序列中的位置访问对应的元素。
std::vector
是C++标准库中的一种顺序序列容器,这意味着它按照元素的顺序进行存储和访问。- 每个元素都有一个唯一的位置(索引),可以通过索引直接访问,同时支持迭代器用于循环访问。
2.1.2,动态数组(Dynamic Array)
支持对序列中的任意元素进行快速直接访问,甚至可以通过指针算述进行该操作。提供了在序列末尾相对快速地添加/删除元素的操作。
std::vector
是一个动态数组容器,它在内部使用动态分配的数组来存储元素。- 它能够动态调整数组的大小,可以在运行时根据需要增加或减少元素的数量,而无需预先指定数组的大小。
2.1.3,能够感知内存分配器的(Allocator-aware)
容器使用一个内存分配器对象来动态地处理它的存储需求。
std::vector
是 allocator-aware 的,这意味着它能够感知并与特定的内存分配器协同工作。- 使用者可以通过传递自定义的内存分配器(通常是一个模板参数),以实现对内存分配和释放过程的控制。
以下是一个简单的示例,演示了 std::vector
的这三个特性:
#include <iostream>
#include <vector>
int main() {
// 创建一个空的vector
std::vector<int> myVector;
// 添加元素
myVector.push_back(10);
myVector.push_back(20);
myVector.push_back(30);
// 遍历并输出元素
for (const auto& num : myVector) {
std::cout << num << " ";
}
return 0;
}
在这个例子中,std::vector
被用作顺序序列,存储了动态数组,并且它的内存管理是由C++标准库的分配器机制处理的。
2.2 容器的属性
下面介绍一下 std::vector
的基本属性,以及一些示例。
2.2.1 动态调整大小:
std::vector
是一个动态大小的容器,可以在运行时根据需要自动调整大小。这意味着你可以随时向vector
中添加或删除元素,而无需担心数组大小的固定性。它会自动处理内存管理。
示例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector; // 创建一个空的vector
myVector.push_back(1); // 添加元素
myVector.push_back(2);
myVector.push_back(3);
std::cout << "Vector的大小: " << myVector.size() << std::endl; // 输出vector的大小
return 0;
}
2.2.2 快速随机访问
std::vector
支持随机访问,即通过索引直接访问元素。这使得在访问和修改元素时效率非常高。 由于元素的连续存储(底层实现是数组),std::vector 支持快速的随机访问。你可以通过索引直接访问向量中的元素。
示例:
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// 随机访问元素
std::cout << "第二个元素是: " << names[1] << std::endl;
return 0;
}
2.2.3 动态内存管理
std::vector
负责管理其内部的动态内存,因此你无需手动分配或释放内存。当元素被添加或删除时,vector
会自动处理内存的分配和释放。当向量的大小超过其当前容量时,会动态分配更多的内存以容纳更多的元素。这可以确保在添加元素时不会频繁重新分配内存。
#include <iostream>
#include <vector>
int main() {
std::vector<double> prices;
// 添加元素
prices.push_back(10.5);
prices.push_back(20.0);
prices.push_back(15.75);
// 遍历元素并输出
for (const auto& price : prices) {
std::cout << "价格: " << price << std::endl;
}
return 0;
}
总结: std::vector
是C++中一个强大且灵活的容器,它提供了动态大小、随机访问、动态内存管理和丰富的功能。通过合理利用std::vector
,你可以更轻松地处理动态数据集合,而无需手动管理内存或担心固定数组大小的限制。除此之外,还有下面属性:
- 1,连续内存存储:std::vector中的元素在内存中是连续存储的,这意味着可以通过指针算术直接访问元素,并且对于许多算法来说,这种存储方式是高效的,有助于提高访问效率。
- 2,尾部插入和删除: 向量支持在尾部进行快速的元素插入和删除操作。
- 3,快速尾部操作: 由于元素的尾部插入和删除是快速的,std::vector 适用于需要在序列的末尾执行大量操作的场景。
- 4,STL 算法和迭代器支持: std::vector 与 C++ 标准模板库(STL)的其他部分无缝集成,可以使用算法和迭代器对向量进行操作。你可以使用迭代器来遍历
std::vector
中的元素。
2.3 如何获取vector的内存
在C++中,std::vector
是一个封装了动态数组的容器,它自动处理内存的分配和释放。要获取 std::vector
的底层内存地址,你可以使用 data()
成员函数,它返回指向容器第一个元素的指针。
以下是一个示例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector = {1, 2, 3, 4, 5};
// 获取底层内存地址
int* ptr = myVector.data();
// 输出每个元素及其内存地址
for (size_t i = 0; i < myVector.size(); ++i) {
std::cout << "元素 " << myVector[i] << " 的内存地址: " << &myVector[i] << std::endl;
}
// 输出整个vector的底层内存地址
std::cout << "Vector的底层内存地址: " << ptr << std::endl;
return 0;
}
请注意,通过 data()
获取的指针指向的是 vector 中第一个元素的地址。如果 vector 是空的,data()
返回的是一个合法但未定义的指针,因此在使用之前应确保 vector 不为空。
注意:一般情况下,直接使用 data()
获取 vector 的底层内存并进行操作可能不是一个良好的实践,因为 std::vector
提供了许多高级的函数和方法,可以更安全和方便地访问元素。直接访问内存可能会引发未定义的行为,特别是在修改元素时。
2.3.1 疑问:获取内存使用 myVector.data() 和 myVector[0]的区别是什么?
myVector.data()
和 myVector[0]
返回的是不同类型的指针,并且有不同的用途。
myVector.data()
:
myVector.data()
返回指向 vector 第一个元素的指针,是一个原始指针(T*
类型,其中T
是 vector 中元素的类型)。- 这个指针允许你直接访问 vector 的底层内存,但是需要小心使用,因为它没有边界检查和安全保障。
- 在对
data()
返回的指针进行操作时,务必确保 vector 不为空。
int* ptr = myVector.data();
// 对 ptr 进行直接内存操作
myVector[0]
:
myVector[0]
是 vector 的运算符重载,它返回 vector 中第一个元素的引用。- 这个引用允许你直接访问 vector 的第一个元素,并且通过引用操作进行修改。它提供了边界检查,确保你访问的是有效的元素。
int& ref = myVector[0];
// 对 ref 进行直接操作,修改会影响 vector 中的元素
选择使用的场景:
- 如果你需要直接访问 vector 中第一个元素的底层内存,并且要进行原始指针的操作,可以使用
myVector.data()
。 - 如果你只是想获取第一个元素的值,并且可能会进行修改,使用
myVector[0]
更直观且更安全,因为它提供了引用而不是裸指针,并且有边界检查。
3,C++ vector 底层实现机制
这里参考的是:https://c.biancheng.net/view/6901.html
STL 众多容器中,vector是最常用的容器之一,其底层所采用的数据结构非常简单,就只是一段连续的线性内存空间。
3.1 vector源码分析
通过分析vector容器的源码就可以看到,他就是使用三个迭代器来表示:
//_Alloc 表示内存分配器,此参数几乎不需要我们关心
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
...
protected:
pointer _Myfirst;
pointer _Mylast;
pointer _Myend;
};
其中,_Myfirst 指向的是 vector 容器对象的起始字节位置;_Mylast 指向当前最后一个元素的末尾字节;_myend 指向整个 vector 容器所占用内存空间的末尾字节。
下图演示了以上这 3 个迭代器分别指向的位置。
如上图所示,通过这 3 个迭代器,就可以表示出一个已容纳 2 个元素,容量为 5 的 vector 容器。
在此基础上,将 3 个迭代器两两结合,还可以表达不同的含义,例如:
- _Myfirst 和 _Mylast 可以用来表示 vector 容器中目前已被使用的内存空间;
- _Mylast 和 _Myend 可以用来表示 vector 容器目前空闲的内存空间;
- _Myfirst 和 _Myend 可以用表示 vector 容器的容量。
- 对于空的 vector 容器,由于没有任何元素的空间分配,因此 _Myfirst、_Mylast 和 _Myend 均为 null。
通过灵活运用这 3 个迭代器,vector 容器可以轻松的实现诸如首尾标识、大小、容器、空容器判断等几乎所有的功能,比如:
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
public:
iterator begin() {return _Myfirst;}
iterator end() {return _Mylast;}
size_type size() const {return size_type(end() - begin());}
size_type capacity() const {return size_type(_Myend - begin());}
bool empty() const {return begin() == end();}
reference operator[] (size_type n) {return *(begin() + n);}
reference front() { return *begin();}
reference back() {return *(end()-1);}
...
};
3.2 vector 扩大容量的本质
上面有说到果,vector作为容器有着动态数组的功能,当加入的数据大于vector容量(capacity)时会自动扩容,系统会自动申请一片更大的空间,把原来的数据拷贝过去,释放原来的内存空间。
另外需要指明的是,当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:
- 完全弃用现有的内存空间,重新申请更大的内存空间;
- 将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
- 最后将旧的内存空间释放。
这也就解释了,为什么 vector 容器在进行扩容后,与其相关的指针、引用以及迭代器可能会失效的原因。
由此可见,vector 扩容是非常耗时的。为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity>=size),以便后期使用。vector 容器扩容时,不同的编译器申请更多内存空间的量是不同的。以 VS 为例,它会扩容现有容器容量的 50%。
4,使用示例
std::vector
是一个非常有用的标准库容器,它提供了动态数组的功能,可以根据需要自动调整大小。以下是一些关于 std::vector
的基本示例:
4.1:引入头文件
首先,使用的话需要引入头文件 <vector>
#include <vector>
4.2:创建 vector 对象
直接使用vector
模板类来创建一个 vector 对象。可以创建存储特定类型元素的 vector,格式为: vector<数据类型> 名字
。例如:
vector<int> myVector; // 创建一个存储整数的 vector,名字为myVector
vector<char> myVector; // 创建一个存储字符的 vector,名字为myVector
vector<string> myVector; // 创建一个存储字符串的 vector,名字为myVector
……
4.3:创建 vector并添加元素
使用push_back()函数将元素添加到vector的末尾,默认且只能添加到末尾。
代码如下:
#include <iostream>
#include <vector>
int main() {
// 创建一个空的 vector
std::vector<int> numbers;
// 向 vector 中添加元素
numbers.push_back(1);
numbers.push_back(2);
numbers.push_back(3);
// 访问 vector 中的元素
std::cout << "Vector elements: ";
for (int i = 0; i < numbers.size(); ++i) {
std::cout << numbers[i] << " ";
}
std::cout << std::endl;
return 0;
}
打印结果如下:
Vector elements: 1 2 3
4.4:使用范围循环遍历 vector
代码如下:
#include <iostream>
#include <vector>
int main() {
// 创建一个 vector,并初始化
std::vector<std::string> fruits = {"Apple", "Banana", "Orange"};
// 也可以向 vector 中添加元素
fruits.push_back("pear");
// 使用范围循环遍历 vector
std::cout << "Fruits: ";
for (const auto& fruit : fruits) {
std::cout << fruit << " ";
}
std::cout << std::endl;
return 0;
}
结果如下:
Fruits: Apple Banana Orange pear
4.5:插入和删除元素
#include <iostream>
#include <vector>
int main() {
// 创建一个 vector,并初始化
std::vector<double> prices = {10.5, 20.3, 15.0};
// 直接在尾部插入元素
prices.push_back(88);
// 在指定位置插入元素
prices.insert(prices.begin() + 1, 25.8);
// 删除指定位置的元素
prices.erase(prices.begin() + 2);
// 打印 vector 元素
std::cout << "Prices: ";
for (const auto& price : prices) {
std::cout << price << " ";
}
std::cout << std::endl;
return 0;
}
结果:
Prices: 10.5 25.8 15 88
4.6:使用迭代器访问数组
除了直接使用for循环遍历访问数组之外,我们也可以利用迭代器将容器中数组输出。
首先声明一个迭代器,vector<int>::iterator it; 来访问vector容器,目的是遍历或者指向vector容器的元素。
#include <iostream>
#include <vector>
int main() {
// 创建一个 vector,并初始化
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用迭代器访问 vector 中的元素
std::cout << "Vector elements: ";
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
结果如下:
Vector elements: 1 2 3 4 5