vector

本文介绍了C++STL中的vector容器的实现原理,包括动态扩容策略和成员函数的使用。同时讨论了模拟实现vector时需要注意的深拷贝问题,以及迭代器失效的原因和解决方案。还提供了两个算法题示例:只出现一次的数字和电话号码的字母组合的思路与代码实现。
摘要由CSDN通过智能技术生成

目录

介绍

模拟实现

注意点

迭代器失效

代码

算法题示例

只出现一次的数字

题目

思路

代码

电话号码组合

题目

思路

代码


介绍

stl中的vector是表示可变大小数组的序列容器
  • 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效
  • 但又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理
本质讲,vector使用动态分配数组来存储它的元素
  • 当新元素插入时候,这个数组需要被重新分配大小
  • 扩容方法 -- 分配一个新的数组,然后将全部元素移到这个数组
vector分配空间策略 -- vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大
  • 不同的库采用不同的策略
  • 但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的
  • 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效

成员函数的参数基本都是使用迭代器,而不是下标来表示位置,且成员变量也是指针

模拟实现

注意点

  • 构造函数,以及为了支持不同对象迭代器的模板构造函数
  • reserve(支持对象的深拷贝 -> 赋值重载来拷贝),resize(匿名对象做缺省值,防止元素是自定义类型)
  • 反正就是要注意写的T有可能是自定义类型的 -> 一定要进行深拷贝,对于三个成员变量的处理!!!
  • insert和erase的迭代器失效问题(用返回值解决)

迭代器失效

失效的原因有很多种
  • 一种是 -- 迭代器底层对应指针 所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)
  • 扩容就会导致野指针的问题
  • 一种是 -- 使用了失效的迭代器
  • 例如erase后,该位置之后的迭代器都会失效(虽然我现在还不明白为啥要这么搞,g++下是可以跑的,但vs下好像做了封装,反正判定它就是失效了,一旦使用就报错,需要用返回值来更新迭代器
  • 但明明返回的还是原来传过来的参数,但可能这样意义不同了吧,一个是失效的迭代器,一个是下一位置的迭代器,不懂为什么一定要这样搞 = ( 

代码

#include <iostream>
#include <assert.h>
#include <string.h>
#include <string>

using namespace std;

namespace bit
{
    template <class T>
    class myvector
    {
    public:
        // Vector的迭代器是一个原生指针
        typedef T *iterator;
        typedef const T *const_iterator;

        iterator begin()
        {
            return _start;
        }
        iterator end()
        {
            return _finish;
        }
        const_iterator begin() const
        {
            return _start;
        }
        const_iterator end() const
        {
            return _finish;
        }

        // construct and destroy

        myvector()
            : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){};
        myvector(int n, const T &value = T()) // 如果这里是size_t类型的n
                                              // 当传入参数都是整数时,会认为俩都是int,优先匹配两个参数是同样类型的模板函数
                                              // 但int无法解引用,就会报错
                                              // 所以这里的类型改为int,就可以匹配上了
            : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr)
        {
            _start = new T[n];
            for (size_t i = 0; i < n; i++)
            {
                _start[i] = value;
            }
            _finish = _endOfStorage = _start + n;
        }

        template <class InputIterator> // 模板函数,为了支持其他类型的迭代器(string等)
        myvector(InputIterator first, InputIterator last)
        {
            int n = last - first;
            _finish = _start = new T[n];
            _endOfStorage = _start + n;
            for (auto begin = first; begin != last; ++begin)
            {
                push_back(*begin);
            }
        }

        myvector(const myvector<T> &v)
            : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr)
        {
            reserve(v.capacity());

            // auto it = v.cbegin();
            // for (; it != v.cend(); ++it)
            // {
            //     push_back(*it);
            // }

            for(auto it: v){
                push_back(it);
            }
        }
        myvector<T> &operator=(myvector<T> v) // v传进来首先会进行拷贝构造,所以形参和实参空间不同
        {
            swap(v);
            return *this;
        }

        ~myvector()
        {
            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)
        {
            if (n > capacity())
            {
                int sz = size();
                T *tmp = new T[n];
                // memcpy(tmp,_start,sizeof(T)*sz);  //如果vector中的元素是自定义类型(string/vector),那这里就是浅拷贝(按字节拷贝)
                // 为了进行深拷贝,可以去赋值重载(内部是深拷贝)(拷贝构造不行,无法调用)
                for (size_t i = 0; i < sz; ++i)
                {
                    tmp[i] = _start[i];
                }
                delete[] _start;
                _start = tmp;
                _finish = _start + sz;
                _endOfStorage = _start + n;
            }
        }
        void resize(size_t n, const T &value = T()) // T() -- 匿名对象(调用默认构造)(对于自定义类型/对象)
        // 对于内置类型,c++在模板出来之后,对内置类型做了升级,也可以调用默认构造了
        {
            if (n > size())
            {
                reserve(n);
                for (iterator begin = _finish; begin != _endOfStorage; begin++)
                {
                    *begin = value;
                }
                _finish = _start + n;
            }
            else
            {
                _finish = _start + n;
            }
        }

        T &operator[](size_t pos)
        {
            assert(pos >= 0 && pos < size());
            return *(_start + pos);
        }
        const T &operator[](size_t pos) const
        {
            assert(pos >= 0 && pos < size());
            return *(_start + pos);
        }

        void push_back(const T &x)
        {
            if (size() == capacity())
            {
                reserve(size() + 1);
            }
            *(_finish++) = x;
        }
        void pop_back()
        {
            _start = erase(_start);
        }

        void swap(myvector<T> &v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endOfStorage, v._endOfStorage);
        }
        iterator insert(iterator pos, const T &x)
        {
            assert(pos >= _start && pos <= _finish);
            int len = pos - _start;
            if (size() == capacity())
            {
                reserve(size() + 1);
            }
            pos = _start + len; // 如果扩容了,原pos指向的空间就已经被释放掉了,所以需要更新pos
            iterator end = _finish - 1;
            while (end >= pos)
            {
                *(end + 1) = *end;
                --end;
            }
            *pos = x;
            ++_finish;
            return pos; // 防止外部传入的pos失效,返回值可以让它更新
        }
        iterator erase(iterator pos)
        {
            assert(pos >= _start && pos < _finish);
            for (iterator begin = pos + 1; begin != _finish; ++begin)
            {
                *(begin - 1) = *begin;
            }
            _finish--;
            return pos; // vs中只要使用了原pos,就会报错
            // g++没有管,但尽量不要使用,虽然这里有返回值来进行迭代器更新,但难免会有疏漏(不同平台上的行为不同,所以不要再使用)
        }

    private:
        iterator _start;        // 指向数据块的开始
        iterator _finish;       // 指向有效数据的尾
        iterator _endOfStorage; // 指向存储容量的尾
    };

};

算法题示例

只出现一次的数字

题目

137. 只出现一次的数字 II - 力扣(LeetCode)

 

思路

这里运用了位运算(真的很难想到啊呜呜)

  • 题目不是说除了一个数字之外,其他都是三个绑定在一起出现吗
  • 而且每一位都是 0 /  1 ,所以必定1 / 0 俩数字里面,会有一个出现的次数不是3的倍数,而那个数字就是要找元素的该位数字
  • 找到该位后,只要拿到所在的位数,就能还原出该位代表的十进制大小

代码

   int singleNumber(vector<int>& nums) {
        int ans = 0;
        for (int i = 0; i < 32; ++i) { //遍历所有元素的每一位
            int tmp=0;
            for(auto c:nums){
                tmp+=((c>>i)&1); //得到所有元素第i位的和
            }
            if(tmp%3==1){ //如果该总和不是3的倍数,说明该位是1(因为0不影响大小)
                ans|=(1<<i);  //将ans的该位还原出来
            }
        }
        return ans;
    }

电话号码组合

题目

17. 电话号码的字母组合 - 力扣(LeetCode)

思路

按照这样的规律拿到序列,很像将数字代表的字母展开,然后进行深搜

  • 为了可以返回后可以成功拿到新的序列,需要将" 此时已经有的序列 "当做参数,不然return后就没辣
  • 除此之外,因为字母顺序对应的是数字顺序,所以也需要将" 此时利用的数字是第几位 "记录下来

代码

void work(string arr[],string digits,int level,string ret,vector<string>& v){
        if(level==digits.size()){ //序列字符数=数字位数,说明已经遍历完成一次
            v.push_back(ret);
            return;
        }
        int count=digits[level]-'0'; //拿到此时对应的数字
        for(int i=0;i<arr[count].size();++i){
            work(arr,digits,level+1,ret+arr[count][i],v); 
            //向已有序列加入对应数字的字母(返回后就可以拿到该数字对应的新的字母)
        }
    }
    vector<string> letterCombinations(string digits) {
        vector<string> ret; 
        if(digits!=""){
            string arr[]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
            work(arr,digits,0,"",ret); 
            //记录此时对应的数字是第几位 -- 0,以及此时序列是多少 -- " "
        }
        
        return ret;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值