• C++学习 STL组件之vector部分总结


    自从上一次学习STL的组件(string)已经过去有些日子了,主要是还在进行其他方面的学习,现在有了空闲继续来总结C++STL方面,vectot也是很早之前就学过的部分,学习过程中也是让我深深感到了C++STL的实用,现在在做有关方面的题目时有的地方会忘记(所以总结顺便复习),总之要学习和总结的东西还有很多,就请路过的看客和我一起学习前进吧。-----(≧∇≦)ノ

    首先是关于vector的介绍

    经过之前一遍学习后,我也大致了解了vector的学习步骤,我将其分为以下三个部分:

    C++学习 STL组件之vector部分总结

    有人可能会问那一条红线?那是与之相关的内容,也是我最近在学习的部分之一,下一次的内容就是他了(当然还是STL相关的,我学的慢。。。)

    然后当然就是给出vector的定义了!

    由于了解到定义这一部分也是蛮重要的知识点内容,所以我将此详细总结了一下,如下:

    1. vector是表示可变大小数组的序列容器

    2. 像数组一样,vector也采用的连续存储空间来存储元素,可以采用下标对vector的元素进行访问

    3. 和数组不同,vector的大小是可以动态改变的,而且它的大小会被容器自动处理(kksk^_^)

    4. 本质上讲,vector使用动态分配数组来存储它的元素,当新元素插入时候,这个数组为了增加存储空间需要被重新分配大小,具体做法是 “分配一个新的数组,然后将全部元素移到这个数组”

    5. vector分配空间策略是:

      • vector会分配一些额外的空间以适应可能的增长,所以存储空间比实际需要的存储空 间更大

      • 虽然不同的库采用不同的策略权衡空间的使用和重新分配,但是无论如何,重新分配 对数增长的间隔大小,以至于在末尾插入一个元素的时间都应该是在常数时间的复杂 度内完成的
    6. 与其它动态序列容器相比(deque、 list 、 forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率较低。

    再再来到最最重要的使用环节!

    首先是vector内所包含的接口有哪些:

    1.构造函数使用:
    构造函数上的使用有以下四种

    * vector()                                                                                无参构造
    * vector(size_type n, const value_type& val = value_type()    构造并初始化n个val
    * vector (const vector& x)                                                           拷贝构造
    * vector (InputIterator first, InputIterator last)                             使用迭代器进行初始化构造

    vector的这四种构造函数中最常用的就是
    无参构造函数vector()和拷贝构造vector (const vector& x)
    第二种构造vector(size_type n, const value_type& val = value_type() 看场合使用即可
    而关于第4种构造 在这里先跳过,毕竟迭代器相关我还没有学习,之后跟进的博客到达迭代器部分的时候再说

    2.vector提供的迭代器(iterator)使用:
    也就是以下两组

    • begin + end 获取第一个数据位置iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator

    • rbegin + rend 获取最后一个数据位置的reverse_iterator, 获取第一个数据前一个位置的reverseiterat

    画个图的话差不多是这种感觉(画画苦手):

    C++学习 STL组件之vector部分总结

    此处只总结迭代器的使用方法:
    eg1: const对象使用const迭代器进行遍历打印

    vector<int>::const_iterator it = v.begin();
    //C++中可以使用auto关键字来代替上面一句it的类型部分,自动匹配数据的类型。
     while (it != v.end())
     {
     cout << *it << " ";
     ++it;
     }
     cout << endl;
    }

    eg2:使用迭代器进行遍历打印

    vector<int>::iterator it = v.begin();//此句同上可使用auto关键字
     while (it != v.end())
     {
     cout << *it << " ";
     ++it;
     }

    eg3:使用反向迭代器进行遍历再打印

    vector<int>::reverse_iterator rit = v.rbegin(); //同上(没错就是懒得写)
     while (rit != v.rend())
     {
     cout << *rit << " ";
     ++rit;
     }

    2.有关容量空间变化的接口
    常用的有以下几种:

    • size 获取数据个数
    • capacity 获取容量大小
    • empty 判断是否为空
    • resize(重点) 改变vector的size
    • reserve (重点) 改变vector放入capacity

    前三种接口都是常用且简单的,与string相似,所以这里不再做演示。
    (注意,再次提醒capacity的增长是不固定的,具体增长多少是根据具体的需求定义
    的,比方说:vs下capacity是按1.5倍增长的,g++是按2倍增长的,vs是PJ版本STL,g++是SGI版本STL)

    主要需要注意的是resize和reserve两个函数:

    • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve就可以缓解vector增容代价缺陷问题。

    • resize在开空间的同时还会进行初始化(对于扩大的那一部分空间),影响size
      此处给出两个使用例子:

    eg1:

    std::vector<int> b;
     int sz = b.capacity();
     b.reserve(100);
     int ss=b.capacity();
     //可输出sz和ss来观察reserve效果

    eg2:

    std::vector<int> a;
    a.resize(5);
    a.resize(8,100); 
    a.resize(12);
    //可通过同上手段来观察

    3.vector内容增删查改操作相关接口
    常用有如下几种:

    • push_back 尾插
    • pop_back 尾删
    • find 查找(算法模块实现,不是vector的成员接口)
    • insert 在position之前插入val
    • erase 删除position位置的数据,返回下一个位置的迭代器
    • swap 交换两个vector的数据空间
    • operator[] 像数组一样访问元素

    其实我感觉上述这些接口都有用武之地,所以需要重点记忆的!

    (1)push_back以及pop_back
    eg:
    vector<int> a;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
    a.push_back(4);
    //此时a中内容为1 2 3 4
    a.pop_back(3);
    a.pop_back(4);
    //此时a中内容为1 2

    (2).operator[](其实这个接口主要学习还是在模拟实现部分,这里给出使用例)
    eg:
    vector<int> a;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
    cout<< a[0]<<a[1]<<a[2];
    //输出结果为123

    (3).find
    eg:
    int a[] = { 1, 2, 3, 4 };
    vector<int> s(a, a+sizeof(a)/sizeof(int)) //vector可以将数组直接插入具体实现在模拟实现
    auto it = find(v.begin(), v.end(), 3);
    // 此时*it的值为3
    //结果it为前两迭代器表示的范围内第一次找到3这个元素时的迭代器位置。

    其他的几种具体实现和学习在模拟实现部分(耐心)

    4.关于增删查改操作的接口有的时候还会导致迭代器的失效
    迭代器的失效产生的原因很多,这里举例常见的几种场景

    (1)insert或者erase导致的迭代器失效

    • insert导致的情况: insert会导致迭代器失效,是因为insert可能会导致增容,增容后pos还指向原来的空间,而原来的空间已经释放了

    • erase导致的情况:删除pos位置的数据,会导致pos迭代器失效,此时再用pos迭代器进行访问就会出现程序错误。

    (2)其他场景出现的迭代器失效

    eg:
    当进行以下操作时
    auto it = a.begin();
    while (it != a.end())
    {
    if (*it % 2 == 0) //简单的删除偶数
    v.erase(it);
    ++it; //在这一步就会出现程序错误,因为erase已经将 it 变成了一个失效迭代器, } 对于失效的迭代器进行++会引发错误

    那么问题来了,上面这段代码要怎么改呢?
    由于erase接口被调用会返回删除位置的下一个位置,所以将代码改为以下这种就可以啦:
    while (it != a.end())
    {
    if (*it % 2 == 0)
    it = a.erase(it);
    else
    ++it;
    }

    每当我学不动了时候我都会找张图片来细细观赏一番(洗眼睛),下面有请老婆登场:

    C++学习 STL组件之vector部分总结

    (嘿嘿)
    来来来现在我们继续,现在进入的就是最后的一部分啦,学完这里我们就可以放开手大胆的去用vector了(maybe~):

    vector的模拟实现(包括深度解析)

    1.对vector深度解析
    这一步是事关重要的,如果没有这一步的铺垫,要进行模拟实现就会难上加难,不清楚构造的东西自然难以实现
    首先附上一张图来做说明(手绘勿喷):

    C++学习 STL组件之vector部分总结

    上图中:start表示指向数据块的开始
    finish表示指向有效数据的尾
    end_of_storage表示指向存储容量的尾

    根据上面我画的这张图我们可以大致了解到vector的结构。
    已上图为例,当a中元素超过capacity时,容量会扩充至两倍,若是两倍还不够则会扩充至足够大的容量。

    注意:“但是扩容的步骤可不是直接在原来空间后面开辟空间这么简单,其中要经过重新配置、元素搬移、释放原空间等过程,是较为复杂的”

    最后到了模拟实现的部分,部分需要注意的点会写在注释里:

    namespace key{
        template<class K>
        class vector{
        public:
            // vector的迭代器是一个原生指针,所以做以下重定义方便实用
            typedef K* iterator;
            typedef const K* const_iterator;
            vector()                             //无参构造
                :_start(nullptr)                 //初始化列表
                , _finish(nullptr)
                , _endofstorage(nullptr){}
    
            vector(int n, const K& value = K())  //含参数构造,且初始化,插入n个value元素,第二个参数做缺省值可不填
                : _start(nullptr)
                , _finish(nullptr)
                , _endofstorage(nullptr){
                reserve(n);
                while (n--)
                {
                    push_back(value);         //若是有第二个参数输入则初始化元素
                }
            }
    
            //所以重新声明迭代器,迭代器区间[first,last]可以是任意容器的迭代器
            template<class InIterator>
            vector(InIterator first, InIterator last){
                reserve(last - first);
                while (first != last)
                {
                    push_back(*first);
                    ++first;
                }
            }
            vector(const vector<K>& v)
                : _start(nullptr)
                , _finish(nullptr)
                , _endofstorage(nullptr){
                reserve(v.capacity());
                K* it = begin();
                K* vit = v.cbegin();
                while (vit != v.cend())
                {
                    *it++ = *vit++;
                }
                _finish = _start + v.size();
                _endofstorage = _start + v.capacity();
            }
            vector<K>& operator= (vector<K> v){
                swap(v);
                return *this;
            }
            ~vector(){
                delete[] _start;
                _start = _finish = _endofstorage = nullptr;
            }
    
            // capacity
            size_t size() const { return _finish - _start; }
            size_t capacity() const { return _endofstorage - _start; }
    
            void reserve(size_t n){  //扩容,改变capacity的值
                if (n > capacity())
                {
                    size_t oldSize = size();
                    K* tmp = new K[n];
                    if (_start)
                    {
                        for (size_t i = 0; i < oldSize; ++i)
                            tmp[i] = _start[i];
                    }
                    _start = tmp;
                    _finish = _start + size;
                    _endofstorage = _start + n;
                }
            }
    
            void resize(size_t n, const K& value = K()){   //重新设定大小并初始化(部分)
                // 1.如果n小于当前的size,则数据个数缩小到n
                if (n <= size())
                {
                    _finish = _start + n;
                    return;
                }
                // 2.空间不够则增容
                if (n > capacity())
                    reserve(n);
                // 3.将size扩大到n        //扩大的部分要初始化
                iterator it = _finish;
                iterator _finish = _start + n;
                while (it != _finish)
                {
                    *it = value;
                    ++it;
                }
            }
            ///操作符重载///
            T& operator[](size_t pos){ 
                return _start[pos]; 
            }
            const T& operator[](size_t pos)const {
                return _start[pos]; 
            }
    
            ///增删查改/
            void push_back(const K& x){     //后插
                insert(end(), x); 
            }
            void pop_back(){             //后删
                erase(--end()); 
            }
    
            void swap(vector<K>& v){           //交换两个vector对象的数据空间
                swap(_start, v._start);
                swap(_finish, v._finish);
                swap(_endofstorage, v._endofstorage);
            }
    
            iterator insert(iterator pos, const K& x){   //插入
                assert(pos <= _finish);
                // 空间不够先进行增容
                if (_finish == _endofstorage)
                {
                    size_t size = size();
                    size_t newCapacity = (0 == capacity()) ? 1 : capacity() * 2;
                    reserve(newCapacity);
                    // 如果发生了增容,需要重置pos
                    pos = _start + size;
                }
                K* end = _finish - 1;
                while (end >= pos)
                {
                    *(end + 1) = *end;
                    --end;
                }
                *pos = x;
                ++_finish;
                return pos;
            }
            // 返回删除数据的下一个数据
            // 方便解决:一边遍历一边删除的迭代器失效问题
            iterator erase(Iterator pos)
            {
                // 挪动数据进行删除
                iterator begin = pos + 1;
                while (begin != _finish) {
                    *(begin - 1) = *begin;
                    ++begin;
                }
                --_finish;
                return pos;
            }
    
            iterator begin() { return _start; }
            iterator end() { return _finish; }
            const_iterator cbegin() const { return _start; }
            const_iterator cend() const { return _finish; }
    
            private:
                iterator _start; // 指向数据块的开始
                iterator _finish; // 指向有效数据的尾
                iterator _endofstorage; //指向存储文件的尾
    
        };
    }
  • 相关阅读:
    python os 用法(转)
    caffe for python
    day20-Python运维开发基础(装饰器 / 类中的方法 / 类的方法变属性)
    day19-Python运维开发基础(类的魔术方法)
    day18-Python运维开发基础(单继承 / 多继承 / 菱形继承、类的多态)
    day17-Python运维开发基础(类的封装 / 对象和类的相关操作、构造方法)
    day16-Python运维开发基础(os / os.path / shutil模块)
    Linux 创建网卡子接口
    day15-Python运维开发基础(json序列化模块、random / time / zipfile / OS模块函数)
    day14-Python运维开发基础(内置函数、pickle序列化模块、math数学模块)
  • 原文地址:https://www.cnblogs.com/Kaniso-Vok/p/13756218.html
Copyright © 2020-2023  润新知