当前位置: 首页 > 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! 🚀" 

相关文章:

  • 【软件工具】基于PDF文件内容识别的改名软件,PDF根据内容自动重命名,如何识别pdf内容并做文件命名,PDF批量改名
  • 费曼技巧实践
  • 数据库--处理模型(Processing Model)(二)
  • undefined reference to `typeinfo for DeviceAllocator‘
  • 西瓜书【机器学习(周志华)】目录
  • 鸿蒙OSUniApp 开发的一键分享功能#三方框架 #Uniapp
  • 深度学习、机器学习及强化学习的联系与区别
  • 实现可靠的 WebSocket 连接:心跳与自动重连的最佳实践
  • 机器学习——朴素贝叶斯练习题
  • 实用工具:微软软件PowerToys(完全免费),实现多台电脑共享鼠标和键盘(支持window系统)
  • 机器学习 day03
  • ARP Detection MAC-Address Static
  • 机器学习08-损失函数
  • 论文学习_Precise and Accurate Patch Presence Test for Binaries
  • CodeEdit:macOS上一款可以让Xcode退休的IDE
  • RabbitMQ最新入门教程
  • 考研408《计算机组成原理》复习笔记,第二章(2)数值数据的表示和运算(浮点数篇)
  • AI智能分析网关V4工服检测算法:工厂车间着装规范管理的智能化解决方案
  • 趣味编程:钟表
  • [250515] 腾讯推出 AI 编程助手 CodeBuddy,对标 Cursor
  • 网易一季度净利增长三成,丁磊:高度重视海外游戏市场
  • 龚正市长调研闵行区,更加奋发有为地稳增长促转型,久久为功增强发展后劲
  • 外交部:国际社会广泛理解和支持中方不同意台参加世卫大会的决定
  • 习近平复信中国丹麦商会负责人
  • 重庆市委原常委、政法委原书记陆克华被决定逮捕
  • 国务院关税税则委员会公布公告调整对原产于美国的进口商品加征关税措施