C++ vector模拟实现

在完成了string类模拟后,我们已经了解了许多函数的用法,那么vector的函数用法与string有很多相似的地方

vector的成员变量

在vector中,有着三个成员变量,分别是:

掌管容器的头地址: _start

掌管容器中有效数据的尾: _finish

掌管容器的尾: _end_of_storage

 

默认成员函数

构造函数

构造函数1

vector支持无参构造函数,也就是创建一个vector的空容器,那么该空容器的三个成员变量都为空指针

        //构造函数1
        vector()
            :_start(nullptr)
            ,_finish(nullptr)
            ,_end_of_storage(nullptr)
        {}

构造函数2 

vector也支持将n和val作为参数的构造函数,也就是构造一个含有n个val的的容器,那么在这种构造函数下,就需要使用到reserve函数开辟空间,然后再用for循环将val的值一个一个填进容器

        vector(size_t n, const T& val = T())
            :_start(nullptr)
            , _finish(nullptr)
            , _end_of_storage(nullptr)
            {
                reserve(n);//开辟n个空间的容器
                for (size_t i = 0; i < n; ++i)
                {
                    push_back(val);//利用push_back函数将val尾插进容器
                }
        }

构造函数3 

vector还支持在一段迭代器区间内构造,该区间可以是别的迭代器区间,也就是说该区间的类型是不确定的,所以我们需要制作成一个函数模板用来接收不同类型的迭代器区间

        template<class InputIterator>//模板函数
        vector(InputIterator first, InputIterator last)
        :_start(nullptr)
	    , _finish(nullptr)
	    , _endofstorage(nullptr)
        {
            while (first != last)
            {
                push_back(*first);//将[first,last)内的数据尾插进容器种
                ++first;
            }
        }

 需要注意的是:

*理论上提供了vector(size_t n, const T& value = T())之后

* vector(int n, const T& value = T())就不需要提供了,但是对于:

* vector<int> v(10, 5);

* 编译器在编译时,认为T已经被实例化为int,而编译器会默认其为int类型

* 就不会走vector(size_t n, const T& value = T())这个构造方法,

* 最终选择的是:vector(InputIterator first, InputIterator last)

* 所以编译器会将InputIterator实例化成int,从而导致在后续的解引用first时,也就是会将int类型进行解引用,但是int类型并不能这样从而导致报错

所以需要增加一个int类型参数的构造函数:

        vector(int n, const T& val = T())
            :_start(nullptr)
            , _finish(nullptr)
            , _end_of_storage(nullptr)
        {
            reserve(n);
            for (int i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }
      

拷贝构造函数

在拷贝构造函数种,通常先开辟一个空间,然后将数据拷贝至新开辟的空间,所以会导致许多同学会第一时间想到memcpy函数,但是在vector中并不能使用memcpy函数,否则会出现深浅拷贝的问题

        //拷贝构造
        vector(const vector<T>& v)
        {
            _start = new T[v.capacity()];
            for(size_t i = 0; i < v.size(); ++i)
            {
                _start[i] = v._start[i];
            }
            _finish = _start + v.size();
            _end_of_storage = _start + v.capacity();
        }

 举一个例子,在拷贝构造一个包含string类的vector容器中:

 每个string类都会指向自己的字符串:

 但是,在进行memcpy之后,

 

尽管memcpy会将string类存在新的空间里,但是string类里的字符串地址还是会指向之前容器的字符串地址,也就是说两个vector的string类中都指向同一个字符串地址,那么这时候一旦进行析构就会将一个地址析构两次从而报错。

这种情况也适用与二维数组的情况,vector中每一个vector中的_start都指向同一个地址。

那么编译器是怎么处理这种情况呢?

编译器会通过调用string类的赋值运算符重载函数进行深拷贝,也就是:

for (size_t i = 0; i < v.size(); i++)
{
    _start[i] = v[i];//调用string类的赋值运算符重载函数进行深拷贝
}

 所以综上所述,如果vector容器内存的是内置类型或者类似于Date的浅拷贝的自定义类型的话,使用memcpy函数是可以的,如果存的是需要深拷贝的各种类型,则不能使用memcpy,所以我们通常不会使用memcpy函数

析构函数

//析构函数
~vector()
{
	if (_start) //避免对空指针进行释放
	{
		delete[] _start; //释放容器存储数据的空间
        _start = _finish = _end_of_storage = nullptr;//将三个变量置空
	}
}

迭代器函数

迭代器函数分为两种,一种是可以进行修改的,一种是不可以进行修改的。

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

 容量大小相关函数

 reserve函数

vector的reserve函数与string的reserve函数思路相同:

如果n>capacity,那么就进行扩容

如果n<capacity,那么啥也不做

        //扩容函数
        void reserve(size_t n)
        {
            if (n > capacity())//判断n是否大于容量
            {
                size_t sz = size();//记录有效数据个数
                T* tmp = new T[n];//开辟空间
                if (_start)//判断容器是否为空
                {
                    for (size_t i = 0; i < sz; i++) 
                    {
                        tmp[i] = _start[i];//将数据转移到新开辟的空间
                    }
                    delete[] _start;//删除旧空间
                }
                _start = tmp;//将_start等函数重新指向新开辟的空间
                _finish = tmp + sz;//有效空间的尾
                _end_of_storage = tmp + n;//容器的尾
            }
        }

resize函数

该函数使用了一个缺省值,也就是说

开辟n个空间,然后初始化成val

假如不填val,那么将默认为默认构造函数的值

然后

n>capacity,那么就进行开空间+初始化

n<capacity,不会将容量缩小,而是直接改变_finish的指向,将size缩小到n即可。

        void resize(size_t n,T val = T())
        {
            if (n < size())
            {
                _finish = _start+n;
            }
            else 
            {
                if (n > capacity())
                {
                    reserve(n);
                }
                while (_finish != _start + n)
                {
                    *_finish = val;
                    ++_finish;
                }
            }
        }

size函数

通过使用地址差即可

        //检查有效大小
        size_t size() const
        {
            return _finish - _start;
        }

capacity函数

通过使用地址差即可

        //检查容量
        size_t capacity() const
        {
            return _end_of_storage - _start;
        }

empty函数

判断地址是否相同即可判空,是则返回true,否则返回false

bool empty()const
{
	return _start == _finish;
}

 修改容器相关函数

push_back函数

首先需要先判断容器是否已满,已满则需要扩容,然后再进行尾插操作

        //尾插
        void push_back(const T& x)      
        {
            if (_finish == _end_of_storage)//判断容器是否已满
            {
                reserve(capacity() == 0 ? 4 : capacity() * 2);//已满则进行扩容
            }
            *_finish = x;//将x尾插进容器
            ++_finish;//_finish指向的位置往后挪
        }

pop_back函数

 首先判断容器是否为空,然后再进行操作

        void pop_back()
        {
            if(!empty())//判断容器是否为空
            {
                --_finish;//不为空则直接改变_finish的指向
            }
        }

insert函数

先判断容器是否已满,否则进行扩容,需要注意的是,假如需要扩容,那么就会出现迭代器失效的问题,因为pos是指向原容器的地址,那么扩容之后容器的地址将更新,此时pos的地址还未更新,所以会报错,那么此时就需要更新pos,将pos指向正确的位置

        void insert(iterator pos, const T& val)
        {
            if (_finish == _end_of_storage)//判断容器是否已满
            {
                size_t len = pos - _start;//接收pos地址与头地址的差

                reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容

                pos = _start + len;//将pos更新为新容器的相对地址
            }
            //新定义一个end变量,用来辅助将pos之后的数据往后挪动
            iterator end = _finish -1;//指向最后一个数据
            while (end >= pos)
            {
                *(end + 1) = *end;
                --end;
            }
            *pos = val;//将数据插入pos位置
            ++_finish;
        }

erase函数

先判断容器是否为空,然后再进行操作,直接将pos之后的数据往前挪动一位即可,直接覆盖,然后更新_finish的指向

        void erase(iterator pos)
        {
            assert(!empty()); //判断容器是否为空
            //然后将pos之后的数据往前挪,直接进行覆盖
            iterator start = pos + 1;
            while (start != _finish)
            {
                *(start - 1) = *start;
                ++start;
            }
            --_finish;//更新_finish的指向
        }

swap函数

直接使用库中的swap函数进行交换即可,需要注意的是,需要加上std::,告诉编译器从全局库中寻找swap函数,否则会存在就近原则,自己调用自己的swap,进入死循环

//交换两个容器的数据
void swap(vector<T>& v)
{
	//交换容器当中的各个成员变量
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
}

访问容器相关函数

operator[]函数

使容器支持以[下标]的方式对容器中的数据进行访问,还需要加多一个const,以便于获取到的数据只允许“只读”的操作

        //重构造[]
        T& operator[](size_t pos)
        {
            return _start[pos];
        }

        //带const
        const T& operator[](size_t pos) const
        {
            return _start[pos];
        }

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
引用和提供了关于实现vector的两种方法。其中,引用展示了一个使用reserve和push_back方法的示例,而引用展示了一个使用new和memcpy函数的示例。这两种方法都是常见的实现vector的方式。 在第一种方法中,通过reserve函数可以预留足够的内存空间,然后使用push_back函数逐个将元素添加到vector中。这种方法的好处是可以避免不必要的内存重分配,提高了效率。 而第二种方法使用new操作符在堆上分配内存空间,并使用memcpy函数将已有的vector对象的数据复制到新的内存空间中。通过这种方式,可以实现深拷贝,即两个vector对象拥有独立的内存空间。这种方法的好处是可以在不修改原始vector对象的情况下创建一个新的vector对象。 除了以上两种方法,还可以使用其他方式实现vector类。例如,可以使用动态数组来实现vector的底层数据结构,然后通过成员函数实现vector的各种操作,如增加、删除、查找等。 总结来说,c语言模拟实现vector的关键是动态内存管理和对元素的增删改查操作。可以使用预留空间和逐个添加元素的方式,也可以使用动态数组和复制数据的方式来实现vector类。具体的实现方式可以根据需求和实际情况选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++——vector模拟实现](https://blog.csdn.net/weixin_49449676/article/details/126813526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值