[C++语法]-vector(用法详解及实现)

1.vector的介绍及使用

1.1 vector的介绍

std::vector 是 C++ 标准库中的动态数组 ,可以自动管理内存 ,在运行时动态调整大小

1.2 vector的使用

1.2.1 vector的定义

|------------------------------------------------------------|--------------|
| 构造函数声明 | 接口说明 |
| vector()(重点) | 无参构造 |
| vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
| vector (const vector& x); (重点) | 拷贝构造 |
| vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |

演示代码:

cpp 复制代码
#include<iostream>
#include<vector>

using namespace std;

//自定义的输出函数,vector无法直接cout输出,这便是最基础的方法
void my_print(vector<int> nums)
{
	for (size_t i = 0; i < nums.size(); i++)
	{
		cout << nums[i]<<' ';
	}
	cout << endl;
}

int main()
{
	vector<int> v1;
	vector<int> v2(5, 2);//创建包含5个2的vector
	//若只有一个参数,则创建该参数个数的0
	vector<int> v3(v2);//拷贝构造,拷贝v2
	vector<int> v4 = v2;//同2,也是拷贝构造
	vector<int> v5 = { 1, 2, 3, 4, 5, 6, 7};//直接给vector赋值
	vector<int> v6(v5.begin() + 1, v5.end()-2);//{2,3,4,5}
	//输出
	my_print(v1);
	my_print(v2);
	my_print(v3);
	my_print(v4);
	my_print(v5);
	my_print(v6);
}

1.2.2 vector iterator 的使用

|-----------------|---------------------------------------------------------------------------|
| iterator的使用 | 接口说明 |
| begin + end(重点) | 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下 一个位置的iterator/const_iterato |
| rbegin + rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置 的reverse_iterator |

迭代器还没太学会的同学建议学一下

1.2.3 vector 空间增长问题

|--------------|-------------------|
| 容量空间 | 接口说明 |
| size | 获取数据个数 |
| capacity | 获取容量大小 |
| empty | 判断是否为空 |
| resize(重点) | 改变vector的size |
| reserve (重点) | 改变vector的capacity |

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2 倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是 根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。 reserve只负责开辟空间,如果确定知道需要用多少空间,
  • reserve可以缓解vector增容的代 价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size。

测试代码:

cpp 复制代码
#include<iostream>
#include<vector>

using namespace std;

//自定义的输出函数,vector无法直接cout输出,这便是最基础的方法
void my_print(vector<int> nums)
{
	for (size_t i = 0; i < nums.size(); i++)
	{
		cout << nums[i]<<' ';
	}
	cout << endl;
}

int main()
{
	vector<int> v1;
	cout <<"此时v1是否为空:" << v1.empty() << endl;//判断v1是否为空
	cout << "尾插数字1" << endl;
	v1.push_back(1);
	cout << "尾插数字2" << endl;
	v1.push_back(2);
	cout << "尾插数字3" << endl;
	v1.push_back(3);
	cout << "此时v1是否为空:" << v1.empty() << endl;//判断v1是否为空
	cout << "此时v1的v1.size():" << v1.size()<<endl;//v1的有效数据个数
	cout << "此时v1的v1.capacity():" << v1.capacity() << endl;//v1的容量大小
	cout << "插入后的v1:";
	my_print(v1);
	v1.resize(5);//将v1的有效数据个数改为5,不足5的部分默认用0填充
	cout << "resize(5)的v1:";
	my_print(v1);
	v1.resize(2);//将v1的有效数据个数改为2,超出2的部分直接删
	cout << "resize(2)的v1:";
	my_print(v1);
	//reserve改变vector的capacity,不改变有效数据
	v1.reserve(20);
	cout<<"reserve后v1的capacity:" << v1.capacity();
}

1.2.4 vector 增删查改

|-------------------|--------------------------------|
| vector 增删查改 | 接口说明 |
| push_back(重点) | 尾插 |
| pop_back (重点) | 尾删 |
| find | 查找。(注意这个是算法模块实现,不是vector的成员接口) |
| insert | 在position之前插入val |
| erase | 删除position位置的数据 |
| swap | 交换两个vector的数据空间 |
| operator[] (重点) | 像数组一样访问 |

push_back(const T& val)(重点)

  • 功能 :在 vector 的末尾添加一个元素(尾插)

  • 示例

cpp 复制代码
std::vector<int> v;

v.push_back(10);  // v: [10]
v.push_back(20);  // v: [10, 20]
v.push_back(30);  // v: [10, 20, 30]

// 也可以插入自定义类型
struct Point {
    int x, y;
};
std::vector<Point> points;
points.push_back({1, 2});
points.push_back(Point{3, 4});

pop_back()(重点)

  • 功能 :删除 vector 的最后一个元素(尾删)

  • 注意

    • 不返回被删除的元素

    • 如果 vector 为空,行为未定义(可能导致崩溃)

  • 时间复杂度:O(1)

  • 示例

cpp 复制代码
std::vector<int> v = {1, 2, 3, 4, 5};

v.pop_back();  // v: [1, 2, 3, 4]
v.pop_back();  // v: [1, 2, 3]

// 常见用法:删除末尾元素
while (!v.empty()) 
{
    v.pop_back();  // 逐个删除所有元素
}

find(算法,非成员函数)

  • 功能:在指定范围内查找特定值

  • 所在头文件<algorithm>

  • 返回值 :指向找到元素的迭代器 ,如果没找到则返回 end()

  • 时间复杂度:O(n)

  • 示例

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>  // 需要包含这个头文件

int main() 
{
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // 查找值为3的元素
    auto it = std::find(v.begin(), v.end(), 3);
    
    if (it != v.end()) 
    {
        std::cout << "找到3,位置在索引: " 
                  << std::distance(v.begin(), it) << std::endl;
        std::cout << "值是: " << *it << std::endl;
    } 
    else 
    {
        std::cout << "没找到" << std::endl;
    }
    
    // 查找不存在的值
    auto it2 = std::find(v.begin(), v.end(), 99);
    //查询到最后位置仍未找到
    if (it2 == v.end()) 
    {
        std::cout << "99不存在" << std::endl;
    }
    
    return 0;
}

insert(iterator position, const T& val)

  • 功能 :在指定位置之前插入一个元素

  • 参数

    1. position:插入位置的迭代器

    2. val:要插入的值

  • 返回值:指向新插入元素的迭代器

  • 时间复杂度:O(n)(可能需要移动元素)

  • 示例

cpp 复制代码
std::vector<int> v = {1, 2, 4, 5};

// 在索引2的位置(值为4)之前插入3
auto it = v.insert(v.begin() + 2, 3);
// v: [1, 2, 3, 4, 5]
// it指向新插入的3

// 在开头插入0
v.insert(v.begin(), 0);
// v: [0, 1, 2, 3, 4, 5]

// 在末尾插入6
v.insert(v.end(), 6);
// v: [0, 1, 2, 3, 4, 5, 6]

// 插入多个相同元素
v.insert(v.begin() + 2, 3, 100);
// 在索引2处插入3个100

// 插入迭代器范围
std::vector<int> source = {7, 8, 9};
//第一个参数是插入的位置,后两个参数是插入内容的开始和结尾
v.insert(v.end(), source.begin(), source.end());
// 将source的所有元素插入到v末尾

erase(iterator position)

  • 功能:删除指定位置的元素

  • 参数:要删除元素的迭代器

  • 返回值:指向被删除元素之后位置的迭代器

  • 时间复杂度:O(n)(可能需要移动元素)

  • 注意:删除操作会使迭代器失效,需要重新获取(后文会详细讲)

  • 示例

cpp 复制代码
std::vector<int> v = {1, 2, 3, 4, 5};

// 删除索引2的元素(值为3)
auto it = v.erase(v.begin() + 2);
// v: [1, 2, 4, 5]
// it指向原来的索引3(现在的索引2,值为4)

// 删除开头的元素
v.erase(v.begin());
// v: [2, 4, 5]

// 删除末尾的元素
v.erase(v.end() - 1);
// v: [2, 4]

范围删除:

cpp 复制代码
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7};

// 删除索引2到索引5(不包括5)的元素
auto it = v.erase(v.begin() + 2, v.begin() + 5);
// v: [1, 2, 6, 7]
// it指向原来的索引5(现在的索引2,值为6)

swap(vector& other)

  • 功能:交换两个 vector 的所有内容

  • 特点:快速,只交换内部指针,不复制元素

  • 时间复杂度:O(1)

  • 示例

cpp 复制代码
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6, 7};


std::swap(v1, v2); 
// v1: [4, 5, 6, 7]
// v2: [1, 2, 3]
// capacity也会交换

operator[](重点)

  • 功能:通过下标访问元素(像数组一样)

  • 特点

    • 不检查边界

    • 速度快

  • 时间复杂度:O(1)

  • 示例

cpp 复制代码
std::vector<int> v = {1, 2, 3, 4, 5};

// 读取元素
std::cout << v[0] << std::endl;  // 1
std::cout << v[2] << std::endl;  // 3

// 修改元素
v[1] = 20;      // v: [1, 20, 3, 4, 5]
v[3] = v[4] * 2;  // v[3] = 5 * 2 = 10

// 遍历
for (size_t i = 0; i < v.size(); ++i) {
    std::cout << v[i] << " ";
}

1.2.5 vector 迭代器失效问题。(重点)

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对 指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器 底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即:如果继续使用已经失效的迭代器,程序可能会崩溃)。

举个例子:你记得朋友住在1号房间,但酒店重新装修后,1号房间可能变成仓库了,你还按老地址去找就会出错!

对于vector可能会导致其迭代器失效的操作有:

1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、 assign、push_back等。

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;
}
  1. 指定位置元素的删除操作--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就认为该位置迭代器失效了。

这编译器事儿这么多?

严谨点儿总是没错的,养成好的编码习惯嘛。那问题来了!我们怎么防止迭代器失效呢?

就是及时更新迭代器,老的失效换成新的不就行了吗?

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    //方法1:接收返回值
    std::cout << "方法1:接收返回值" << std::endl;
    
    auto it = v.begin() + 1;  // 指向2
    std::cout << "原it指向: " << *it << std::endl;
    
    // 删除2,同时自动更新it指向3
    it = v.erase(it);
    std::cout << "删除后it自动指向: " << *it << std::endl;  // 3
    
    //方法2:重新获取
    std::cout << "\n方法2:重新获取" << std::endl;
    
    v = {1, 2, 3, 4, 5};
    size_t index = 1;  // 记住索引1(值2)
    
    // 删除索引1的元素(值2)
    v.erase(v.begin() + index);
    
    // 重新获取迭代器(但要注意元素已移动)
    it = v.begin() + index;  // 现在指向3
    std::cout << "重新获取后it指向: " << *it << std::endl;  // 3
    
    //特殊情况:push_back
    std::cout << "\n特殊情况:push_back" << std::endl;
    
    it = v.begin();  // 指向1
    
    // push_back不返回迭代器
    v.push_back(6);  // 可能重新分配内存
    
    // it可能失效,必须重新获取
    it = v.begin();  // 重新获取
    std::cout << "push_back后重新获取it指向: " << *it << std::endl;  // 1
    
    return 0;
}

学会了就给博主点个赞呗?(✪ω✪)

---------(如有问题,欢迎评论区提问)---------

相关推荐
安全二次方security²2 小时前
CUDA C++编程指南(7.15&16)——C++语言扩展之内存空间谓词和转化函数
c++·人工智能·nvidia·cuda·内存空间谓词函数·内存空间转化函数·address space
代码雕刻家2 小时前
4.3.多线程&JUC-多线程的实现方式
java·开发语言
L186924547822 小时前
Win 下 PCL部分函数析构崩溃问题总结
c++·计算机视觉·3d·pcl
梦6502 小时前
网络传输七层协议
开发语言·网络·php
南 阳2 小时前
Python从入门到精通day16
开发语言·python·算法
沉默-_-2 小时前
力扣hot100-子串(C++)
c++·学习·算法·leetcode·子串
李少兄2 小时前
Java 后端开发中 Service 层依赖注入的最佳实践:Mapper 还是其他 Service?
java·开发语言
不会c+2 小时前
@Controller和@RequestMapping以及映射
java·开发语言
難釋懷3 小时前
解决状态登录刷新问题
java·开发语言·javascript