
🫧个人主页:小年糕是糕手
💫个人专栏:《C++》《Linux》《数据结构》《Blender 修行笔记》
🎨你不能左右天气,但你可以改变心情;你不能改变过去,但你可以决定未来!
目录
[1. size() → 获取元素个数](#1. size() → 获取元素个数)
[2. empty() → 判断是否为空](#2. empty() → 判断是否为空)
[3. resize(n) → 重新调整大小](#3. resize(n) → 重新调整大小)
[4. capacity() → 查看容量(预分配空间)](#4. capacity() → 查看容量(预分配空间))
[5. reserve(n) → 预分配空间(提升效率)](#5. reserve(n) → 预分配空间(提升效率))
[1. push_back(x) → 尾部添加元素(最常用)](#1. push_back(x) → 尾部添加元素(最常用))
[2. emplace_back(x) → 尾部高效添加(C++11)](#2. emplace_back(x) → 尾部高效添加(C++11))
[3. insert(pos, x) → 指定位置插入](#3. insert(pos, x) → 指定位置插入)
[1. pop_back() → 删除最后一个元素](#1. pop_back() → 删除最后一个元素)
[2. erase(pos) → 删除指定位置元素](#2. erase(pos) → 删除指定位置元素)
[3. erase(begin, end) → 删除区间](#3. erase(begin, end) → 删除区间)
[4. clear() → 清空所有元素](#4. clear() → 清空所有元素)
[1. [] → 下标访问(最快,不检查越界)](#1. [] → 下标访问(最快,不检查越界))
[2. at(i) → 下标访问(会检查越界,更安全)](#2. at(i) → 下标访问(会检查越界,更安全))
[3. front() → 访问第一个元素](#3. front() → 访问第一个元素)
[4. back() → 访问最后一个元素](#4. back() → 访问最后一个元素)
[【遍历 / 迭代器】常用函数](#【遍历 / 迭代器】常用函数)
[1. begin() → 指向第一个元素的迭代器](#1. begin() → 指向第一个元素的迭代器)
[2. end() → 指向最后一个元素下一位的迭代器](#2. end() → 指向最后一个元素下一位的迭代器)
[1. swap(vec) → 交换两个 vector](#1. swap(vec) → 交换两个 vector)
[2. assign(n, val) → 赋值(覆盖原有数据)](#2. assign(n, val) → 赋值(覆盖原有数据))
[erase 的精确失效规则:](#erase 的精确失效规则:)
[insert 的精确失效规则:](#insert 的精确失效规则:)
[五、erase 返回值的精确行为](#五、erase 返回值的精确行为)
[六、insert 也有返回值(C++11 起)](#六、insert 也有返回值(C++11 起))
一、定义和初始化
vector和string不同的是vector是没有重载流插入和提取的,我们需要自己来写一个打印函数,我们依旧有三种方式:
cpp
//vector是没有重载流插入和提取的,我们需要自己来写一个打印函数
void Print(const vector<int>& v)
{
//方式一
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
//方式二
//这里auto实际上是vector<int>::const_iterator
for (auto it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
//方式三
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
}
下面我们来尝试给他定义和初始化一下:
cpp
//定义和初始化
void test_vector1()
{
vector<int>v1;
vector<int>v2(10, 1);//创建了十个空间,空间里都是1
vector<int>v3(v2.begin(), v2.end());//v2和v3完全一致
string s1("hello world");
vector<int>v4(s1.begin(), s1.end());//把字符串里的每个字符,按ASCII码存入v4中
vector<int>v5(v4);//拷贝构造
vector<int>v6({ 1,2,3,4,5,6,7 });
vector<int>v7 = { 1,2,3,4,5,6,7 };
//vector没用支持流的插入和提取(string支持)
Print(v2);
Print(v3);
Print(v4);
Print(v5);
}
其实vector大家就给他当成一个动态数组,我们之前学过的变长数组,但是我们说过有些编译器是不支持变长数组的,但是vector大家一般都是支持的
二、vector的常用成员函数
【容量相关】常用函数
1. size() → 获取元素个数
cpp
v.size(); // 返回元素数量(无符号整数)
2. empty() → 判断是否为空
cpp
v.empty(); // 空返回true,非空返回false
3. resize(n) → 重新调整大小
cpp
v.resize(10); // 变成10个元素,多删少补
resize(n):扩大时自动填 0 / 空值
resize(n, 66):扩大时新元素全部填 66
4. capacity() → 查看容量(预分配空间)
cpp
v.capacity(); // 实际分配的内存大小 >= size
5. reserve(n) → 预分配空间(提升效率)
cpp
v.reserve(100); // 提前分配100个元素空间,避免频繁扩容
下面我们在函数中来看看vector的和容量相关的函数(大家重点看注释):
cpp
//扩容机制 -- 我们通常不会去缩容
//我们不难发现VS2022下依旧保持1.5倍左右的扩容机制
void test_vector2()
{
vector<int> v1;
//如果我们知道我们需要开多少空间可以直接使用reverse
int x;
cin >> x;
v1.reserve(x);
size_t old = v1.capacity();
cout << old << endl;
for (size_t i = 0; i < 100; i++)
{
v1.push_back(i);
if (old != v1.capacity())
{
cout << v1.capacity() << endl;
old = v1.capacity();
}
}
}
//clock()统计的是CPU运行时间,不是墙钟时间,不受系统调度影响
void test_vector3()
{
vector<int> v1;
const size_t N = 10000000; // 合理测试次数:1000万次
size_t begin = clock();
for (long long i = 0; i < N; i++)
{
v1.push_back(i);
}
size_t end = clock();
cout << end - begin << endl;
}
//reserve = 开空间,不造元素
//resize = 开空间 + 造元素,直接能用
void test_vector4()
{
vector<int> v1;
v1.reserve(10);
// size=0,不能访问 v[0]
// 只是预留空间
vector<int> v2;
v2.resize(10);
// size=10,可以直接用 v[0]~v[9]
// 有10个元素
vector<int> v3;
v3.resize(100, 1);
//把v的大小改成100个元素,所有元素的值都是 1。
}
【添加元素】常用函数
1. push_back(x) → 尾部添加元素(最常用)
cpp
v.push_back(10); // 尾部加10
v.push_back(20);
2. emplace_back(x) → 尾部高效添加(C++11)
比 push_back 更快,用法一样:
cpp
v.emplace_back(30);
3. insert(pos, x) → 指定位置插入
cpp
// 在第2个位置(下标1)插入 100
v.insert(v.begin() + 1, 100);
这里我们需要对比一下push_back和emplace_back:
cpp
//emplace
struct AA {
int _a1 = 1;
int _a2 = 2;
AA(int a1 = 1, int a2 = 1)
:_a1(a1)
,_a2(a2)
{ }
};
void test_vector6()
{
AA aa1 = { 0,0 };
vector<AA> v = { aa1,{1,1},{2,2},{3,3} };
auto it = v.begin();
while (it != v.end())
{
//结构体不属于内置类型不能直接解引用打印
//cout << *it << endl;
cout << it->_a1 << ":" << it->_a2 << endl;
it++;
}
//push_back只可以传AA对象
//emplace可以传AA对象也可以传构造AA对象的参数
//emplace更高效
v.push_back(aa1);
v.emplace_back(aa1);
v.push_back({ 1,1 });
v.emplace_back(1, 1);
}
【删除元素】常用函数
1. pop_back() → 删除最后一个元素
cpp
v.pop_back(); // 无返回值,直接删尾部
2. erase(pos) → 删除指定位置元素
cpp
// 删除第3个元素(下标2)
v.erase(v.begin() + 2);
3. erase(begin, end) → 删除区间
cpp
// 删除前3个元素
v.erase(v.begin(), v.begin() + 3);
4. clear() → 清空所有元素
cpp
v.clear(); // size=0,capacity不变
这里依旧给出一段解释代码:
cpp
void test_vector5()
{
//insert -- 头插
//erase -- 删除
vector<int>v1 = { 1,2,3,4,5,6,7 };
v1.push_back(8);
v1.insert(v1.begin(), 0);//传进去的是迭代器
Print(v1);
//insert迭代器是支持加的,如果我们想在下标为3的地方插入数字x
//下标是从0开始的
int x;
cin >> x;
v1.insert(v1.begin() + 3, x);
v1.erase(v1.begin());//头删
v1.erase(v1.begin() + 3);//删除下标为3的数字
}
【访问元素】常用函数
1. [] → 下标访问(最快,不检查越界)
cpp
int x = v[0]; // 访问第一个元素
v[0] = 100; // 修改第一个元素
2. at(i) → 下标访问(会检查越界,更安全)
cpp
int x = v.at(0);
3. front() → 访问第一个元素
cpp
v.front(); // 等价 v[0]
4. back() → 访问最后一个元素
cpp
v.back(); // 等价 v[v.size()-1]
【遍历 / 迭代器】常用函数
1. begin() → 指向第一个元素的迭代器
2. end() → 指向最后一个元素下一位的迭代器
遍历示例(最常用):
cpp
// 方式1:for循环(推荐)
for(int i=0; i<v.size(); i++) cout << v[i];
// 方式2:范围for(C++11,最简单)
for(auto x : v) cout << x;
// 方式3:迭代器
for(auto it = v.begin(); it != v.end(); it++)
cout << *it;
【其他实用函数】
1. swap(vec) → 交换两个 vector
cpp
vector<int> a, b;
a.swap(b);
2. assign(n, val) → 赋值(覆盖原有数据)
cpp
v.assign(5, 10); // 5个10
cpp
#include<iostream>
#include<vector>
using namespace std;
void Print(const vector<int>& v)
{
for (auto x : v)
{
cout << x << " ";
}
cout << endl;
}
int main()
{
vector<int> a, b;
a.emplace_back(1);
a.emplace_back(3);
a.emplace_back(5);
a.emplace_back(7);
b.emplace_back(2);
b.emplace_back(4);
b.emplace_back(6);
b.emplace_back(8);
//将a和b的全部元素进行交换
a.swap(b);
Print(a);
Print(b);
//assign替换机制是会先清空数组然后再覆盖
a.assign(3, 7);//将a里面的元素替换成3个7
Print(a);
a.swap(b);
Print(a);
Print(b);
return 0;
}
高频使用速记表
| 功能 | 函数 |
|---|---|
| 尾部添加 | push_back(x) |
| 尾部删除 | pop_back() |
| 获取大小 | size() |
| 判断为空 | empty() |
| 访问第 i 个元素 | v[i] / v.at(i) |
| 访问首尾 | front() / back() |
| 指定位置插入 | insert(pos, x) |
| 删除指定位置 | erase(pos) |
| 清空所有元素 | clear() |
三、尝试手搓vector
大家这部分尝试去看注释理解,如果理解不了其实也不影响我们后面去做一些项目
vector.h
cpp
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace Byte
{
template<class T>
class vector
{
public:
//typedef T* iterator;
using iterator = T*;
using const_iterator = const T*;
using value_type = T; // 添加 value_type 类型别名
vector()
//:_start(nullptr)
//, _finish(nullptr)
//, _end_of_storage(nullptr)
{
}
//这是vector的构造函数:初始化列表构造
//就是让你能直接用花括号给vector批量初始化
//initializer_list<value_type>
//initializer_list: C++专门用来接收大括号{ ... }列表的标准类型
//value_type:就是vector存的元素类型,比如int、string
//参数名il就是随便起的,写成lst也一样
vector(initializer_list<value_type> il)
{
reserve(il.size());
for (const auto& e : il)
{
push_back(e);
}
}
//析构
~vector()
{
if (_start != nullptr) // 修改:0 改为 nullptr
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
//传统写法
//拷贝构造v2(v1)
//这里看似只有一个参数但是有一个隐含的this指针
//vector(const vector<T>& v)
//{
// reserve(v.capacity());
// for (const auto& e : v)
// {
// push_back(e);
// }
//}
//v1 = v3
//vector<T>& operator=(const vector<T>& v)
//{
// if (this != &v)
// {
// clear();
// reserve(v.capacity());
// for (const auto& e : v)
// {
// push_back(e);
// }
// }
// return *this;
//}
//n个val的初始化
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//函数模板,迭代器不一定是vector迭代器,也可以是其他容器的迭代器
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
//reserve(last - first);
while (first != last)
{
push_back(*first);
++first;
}
}
//现代写法
vector(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
//v1 = v3
vector<T>& operator=(vector<T> tmp)
{
swap(tmp);
return *this;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage); // 修改:_end_of_start 改为 _end_of_storage
}
void clear()
{
_finish = _start;
}
//判空
bool empty() const
{
return (_start == _finish);
}
//初始位置的迭代器
iterator begin()
{
return _start;
}
//结束位置的迭代器
iterator end()
{
return _finish;
}
//重载一个const版本防止权限放大
//初始位置的迭代器
const_iterator begin() const
{
return _start;
}
//结束位置的迭代器
const_iterator end() const
{
return _finish;
}
//扩容
void reserve(size_t n)
{
//不去缩容,只去扩容
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start != nullptr)
{
// 修改:memcpy 改为循环拷贝,支持非POD类型
for (size_t i = 0; i < sz; ++i)
{
new (tmp + i) T(_start[i]); // placement new 拷贝构造
_start[i].~T(); // 析构原对象
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
//这里后面一个参数可以看成给了一个缺省值
//做缺省参数:字面量常量、全局变量/类的静态成员、匿名对象
void resize(size_t n, T val = T())
{
if (n < size())
{
//删除数据
_finish = _start + n;
}
else
{
//扩容
reserve(n);
//插入数据
while (_finish < _start + n)
{
*_finish = val; // 修改:x 改为 val
++_finish;
}
}
}
//存储空间
size_t capacity() const
{
return _end_of_storage - _start;
}
//数据个数
size_t size() const
{
return _finish - _start;
}
//对于普通对象可以读可以写
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
//针对const对象,只可以读
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
//头插
void push_back(const T& x)
{
//没有多余空间
if (_finish == _end_of_storage)
{
//如果capacity是0的话就开一个大小为2字节的空间,如果不为0就二倍扩容
reserve(capacity() == 0 ? 2 : capacity() * 2);
}
*_finish = x;
++_finish;
}
//头删
void pop_back()
{
assert(!empty());
--_finish;
}
//在pos位置插入一个x
void insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
//扩容
if (_finish == _end_of_storage)
{
//原来我们扩容
//reserve(capacity() == 0 ? 4 : capacity() * 2);
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
//挪动数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
//如果我们按照原方式去扩容这里会造成一个问题叫做迭代器失效,我们pos假设是在中间位置
//现在我们要去进行扩容,但是pos指向的还是上面的位置(已经被释放了),不在这个新的空间中
//所以就会造成迭代器失效,所以我们现在要让pos指向新的空间中来(如果不扩容的话就没有这个问题)
//删除pos位置的值 - 带返回值的版本(C++标准做法)
//返回值:返回被删除元素下一个位置的有效迭代器(其实就是pos位置)
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish); // 修改:<= 改为 <,因为不能删除 end()
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}
test.c
cpp
#include"vector.h"
namespace Byte
{
void Print(const vector<int>& v)
{
for (auto iu : v)
{
cout << iu << " ";
}
cout << endl;
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
}
void test_vector1()
{
Byte::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(0);
v.push_back(6);
Print(v);
}
void test_vactor2()
{
Byte::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(0);
v.push_back(6);
Print(v);
v.insert(v.begin(), 0);
Print(v);
auto it = v.begin() + 3;
//it作为实参传给形参,pos位置的修改不会影响我们it
//扩容才会失效,但是我们不知道他是否扩容
//所以无论是否扩容我们都当作it失效了,形参的改变不影响实参
//这也就意味着insert之后it就不能使用了
v.insert(it, 30);
Print(v);
}
void test_vactor3()
{
Byte::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(0);
v.push_back(6);
Print(v);
v.erase(v.begin());
Print(v);
auto it = v.begin() + 2;
//如果这个it在末尾就会失效,不能访问,访问结果未定义
v.erase(it);
Print(v);
//vs2019及其之后的版本会严格检查,大家可以简单记只要erase以后迭代器对象就失效了,不能访问
}
//深浅拷贝
void test_vector4()
{
Byte::vector<int>v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(0);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
Byte::vector<int> v2(v1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//我们不写拷贝构造这里也可以运行出结果但是系统会崩溃
//调用vector(initializer_list<value_type> il);
//Byte::vector<int> v3({ 10,20,30,40 });
//这里其实也是一个浅拷贝,我们要自己写一个深拷贝
Byte::vector<int> v3 = { 10,20,30,40 };
v1 = v3;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
Byte::vector<int> v4(10u, 1);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
void test_vector5()
{
Byte::vector<string> v1;
v1.push_back("111111111111111111111");
v1.push_back("222222222222222222222");
for (auto& e : v1)
{
cout << e << "";
}
cout << endl;
}
}
int main()
{
Byte::test_vector1();
return 0;
}
四、迭代器失效
我们在尝试手搓vector的时候经常会出现一个问题叫做迭代器失效,我们之前根本没遇到过这个问题,下面我们就来详细说说什么是迭代器失效:
一、简单记忆规则
我们先简单记:只要erase以后迭代器对象就失效了就不能访问,vector为了防止迭代器失效给erase了一个放回值
只要调用了
erase或insert,之前所有的迭代器都不能再用了。这个规则:
✅ 100% 安全(永远不会出现迭代器失效导致的 bug)
✅ 容易记忆(不用记复杂的失效规则)
✅ 适用于所有情况(包括扩容、不扩容、插入在前在后)
代价只是:
- ⚠️ 可能错过一些"实际上仍然有效"的情况(但安全第一)
二、严格来说的正确规则
erase 的精确失效规则:
cpp
vector<int> v = {10, 20, 30, 40, 50};
auto it1 = v.begin(); // 指向 10
auto it2 = v.begin() + 2; // 指向 30
auto it3 = v.begin() + 3; // 指向 40
v.erase(v.begin() + 2); // 删除 30
// 结果:
// it1 仍然有效(指向 10) ✅ 没失效
// it2 失效了(指向已删除元素)❌
// it3 失效了(指向 40,但后面元素前移了)❌
精确规则:被删除元素及之后的所有迭代器失效,之前的仍然有效。
insert 的精确失效规则:
cpp
vector<int> v = {10, 20, 30};
v.reserve(100); // 确保不扩容
auto it1 = v.begin(); // 指向 10
auto it2 = v.begin() + 1; // 指向 20
v.insert(v.begin() + 1, 99); // 插入到 20 前面
// 结果:
// it1 仍然有效(指向 10)✅
// it2 失效了(元素移动了)❌
精确规则:插入点及之后的所有迭代器失效,之前的仍然有效(前提是不扩容)。
三、erase的返回值
cpp
iterator erase(iterator pos); // 删除单个元素
iterator erase(iterator first, iterator last); // 删除区间
返回值: 返回被删除元素下一个位置的有效迭代器。
四、典型用法(安全删除)
cpp
vector<int> v = {1, 2, 3, 4, 5};
// ❌ 错误:迭代器失效
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it % 2 == 0) {
v.erase(it); // it 失效,++it 崩溃
}
}
// ✅ 正确:使用返回值
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it); // it 被更新为下一个有效位置
} else {
++it;
}
}
// 结果:v = {1, 3, 5}
五、erase 返回值的精确行为
cpp
vector<int> v = {10, 20, 30, 40, 50};
auto it = v.begin() + 2; // 指向 30
auto new_it = v.erase(it); // 删除 30
// new_it 现在指向 40(被删除元素的下一个)
cout << *new_it << endl; // 输出 40
图示:
cpp
删除前: [10, 20, 30, 40, 50]
↑
it
删除后: [10, 20, 40, 50]
↑
new_it (指向原来30的下一个元素)
六、insert 也有返回值(C++11 起)
cpp
iterator insert(iterator pos, const T& value);
// 返回指向新插入元素的迭代器
cpp
vector<int> v = {1, 2, 4, 5};
auto it = v.begin() + 2; // 指向 4
it = v.insert(it, 3); // 插入 3,it 被更新为指向新元素
cout << *it << endl; // 输出 3
// 现在可以继续使用 it
it = v.insert(it + 2, 99); // 安全
