IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> vector -> 正文阅读

[数据结构与算法]vector

一:vector的介绍及使用

1.1vector的介绍

1.vector时可变大小数组的序列容器。

2.类似于数组,vector也采用连续储存空间来储存元素。vector支持下标访问元素。

3.vector使用动态分配数组来储存他的元素。当新的元素插入的时候,这个数组需要被重新分配大小。

4.vector分配空间策略:vector会分配一些额外的空间来适应可能的增长,因为储存空间比实际需要的存储空间更大。重新分配都应该是对数增长的间隔的大小,以至于在末尾插入一个新的元素的时间复杂度往往为O(1).

5.vector占用了更多的储存空间,为了获得管理储存空间的能力,以一种有效的方式动态增长。

6.与其他动态序列容器相比,vector在访问元素的时候更加高效,在末尾添加或删除元素相对高效,但在中间位置进行相关操作,成本稍高。

1.2vector使用

1.2.1vector定义

vector

一.vector的介绍及使用

1.1vector的介绍

1.vector时可变大小数组的序列容器。

2.类似于数组,vector也采用连续储存空间来储存元素。vector支持下标访问元素。

3.vector使用动态分配数组来储存他的元素。当新的元素插入的时候,这个数组需要被重新分配大小。

4.vector分配空间策略:vector会分配一些额外的空间来适应可能的增长,因为储存空间比实际需要的存储空间更大。重新分配都应该是对数增长的间隔的大小,以至于在末尾插入一个新的元素的时间复杂度往往为O(1).

5.vector占用了更多的储存空间,为了获得管理储存空间的能力,以一种有效的方式动态增长。

6.与其他动态序列容器相比,vector在访问元素的时候更加高效,在末尾添加或删除元素相对高效,但在中间位置进行相关操作,成本稍高。

1.2vector使用

1.2.1vector定义

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

示例:

?

1.2.2vector iterator 的使用

iterator接口说明
begin+ end获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin+ rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的 reverse_iterator

示例:

?

1.2.3vector空间增长问题

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

capacity再vs下是1.5倍增长,g++下是2倍增长。

?

?

reserve只负责开辟空间。

resize支持开辟空间的同时也会进行初始化,影响size。

可以看得到,前期扩容比较频繁,可以使用reserve进行提前扩容,以提升性能。

?

?

1.2.3vector增删查改

增删擦改接口说明
push_back尾插
pop_back尾删
find查找(属于算法模块)
insert在pos位置插入数据
erase删除postion位置的数据
swap交换两个vector的数据空间
opertaor[]像数组一样访问

?

?

1.2.4vector迭代器失效问题

迭代器的主要作用是让算法能够不关心底层数据结构,其底层实际上是一个指针,或者是针对指针进行了封装。

vector的迭代器就是原生指针T*,迭代器失效就是迭代器底层对应指针所指向的空间被销毁了,使用了一块已经释放的空间。造成的结果就是程序崩溃。

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

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

2. 指定位置元素的删除操作--erase

1.erase的失效都是意义变了,或者不在有效访问数据范围。

2.一般不会使用缩容方案,那么erase的失效,一般不存在野指针的失效。 一般vector删除数据,都不会考虑缩容方案。 缩容方案:size()<capacity()/2时,可以考虑开一个size()大小的空间,拷贝数据,释放旧空间。 缩容的本质是时间换空间。一般设计都不考虑缩容,因为实际关注的大都是时间效率,不关注空间效率,因为现在的硬件设备都比较大,时间储存也比较便宜。

erase(pos)以后pos就失效了,pos的意义变了,但是不同平台下面对于访问pos的反应是不一样的。我们用的时候要小心,统一以失效角度去看待

3. 注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

g++对于erase迭代器失效检查时就非常的佛系,但在实际场景中,迭代器的意义变了,也会出现各种问题

*补充:vector迭代器失效有2种: 1.扩容,缩容,导致野指针,失效。 2.迭代器指向位置意义变了。 * 系统的越界机制检查。 ---- 不一定能查到。 编译实现机制检查。 ---- 相对靠谱。

二.模拟实现vector部分接口#

2.1关于底层的问题##

vector本身就是容器,意味着vector本身就必须满足各种类型的变量,即vector要使用模板。 准备阶段:

namespace bit
{
    template<class T>
    class vector
    {
    public:
        typedef T* iterator;
        typedef const T* const_iterator;
        *******
    private:
        iterator _start;
        iterator _finish;
        iterator _end_of_storage;
        }
}

2.2 构造函数

无参的默认构造函数

只需要将private中的参数设置成nullptr即可

//实现默认构造函数
        //无参
        vector()
            :_start(nullptr)
            ,_finish(nullptr)
            ,_end_of_storage(nullptr)
        {}

迭代器的方法初始化一段空间

采用模板,使得其能够泛型编程

//构造函数,拷贝构造一段空间
        template<class InputIterator>
        vector(InputIterator first, InputIterator last)
            : _start(nullptr)
            , _finish(nullptr)
            , _end_of_storage(nullptr)
        {
            while (first != last)
            {
                push_back(*first);
                first++;
            }
        }

构造并初始化一段空间

这个接口并不常用,但是是给构造函数铺路用的

//构造并初始化一段空间
        vector(size_t n, T& val = T())//给予一个默认构造函数
            :_start(nullptr)
            , _finish(nullptr)
            , _end_of_storage(nullptr)
        {
            //预开n个大小的空间
            reserve(n);
            //插入val
            for (size_t i = 0; i < n; i++)
            {
                push_back(val);
            }
        }

还需要重载一个(int, T)类型的,因为无法完成类似于vector<int> v(10,2)的初始化。

//构造并初始化一段空间
        vector(int n, T& val = T())//给予一个默认构造函数
            :_start(nullptr)
            , _finish(nullptr)
            , _end_of_storage(nullptr)
        {
            //预开n个大小的空间
            reserve(n);
            //插入val
            for (size_t i = 0; i < n; i++)
            {
                push_back(val);
            }
        }

交换函数

采用c++库中的swap接口即可,本质上是交换指针

//交换swap函数,本质上是交换指针。
        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后,通过swap函数,交换指针即可

//拷贝构造
        vector(const vector<T>& v)
            :_start(nullptr)
            , _finish(nullptr)
            , _end_of_storage(nullptr)
        {
            vector<T> tmp(v.begin(), v.end());
            swap(tmp);
        }

重载=

现代化写法

//重载=
        vector<T> operator=(vector<T> v)//这里已经进行了一次拷贝构造了
        {
            swap(v);
            return *this;
        }

2.3实现size 和 capacity接口

值得注意的是size指的是有效数据的大小 capacity指的是可以储存的有效数据的大小 因为类型没有规定,所以要单独使用接口来获取

//大小
        size_t size()const
        {
            return _finish - _start;
        }
//容量
        size_t capacity()
        {
            return _end_of_storage - _start;
        }

2.4迭代器部分接口实现

主要实现迭代器的begin 和 end 接口,保证正向迭代器能够正常使用,至于++不需要重载,因为 本身底层就是使用的“迭代器”,指针++可以跳过一个类型的长度。 <u>迭代器要满足const && 非const类型的指针都能够使用。<u>

//迭代器返回begin end
        iterator begin()
        {
            return _start;
        }
        iterator end()
        {
            return _finish;
        }
        const_iterator begin()const
        {
            return _start;
        }
        const_iterator end()const
        {
            return _finish;
        }

2.5模拟实现reserve

reserve就是进行扩容的函数接口

//实现扩容接口reverse
//上面提及扩容会导致迭代器失效,这里就是因为扩容销毁了更改了原有的指针指向
//导致pos指针成了野指针,所以要更新pos指针。
/***更新方法:记录pos和_start之间的相对距离,以便更新pos***/
        void reserve(size_t n)
        {
            size_t sz = size();
            //避免无端的申请扩容
            if (n > capacity())
            {
                T* tmp = new T[n];
                //判断数据是否需要拷贝
                if (_start)
                {
                    //memcpy(tmp, _start, size() * sizeof(T));
                    //memcpy是浅拷贝,会导致内存泄漏的风险。
 ? ? ? ? ? ? ? ? ? ?for(size_t i = 0;i<size(),i++)
 ? ? ? ? ? ? ? ? ?  {
 ? ? ? ? ? ? ? ? ?      tmp[i] = _start[i];
 ? ? ? ? ? ? ? ? ?  }
                    delete[] _start;
                }
                //更新private数据
                _start = tmp;
            }
                _finish = _start + sz;
                _end_of_storage = _start + n;
        }

2.5模拟实现resize接口

string提及过resize可以更改capacity和size,并且还能进行相应的初始化,vector也一样。

  1. n>capacity 进行扩容即可

  2. n<=capacity 1.n>size 那么将size至capacity之间的空间赋值val。 2.n<size 截断即可。

void resize(size_t n, T val = T())//单参数支持隐式类型的转换
        {
            //n > capacity
            if (n > capacity)
            {
                reserve(n);
            }
            else
            {
                //n>size() && n<capacity
                if (n > size())
                {
                    while (_finish < _end_of_storage)
                    {
                        *_finish = val;
                        _finish++;
                    }
                }
                else
                {
                    _finish = _start + n;
                }
            }
        }

2.6模拟实现push_back && pop_back##

push_back是尾插,尾插就需要考虑扩容和越界访问的问题 pop_back是尾删,要考虑越界访问的问题。

//插入push_back
            void push_back(const T& val)
            {
                //判断是否需要扩容
                if (_finish == _end_of_storage)
                {
                    size_t newcapacity = capacity() == 0 ? 4 :capacity()* 2;
                    reserve(newcapacity);
                }
                //插入数据
                *_finish = val;
                _finish++;
            }
?
            //pop_back
            void pop_back()
            {
                assert(_finish > _start);
                --_finish;
            }

2.7重载[]

vector是在堆上开辟的一段连续的空间,用于储存数据的容器,所以它可以支持类似于数组的下标直接访问的优势,重载[]是必不可少的。

//重载[]
            //为了减少拷贝构造,可以引用返回。
            T& operator[](const size_t pos)
            {
                assert(pos < size());
?
                return *(_start + pos);
            }
            //const版本
            const T& operator[](const size_t pos)const
            {
                assert(pos < size());
?
                return _start[pos];
            }

2.8模拟实现insert && ersae

对于insert要考虑: 1.给定的pos是否合法? 2.插入是否会需要扩容? 3.pos指针是否会失效,导致迭代器失效? 4.挪动数据是否会出现问题? 5.是否需要更新基底? 对于earse要考虑: *1.给定的pos是否合法? 2.是否存在越界删除? 3.是否需要更新基地?*

//模拟insert
            iterator insert(iterator pos, const T& val)
            {
                //检查pos是否合法
                assert(pos>=_start && pos<=_finish);
                //判断是否需要扩容
                if (_finish == _end_of_storage)
                {
                    size_t n = pos - _start;
?
                    size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
                    reserve(newcapacity);
?
                    pos = _start + n;
                }
                //开始挪动数据
                //扩容完成后,pos的位置会指向不明确,会使得迭代器失效。需要单独处理pos.
                auto end = _finish -1;
                while (end >= pos)
                {
                    *(end + 1) = *end;
                    end--;
                }
                *pos = val;
                _finish++;
                return pos;
            }
            iterator erase(iterator pos)
            {
                //判断pos是否合法
                assert(pos >= _start && pos <= _finish);
                auto it = pos;
                while (it < _finish)
                {
                    *it = *(it + 1);
                    it++;
                }
                --_finish;
                return pos;
            }

2.9清屏函数

清屏:将有效数据删除

void clear()
            {
                _start = _finish;
            }

3.0资源管理

//资源管理
        ~vector()
        {
            if (_start)
            {
                delete[] _start;
                _start = _finish = _end_of_storage = nullptr;
            }
        }

vector完结。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 21:19:25  更:2022-09-24 21:23:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 21:39:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码