
◆博主名称:少司府
欢迎来到少司府的博客☆*: .。. o(≧▽≦)o .。.:*☆
⭐数据结构系列个人专栏:
⭐C++基础个人专栏:
⭐琢玉成器终有时,笔底生花夺锦归
目录
[一、vector 相关介绍](#一、vector 相关介绍)
[1.1 vector 的介绍](#1.1 vector 的介绍)
[1.2 vector 的基本使用](#1.2 vector 的基本使用)
[二、capacity 相关接口](#二、capacity 相关接口)
[2.1 reserve()](#2.1 reserve())
[2.2 resize()](#2.2 resize())
[2.3 vs中的vector扩容](#2.3 vs中的vector扩容)
[三、modifiers 相关接口](#三、modifiers 相关接口)
[3.1 push_back 和 insert](#3.1 push_back 和 insert)
[3.2 自定义类型作为顺序表中元素](#3.2 自定义类型作为顺序表中元素)
[四、vector 模拟实现](#四、vector 模拟实现)
[4.1 基本架构](#4.1 基本架构)
[4.2 构造、析构、赋值运算符](#4.2 构造、析构、赋值运算符)
[4.3 begin、end、reserve、resize](#4.3 begin、end、reserve、resize)
[4.4 size、capacity、empty、push_back](#4.4 size、capacity、empty、push_back)
[4.5 print_vector 和 print_container](#4.5 print_vector 和 print_container)
[4.6 insert、erase、[]](#4.6 insert、erase、[])
[4.7 迭代器失效](#4.7 迭代器失效)
[4.8 内置类型的显示实例化](#4.8 内置类型的显示实例化)
一、vector 相关介绍
1.1 vector 的介绍
vector的英文是"向量"的意思,在C++中,我们可以利用vector创建类似于顺序表的数据结构。其主要是通过模板来实现的。
我们可以看看官方的相关文档如何介绍vector的:
无论是在学习vector、string还是其他相关容器的时候,我们都要学会看文档来帮助我们学习。
1.2 vector 的基本使用
vector本质属于类模板,因此,我们需要使用类模板的相关的实例化方式。
cpp
void test_vector1()
{
vector<int> v1;
vector<int> v2(10, 1); // 10个数据,全为1
vector<int> v3(++v2.begin(), --v2.end());
for (int i = 0; i < v3.size(); i++) cout << v3[i] << " ";
cout << endl;
vector<int>::iterator it = v3.begin();
while (it != v3.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto v : v3) cout << v << " ";
}
如图,和string一样,vector也有迭代器,也有范围for,以及其他相关的 begin、end、push_back、insert等相关接口。
我们实例化v1,v1没有数据;实例化v2,第一个参数是数据个数,第二个是具体数据;实例化v3,传入v2的迭代器,左闭右开,有8个1。
打印出来看看:

如图,打印v3中的8个1。
二、capacity 相关接口
2.1 reserve()
cpp
vector<int> v(10, 1);
v.reserve(20);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.reserve(15);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.reserve(5);
cout << v.size() << endl;
cout << v.capacity() << endl;
cout << endl;
如图,reserve的用法和string类中基本保持一致,需要注意的是:与string不一样的是,reserve 不能缩容改变capacity!

如图,v.reserve(5) 既不会删除数据,也不会缩容。
2.2 resize()
cpp
v.resize(5);
cout << v.size() << endl;
cout << v.capacity() << endl; // <size,不缩容,但删除数据,默认保留前5个
for (auto V : v) cout << V << ' ';
cout << endl;
v.resize(10, 2);
cout << v.size() << endl;
cout << v.capacity() << endl; // >size && <capacity ,尾插数据
for (auto V : v) cout << V << ' ';
cout << endl;
v.resize(25, 3);
cout << v.size() << endl;
cout << v.capacity() << endl; // >size && >capacity,扩容并插入数据
for (auto V : v) cout << V << ' ';
cout << endl;
v.resize(30);
cout << v.size() << endl;
cout << v.capacity() << endl; // 不给数据的话会直接初始化为0
for (auto V : v) cout << V << ' ';
cout << endl;
如图,第一个resize(5)就是删除数据,只保留前5个。
第二个resize(10,2) 是保留10个数据空间,>size 的话就尾插5个2。
第三个 > capacity 的话会扩容。
没有给数据默认插入0。

结果如图。
2.3 vs中的vector扩容
cpp
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
//v.reserve(100);
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}

如图,这是在调试状态下单个TestVectorExpand() 函数缓慢扩容的状态,大约是1.5倍扩容的样子。

把它封装在测试函数中,编译器就会优化它的扩容。
三、modifiers 相关接口
3.1 push_back 和 insert
cpp
void test_vector3()
{
vector<int> v(10, 1);
v.push_back(2);
v.insert(v.begin(), 0);
for (auto e : v) cout << e << ' ';
cout << endl;
v.insert(v.begin() + 3, 3); // 第一个参数只能是迭代器,不支持下标
for (auto e : v) cout << e << ' ';
cout << endl;
// [] erase clear的用法和string中的类似
}
如图,这两者的用法和string中基本一样。

需要注意的是,insert 只能支持传迭代器,不支持下标。
3.2 自定义类型作为顺序表中元素
cpp
void test_vector4()
{
vector<string> v1;
string s1("xxxx");
v1.push_back(s1);
v1.push_back("yyyy"); // 隐式类型转换
// 本质是二维数组
// 10*5
vector<int> v(5, 1);
vector<vector<int>> vv(10, v);
vv[2][1] = 2;
vv.operator[](2).operator[](1) = 3; // 两个[] 调用的不是同一个函数,不一样
for (int i = 0; i < vv.size(); i++)
{
for (int j = 0; j < vv[i].size(); j++)
{
cout << vv[i][j] << ' ';
}
cout << endl;
}
}
如图,vector中存放的是string类的对象。
1)、除了内置类型外,vector顺序表中还能存放内置类型,例如string
2)、vector中存放vector的话,相当于是二维数组的实现。

四、vector 模拟实现
4.1 基本架构

如图,与string不一样的一点是,vector的模拟实现需要用到类模板。

如图,成员变量中,_start 指向起始位置,_finish 指向最后一个数据的下一个位置,_end_of_storage 指向容量的下一个位置。
4.2 构造、析构、赋值运算符
cpp
// 默认构造
vector()
{ }
如图是默认构造的写法,中间不需要处理任何数据。
C++11 中提供了强制生成默认构造的写法:
cpp
// C++11 强制生成默认构造写法
vector()=default;
cpp
vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
如图是拷贝构造,直接范围for赋值插入就行。
新 vector 有独立的内存空间,和原 vector 完全分离,修改新对象不会影响原对象,因此是深拷贝。
cpp
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
如图,赋值运算符重载方式和string一模一样。
cpp
// 类模板的成员函数,还可以是函数模板
template<class InputIterator>
vector(InputIterator first, InputIterator last) // 可以用任意类型的迭代器初始化
{
while (first != last)
{
push_back(*first);
++first;
}
}
如图,可以在类模板中通过模板函数来实现迭代器区间构造。
cpp
~vector()
{
// 销毁所有元素
for (auto& e : *this)
{
e.~T();
}
// 释放内存
delete[] _start;
// 指针清空
_start = _finish = _end_of_storage = nullptr;
}
如图,析构函数释放内存时,必须显示调用每个元素的析构。
最后,再delete释放内存并让指针置空。
4.3 begin、end、reserve、resize
cpp
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
如图,分别是普通版本和const版本的begin和end。
且,我们发现,实现迭代器的时候大多数左闭右开的情况。
cpp
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
T* tmp = new T[n];
// memcpy(tmp,_start,size()*sizeof(T)) 浅拷贝
for (size_t i = 0; i < old_size; i++) // 深拷贝
{
tmp[i] = _start[i]; // 库里的string的=会自己在末尾加上\0,string自己深拷贝
}
delete[] _start;
_start = tmp;
_finish = _start + old_size;
_end_of_storage = _start + n;
}
}
如图是reserve实现深拷贝的逻辑。和拷贝构造一样,for循环直接实现深拷贝就好。需要注意的是:拷贝完成之后更新指针,_start、_finish 和 _end_of_storage 指向新空间。
cpp
void resize(size_t n, T val = T())
{
if (n < size())
{
for (T* it = _start + n; it != _finish; ++it)
{
it->~T(); // 显式调用顺序表内单个元素的析构
}
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start + n)
{
// ✅ 正确:定位new,在指定内存构造对象
new(_finish) T(val);
_finish++;
}
}
}
如图是resize的实现,缺省参数的传入实现了resize(n) 版本和n个val版本。T()是匿名对象。
利用定位new来实现对已经分配好的内存空间的初始化。
4.4 size、capacity、empty、push_back
cpp
size_t size() const
{
return _finish - _start;
}
size_t capacity()
{
return _end_of_storage - _start;
}
如图,左闭右开返回个数。
cpp
bool empty()
{
return _start == _finish;
}
判空逻辑简单,判断是否是**_start==_finish**就行。
cpp
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
_finish++;
}
push_back 实现逻辑和string中的push_back几乎一模一样。

如图是下标+[] 的遍历和范围for的遍历。
4.5 print_vector 和 print_container
cpp
template<class T>
void print_vector(const vector<T>& v)
{
// 不能从没有实例化的类模板里面取东西,编译器无法确定const_iterator是类型还是静态成员变量
// typename 指明类型
typename vector<T>::const_iterator it = v.begin();
// 可以直接写 auto it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : v) cout << e << " ";
cout << endl;
}
如图,我们不能从没有实例化的类模板里面取东西,编译器无法确定const_iterator是类型还是静态成员变量,需要用到 typename 指明类型。
当然,也可以直接用 auto 推导类型。
cpp
template<class Container>
void print_container(const Container& v)
{
auto it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
如图代码可以打印任意类型的容器。用模板来推导对应容器类型生成相应代码。
4.6 insert、erase、[]
cpp
T& operator[](size_t n)
{
assert(n < size());
return *(_start + n);
}
如图,_start 是原生指针,不需要operator *
cpp
iterator insert(iterator pos,T x)
{
assert(pos >= _start);
assert(pos <= _finish);
// 扩容导致迭代器失效,本质就是野指针
if (_finish == _end_of_storage)
{
size_t len = pos - _start; // 迭代器失效,pos指向旧空间
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *(end);
--end;
}
*(pos) = x;
_finish++;
return pos;
}
如图,扩容之后会导致指向原空间的迭代器pos失效,因此扩容之后需要更新pos。
插入完之后返回更新之后的迭代器pos。
cpp
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos + 1;
}
如图,erase删除之后返回原空间原pos位置后一个位置。
4.7 迭代器失效
cpp
void test_vector2()
{
vector<int> v;
//v.push_back(1);
v.insert(v.begin(), 2);
v.insert(v.begin() + 1, 3);
v.insert(v.begin() + 2, 4);
print_vector(v);
int x;
cin >> x;
auto pos = find(v.begin(), v.end(), x); // C++规定传迭代器范围要求左闭右开
if (pos != v.end())
{
pos = v.insert(pos, 30);
// v.insert(pos,30);
// (*pos) *= 10;
(*(pos + 1)) *= 10;
}
print_vector(v);
}
注意:
1)、C++规定传迭代器范围要求左闭右开。
2)、对于 v.insert(pos,30); 和 (*pos) *= 10; 这两句:
如果扩容,迭代器失效,insert之后形参修正pos,实参pos失效,不要访问,错误写法!!
如果不扩容,也算一种迭代器失效,pos位置意义变了,不再指向原来的数据,不要访问!
不扩容的话,这句在vs下会挂,Linux下不会。
3)、库里面insert实现了返回新pos的功能。

结果如图。
4.8 内置类型的显示实例化
cpp
int i = int(); // 内置类型也可以构造函数实例化的写法
int j = int(1);
int k(2);
printf("%d %d %d\n", i, j, k);
如图,内置类型也可以显示实例化。

int() 没传参就是默认构造0。
本期的分享就到这里,如果觉得博主的文章比较对胃口的话,可以点一个小小的关注~
您的三连是我持续更新的动力~