vector
std::vector是一个动态数组,它在内存中以连续的储存元素。与静态数组相比,std::vector允许在运行时动态调整大小,而无需手动管理内存。
内存管理
std::vector维护了两个重要的状态信息:容量(capacity)和大小(size)。
容量(capacity):表示当前vector分配内存的空间大小。
大小(size):vector 当前包含的元素数量。
当向vector中添加元素时,如果元素数量(size)超过容量(capacity),就需要进行内存分配。
内存分配和释放是std::vector内部的重要操作
当容量不足以容纳新元素时,std::vector会分配一块新的内存空间,将原有元素复制到新的内存中,然后释放原内存。这个过程确保了元素的连续存储。
动态扩容策略:
为了提高性能,std::vector采用了一种称为“指数增长(exponential growth
)”的策略进行动态扩容。
当需要进行扩容时,std::vector通常会将容量翻倍,以避免频繁的内存分配操作,从而减少系统开销。
这种指数增长策略确保了平均情况下的插入操作具有常数时间复杂度,而不是线性时间复杂度。
随机访问和迭代器:
std::vector提供了高效的随机访问能力,即通过索引直接访问元素。
这是因为元素在内存中是连续存储的,通过简单的指针运算即可实现 O(1) 时间复杂度的访问。
性能考虑:
std::vector在许多场景下表现出色,但需要注意的是,在频繁插入或删除元素的情况下,std::vector可能不是最佳选择,因为这样的操作可能触发频繁的动态扩容,导致性能下降。
在这种情况下,考虑使用 std::deque或 std::list这样的容器,它们对插入和删除操作有更好的性能。
vector的基本用法
#include <iostream>
#include <vector>
int main() {
// 创建一个空的 vector
std::vector<int> myVector;
// 使用初始化列表创建 vector
std::vector<int> myVector2 = {1, 2, 3, 4, 5};
// 在尾部插入元素
myVector.push_back(42);
// 使用下标访问元素
std::cout << "First element: " << myVector[0] << std::endl;
// 打印 vector 的大小
std::cout << "Size of myVector: " << myVector.size() << std::endl;
return 0;
}
vector工作原理
C++中vector的数组内存通常是在堆上分配的。
当创建一个vector
对象时,对象本身(即vector
的控制结构,包括指向数据的指针、大小和容量等)通常存储在栈上(如果是局部变量)或其他存储区(如全局/静态存储区),但实际的元素数据是在堆上分配的。
这种设计允许vector
在运行时动态增长和收缩,因为堆是用于动态内存分配的区域,没有固定的大小限制(除了可用内存的物理限制)。
当调用vector
的push_back
等方法时,vector
可能会重新分配其底层的动态数组以适应新元素。这通常涉及申请新的更大的内存块,复制现有元素到新内存,添加新元素,然后释放旧的内存块。在C++
官方实现的vector
中,这种动态内存管理通常是通过分配器来完成的,vector
使用一个默认的分配器std::allocator
,它封装了动态内存分配函数,如new
和delete
。开发者也可以为vector
提供自定义的分配器,以适应特定的内存分配策略。
图例说明如下:
- 虚线以上的内存为栈内存,虚线以下的内存为堆内存
- 红色区域为
vector
对象控制结构存储的位置 - 紫色区域和蓝色区域为存储元素的数组的位置, 其中紫色区域表示已经使用, 蓝色区域表示未使用
有一个vector<int> v
对象,其控制结构存储在在了栈上
size:数组已使用的大小
capacity:数组的容量
data pointer:数组首地址
最左边表示初始时刻的堆栈状态, 某时刻调用v.push_back(2)
, 检查发现此操作不会超出容量上限, 因此在中间的堆栈示意图中插入了2, 并更新控制结构中的size = 2
, 下一时刻调用v.push_back(3)
, 此时检查发现此操作要求的容量不足, 因此需要重新在堆内存申请容量为4的内存空间, 如最右边的示意图所示, 并且复制原来的内容到新的地址, 完成此操作后可以丢弃原来的堆内存空间, 并插入3。
实现vector
Vector
类包含了构造函数、析构函数、拷贝构造函数、拷贝赋值操作符、添加元素、获取元素个数、获取容量、访问元素的功能。
#include<iostream>
#include<string>
#include<sstream>
#include<stdlib.h>
#include <algorithm>
#include <stdexcept>
template<typename T>
class Vector
{
private:
T* elements;//指向动态数组的指针
size_t capacity;//数组的容量
size_t size;//数组元素的个数
public:
//构造函数
Vector()
:elements(nullptr)
, capacity(0)
, size(0)
{}
/*
初始化 Vector 对象,其中 elements 是指向 T 类型动态数组的指针,初始时没有分配内存(nullptr);
capacity 表示数组的容量,初始为 0;
size 表示数组中当前元素的数量,初始也为 0。
*/
//析构函数,当 Vector对象被销毁时,析构函数释放 elements 指向的动态分配的内存。
~Vector()
{
delete[] elements;
}
//拷贝构造
Vector(const Vector& other)
:capacity(other.capacity)
, size(other.size)
{
elements = new T[capacity];
std::copy(other.elements, other.elements + size, elements);//std::copy的用法 是一个标准库算法,通常用于从一个容器复制元素到另一个容器。
//std::copy需要三个参数:两个指定要复制的元素范围的迭代器(起始迭代器和结束迭代器),以及一个指向目标位置开始的迭代器。 指针也是一种天然的迭代器
}
/*
用于创建一个新的 Vector 对象,它是另一个 Vector 对象的副本;
分配与原始 Vector 相同的容量;
将原始 Vector 中的元素复制到新对象中
*/
//拷贝赋值操作符
Vector &operator=(const Vector& other)
{
if (this != &other) {
delete[] elements;
capacity = other.capacity;
size = other.size;
elements = new T[capacity];
std::copy(other.elements, other.elements + size, elements);
}
}
/*
允许将一个 Vector 对象的值赋给另一个 Vector 对象;
首先检查自赋值的情况,如果不是自赋值,则释放当前对象的内存;
分配新内存,并复制所有元素;
返回对当前对象的引用。
*/
//push_back函数
void push_back(const T& value)
{
if (size == capacity) {
reserve(capacity == 0 ? 1 : 2 * capacity);
}
elements[size++] = value;
}
/*
在 Vector 的末尾添加一个新元素;
如果当前没有足够的容量来存储新元素,则通过 reserve 函数扩展数组的容量;
将新元素添加到数组的末尾,并递增 size。
*/
//getsize和getCapacity
size_t getSize() const {
return size;
}
size_t getCapacity() const {
return capacity;
}
//访问数组中的元素(需要注意的是, 这里返回的是引用, 对应用的修改也会反映在Vector上)
T &operator[](size_t index) {
// 检查索引是否越界
if (index >= size) {
throw std::out_of_range("Index out of range");
}
return elements[index];
}
const T& operator[](size_t index) const {
if (index >= size) {
throw std::out_of_range("index out of range");
}
return elements[index];
}
//在指定位置插入元素
void insert(size_t index, const T& value) {
if (index > size) {
throw std::out_of_range("Index out of range");
}
if (size == capacity) {
reserve(capacity == 0 ? 1 : capacity * 2);
}
for (size_t i = size; i > index; --i) {
elements[i] = elements[i - 1];
}
elements[index] = value;
++size;
}
/*
在 Vector 的指定位置 index 插入一个元素 value;
如果 index 大于 size,则抛出 std::out_of_range 异常;
如果当前没有足够的容量来存储新元素,则通过 reserve 函数扩展数组的容量;
将 index 之后的所有元素向后移动一个位置,为新元素腾出空间;
将新元素放置在 index 位置;
增加 Vector 的 size。
*/
// 删除数组末尾的元素
void pop_back() {
if (size > 0) {
--size;
}
}
//清空数组
void clear() {
size = 0;
}
//使用迭代器遍历数组的开始位置
T* begin() {
return elements;
}
//使用迭代器遍历数组的结束位置
T* end() {
return elements + size;
}
//只读
const T* begin() const {
return elements;
}
const T* end() const {
return elements + size;
}
/*
begin 和 end 函数提供了遍历 Vector 的能力;
非 const 版本返回指向 elements 的指针,可以用来修改 Vector 中的元素;
const 版本返回 const 指针,用于只读访问;
begin 返回指向第一个元素的指针;
end 返回指向最后一个元素之后的位置的指针,通常用于表示范围的结束。
*/
//打印
void printElements() const {
for (size_t i = 0; i < size; ++i) {
std::cout << elements[i] << " ";
}
std::cout << std::endl;
}
private:
//扩容操作
void reserve(size_t newCapacity)
{
if (newCapacity > capacity)
{
T* newElements = new T[newCapacity];
std::copy(elements, elements + size, newElements);
delete[] elements;
elements = newElements;
capacity = newCapacity;
}
}
};
int main()
{
Vector<int> myvector;
//包含 N 行输出,不同命令需要给出明确的反馈
int N;
std::cin >> N;
getchar();
std::string line;
for (int i = 0; i < N; i++)
{
//读取整行
std::getline(std::cin, line);
std::istringstream iss(line);
std::string command;
iss >> command;
if (command == "push")
{
int value;
iss >> value;
myvector.push_back(value);
}
else if(command == "print")
{
if (myvector.getSize() == 0) {
std::cout << "empty" << std::endl;
continue;
}
myvector.printElements();
}
else if (command == "size")
{
std::cout << myvector.getSize() << std::endl;
}
else if(command == "get")
{
int index;
iss >> index;
std::cout << myvector[index] << std::endl;
}
else if (command == "insert")
{
int index, value;
iss >> index >> value;
myvector.insert(index, value);
}
else if (command == "pop")
{
myvector.pop_back();
}
else if (command == "iterator")
{
if (myvector.getSize() == 0)
{
std::cout << "empty" << std::endl;
continue;
}
for (auto it = myvector.begin(); it != myvector.end(); ++it)
{
std::cout << *it << " ";
}
std::cout << std::endl;
}
else if (command == "foreach")
{
if (myvector.getSize() == 0)
{
std::cout << "empty" << std::endl;
continue;
}
for (const auto& element : myvector)
{
std::cout << element << " ";
}
std::cout << std::endl;
}
else if (command == "clear")
{
myvector.clear();
}
}
return 0;
}
/*
输入示例
15
push 20
push 30
push 40
print
insert 0 10
size
print
get 1
pop
print
iterator
foreach
clear
size
print
输出实例
20 30 40
4
10 20 30 40
20
10 20 30
10 20 30
10 20 30
0
empty
*/