当前位置: 首页 > news >正文

《C++ vector详解》

目录

1.结构

2.构造函数

无参构造

指定参数构造

迭代器构造

初始化列表构造

3.拷贝构造

4.析构函数

5.遍历

重载【】

5.插入

扩容

尾插

指定位置插入

6.删除

尾删

指定位置删除


1.结构

vector是一个类似于数组一样的容器,里面可以放各种各样的元素。我们只需在其中储存空间的地址即可,所以包含三个私有元素,分别是_start_finish_end_of_storage。使用模板T可以使vector储存各种各样的元素。将元素指针类型typedef成迭代器。代码如下。

        template <class T>typedef T* iterator;    typedef const T* const_iterator;
private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;

2.构造函数

包含无参构造指定参数构造迭代器构造初始化列表构造

无参构造

不用传参,使用缺省参数或者初始化列表中的参数。

指定参数构造

第一个参数n表示空间大小,第二个参数const T& val = T()是一个缺省参数,当没有传递第二个参数的时候,会调用T的默认构造函数,用val来引用它。比如说T是int类型,那么val就是0的引用所以这里要加const,然后再用val去初始化剩余的空间

为什么要n有size_t和int两种类型呢?

如果只定义size_t,那么传参(6,5)的时候,编译器默认6是int类型,与size_t类型不符合,那么就会找更匹配的构造函数,这时候会找到迭代器构造,而此时就会出现非法的间接寻址(int类型的值无法解引用)编译错误。

如果只定义int,那么传递的参数指定是size_t类型,如(6u,5)(后缀加u表示unsign int类型),同样会产生相同的问题。所以两者都显示定义了。

迭代器构造

参数为迭代器,迭代器是与指针类似的东西,可以进行解引用,+,-等操作。

这里为什么不使用前面的迭代器iterator,而是要使用一个成员函数模板?前面的iterator只能表示vector容器的迭代器,当我们想用其他容器的迭代器来初始化vector的时候就不适用了,所以要定义一个新的函数模板。

其实我自己在学的时候还有一个疑问就是为什么std::list<double> iterator能初始化vevtor<int>?

一是本质上使用的是(*first)来初始化的,拿到的是double类型的元素,

二是这其中实际上涉及到了隐式类型转换将double转换成了int。

初始化列表构造

std::initializer_list<T>是C++11 标准类型,专门处理花括号 {} 初始化(如 {1, 2, 3},之后再遍历尾插。这种方法可以避免重复地push_back。

//无参
vector<int> v1;
//指定参数
vector<int> v2(6);
//迭代器构造
string s1("wit lzk");
vector<int> v3(s1.begin(), s1.end());
//花括号
vector<int> v4 = { 1,2,3,5 };
//构造函数vector(){}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(std::initializer_list<T> il){reserve(il.size());for (auto t& : il){push_back(t);}}

3.拷贝构造

旧版拷贝构造分为开空间,遍历,拷贝

//拷贝构造vector(const vector<T>& v){reserve(v.capacity());for (auto& t : v){push_back(t);}}

赋值本质上也复用了拷贝构造。比如说v2 = v1把v1赋值给v2,v1进行传值传参,调用拷贝构造函数,v1会生成一个临时副本拷贝给v,然后利用swap将v的值和v2交换,实现赋值操作。同时v是一个临时对象,在和v2进行交换后函数结束时就会将原空间析构。

        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;}

4.析构函数

析构函数还是看构造函数构造了什么,析构函数就析构什么。

/析构函数~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}

5.遍历

得到对象空间大小,和实际数据个数。

size_t size() const
{return _finish - _start;
}
size_t capacity() const
{return _end_of_storage - _start;
}

重载【】

T operator[](size_t n)
{assert(n < size());return _start[n];
}
vector<int> v1 = { 1,2,3,4 };
//[]遍历
for (size_t t = 0; t < v1.size(); t++)
{cout << v1[t] << " ";
}
cout << endl;
//范围for遍历
for (auto ch : v1)
{cout << ch << " ";
}
cout << endl;

5.插入

插入前需要判断空间是否足够,不够需要扩容。拷贝数据到新空间时,不能使用memcpy,如果是内置类型还好,如果是自定义类型,浅拷贝将会导致空间析构两次。所以直接暴力拷贝数据。因为是异地扩容,所以拷贝完毕要重新给类成员变量赋值。

扩容

//扩容
void reserve(size_t n)
{//扩容是改变(_end_of_storage-start)的值/*if (n <= _end_of_storage - _start){_finish = _start + n;}*/if(n > capacity()){size_t pre_size = size();T* tmp = new T[n];//_start不能为空if (_start){//使用memcpy为浅拷贝,资源会被析构两次//memcpy(tmp, _start, sizeof(_start));for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}//数据拷贝完要把旧空间删除delete[] _start;}_start = tmp;_finish = _start + pre_size;_end_of_storage = _start + n;}
}

尾插

//尾插
void push_back(const T& v)		 
{size_t len = _finish - _start;//写法太冗余//if (_finish < _end_of_storage)//{//	_start[len] = v;//}//else {//	//空间不够扩容//	reserve(capacity() == 0 ? 4 : 2 * capacity());//	_start[len] = v;//}//_finish++;//相等就扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//无论满没满都要进行这两步(*_finish) = v;_finish++;
}

指定位置插入

这里需要注意迭代器失效的问题,异地扩容后_start被改变,pos也就失效了。所以要先记录pos到_start的距离,扩容后重新赋值。

//指定位置插入
iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容完后的_start会改变//原来的pos会失效(迭代器失效了)pos = _start + len;}//移动数据iterator it = _finish;while (it >= pos){*it = *(it - 1);it--;}*pos = x;_finish++;return pos;
}

6.删除

尾删

改变一下_finish的位置即可。


//尾删
void pop_back()
{assert(_finish > _start);_finish--;}

指定位置删除

那么在这个地方vs实现的vector会出现迭代器失效的问题。vs会认为erase一次后it就失效了,所以必须在使用erase之后,重新给it赋值。 当然自己实现的vector中不会出现这个问题。

vector<int> v = { 1,1,2,3,3,4,5,6,7,8,9,9 };
for (auto ch : v)
{cout << ch << " ";
}
cout << endl;
//删除所有奇数
vector<int>::iterator it = v.begin();
while (it != v.end())
{if (*it % 2 == 1){//重新赋值it = v.erase(it);}else {it++;}
}
for (auto ch : v)
{cout << ch << " ";
}
cout << endl;
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos <= _finish);iterator it = pos;while (it < _finish){*it = *(it + 1);it++;}_finish--;return pos;
}

"Happy Coding! 🚀" 

http://www.xdnf.cn/news/455797.html

相关文章:

  • Python----神经网络(基于DNN的风电功率预测)
  • GpuGeek:为创新者提供灵活、快速、高效的云计算服务!
  • PHP基础知识
  • 以项目的方式学QT开发(二)
  • Apple Vision Pro空间视频创作革命:从180度叙事到沉浸式语法的重构——《Adventure》系列幕后技术深度解析
  • AI浪潮:开启科技新纪元
  • 数据分析NumPy
  • wps excel将表格输出pdf时所有列在一张纸上
  • RabbitMQ 消息模式实战:从简单队列到复杂路由(一)
  • 数据可视化-----子图的绘制及坐标轴的共享
  • C语言经典笔试题目分析(持续更新)
  • conda更换清华源
  • Spring Cloud探索之旅:从零搭建微服务雏形 (Eureka, LoadBalancer 与 OpenFeign实战)
  • Synchronized详解及高频面试问答
  • CSRF 和 XSS 攻击分析与防范
  • Python爬虫第21节- 基础图形验证码识别实战
  • 前端的面试笔记——JavaScript篇(二)
  • 数字孪生工厂实战指南:基于Unreal Engine/Omniverse的虚实同步系统开发
  • 图像颜色理论与数据挖掘应用的全景解析
  • OrangePi Zero 3学习笔记(Android篇)9 - I2C和从设备
  • 【AI News | 20250514】每日AI进展
  • 如何通过命令提示符(CMD)检查虚拟化是否已启用
  • 任意复杂度的 JSON 数据转换为多个结构化的 Pandas DataFrame 表格
  • 吊舱夜视聚光灯模块设计的要点概述
  • vxe-table 同时实现合并单元格与任意列展开行
  • gmsh读取 STEP 文件并划分网格
  • eBPF 开源项目 Cilium 深入分析
  • Express项目解决跨域问题
  • 代码随想录 算法训练 Day1:数组
  • Vue 3 打开 el-dialog 时使 el-input 获取焦点