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 步:

  1. 完全弃用现有的内存空间,重新申请更大的内存空间;
  2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
  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

  

C++ ——vector数组笔记的更多相关文章

  1. c++中的array数组和vector数组

    我觉得实验一下会记得比较牢,话不多直接上代码. 下面是array数组,感觉用的不多. //cpp 风格数组 array #include <iostream> #include <a ...

  2. c++ vector数组的定义使用

    /* *********************************************** Author :guanjun Created Time :2017/3/18 13:32:52 ...

  3. 2.vector数组

    创建vector数组 vector<,,,, }; 尾部插入 ; i < ; i++) { myint.push_back(i); } 读取头部和尾部 cout << myin ...

  4. c++ vector数组的使用

    介绍: 首先我们要明白以下几点: 1.vector数组是一个能存放任意数据类型(类,结构,普通变量类型等)的动态数组!,在数据结构中就相当于顺序储存的线性表,寻找元素非常快,但是插入元素的时间却很大( ...

  5. vector数组的相关知识

    Vector 类实现了一个动态数组.和 ArrayList 很相似,但是两者是不同的: Vector 是同步访问的. Vector 包含了许多传统的方法,这些方法不属于集合框架. Vector 主要用 ...

  6. 动态创建二维vector数组 C和C++ 及指针与引用的区别

    二维vectorvector<vector <int> > ivec(m ,vector<int>(n));    //m*n的二维vector 动态创建m*n的二 ...

  7. ArrayList , Vector 数组集合

    ArrayList 的一些认识: 非线程安全的动态数组(Array升级版),支持动态扩容 实现 List 接口.底层使用数组保存所有元素,其操作基本上是对数组的操作,允许null值 实现了 Randm ...

  8. UVa——1593Alignment of Code(string重定向+vector数组)

    UVA - 1593 Alignment of Code Time Limit: 3000MS   Memory Limit: Unknown   64bit IO Format: %lld & ...

  9. C++ 中vector数组的使用

    (1)头文件:#include<vector>.(2)创建vector对象: vector < 类型 > 名字;     例:vector<int> vec;(3) ...

  10. <<Vector Calculus>>笔记

    现在流行用Exterior Caculus, 所以个人觉得Matthews这本书有点过时了. 想学Vector Calculus的话,推荐<Vector Calculus, Linear Alg ...

随机推荐

  1. Codeforces-470 div2 C题

    C. Producing Snow time limit per test 1 second memory limit per test 256 megabytes input standard in ...

  2. django数据库事务操作celery任务注意事项

    from django.db import transaction from django.http import HttpResponseRedirect @transaction.atomic d ...

  3. 浅谈android的activity

    说道activity,大家可以说是熟悉的不能再熟悉,首先,先来个镇楼图, 个人觉得谷歌的这张图,比别的什么生命周期图都好;说下各个生命周期注意的: 1:onstart()时,activity可见; 2 ...

  4. ElasticSearch之线程池

    ElasticSearch节点可用的CPU核的数量,通常可以交给ElasticSearch来自行检测和判定,另外可以在``elasticsearch.yml`中显式指定.样例如下: node.proc ...

  5. 如何从零开始实现TDOA技术的 UWB 精确定位系统(3)

    ​ 这是一个系列文章<如何从零开始实现TDOA技术的 UWB 精确定位系统>第3部分. 重要提示(劝退说明): Q:做这个定位系统需要基础么?A:文章不是写给小白看的,需要有电子技术和软件 ...

  6. C语言汉诺塔递归算法实现

    这是个目录 一.什么是递归函数 1.先看一下一个递归的例子 2.递归的基本原理 二.汉诺塔问题 1.简要概括一下汉诺塔的故事 2.回到编程,汉诺塔问题主要就是解决这个问题: 3.怎么解决汉诺塔问题 要 ...

  7. 扩展中国剩余定理(Excrt)笔记

    扩展中国剩余定理(excrt) 本来应该先学中国剩余定理的.但是有了扩展中国剩余定理,朴素的 CRT 就没用了. 扩展中国剩余定理用来求解如下形式的同余方程组: \[\begin{cases} x \ ...

  8. Java并发(二十二)----wait notify的正确姿势

    开始之前先看看,sleep(long n) 和 wait(long n) 的区别: 1) sleep 是 Thread 的静态方法,而 wait 是 Object 的方法 2) sleep 不需要强制 ...

  9. Istio 为服务指定协议

    1.背景 Istio 默认支持代理所有 TCP 流量.包括 HTTP.HTTPS.gRPC 以及原始 TCP 协议.但为了提供额外的能力,比如路由和丰富的指标,必须确定协议.协议可以被自动检测或者手动 ...

  10. vue全屏

    <template> <div> <img src="../assets/fangda.png" @click="toggleFullscr ...