C++之STL-vector+模拟实现

目录

一、vector的介绍和基本使用的方法

1.1 介绍

1.2 迭代器

1.3 vector的一些基本使用

1.3.1 构造函数

1.3.2 迭代器

1.3.3 有关容量的接口

1.3.4 增删查改

二、模拟实现vector

2.1 成员变量

 2.2 迭代器的实现

2.3 容量接口的实现

2.3.1 size函数实现

2.3.2 capacity函数实现

2.3.3 reserve函数实现       

2.3.4 resize函数的实现

2.4 增删查改接口的实现

2.4.1 operator[] 函数重载的实现

2.4.2 insert函数的实现      

2.4.3 erase函数的实现

2.4.4 swap函数的实现

2.4.5 pop_back函数实现

2.4.6 push_back函数的实现

2.5 构造函数和析构函数的实现 

2.5.1 有参构造 vector(int n, const T& value = T())

2.5.2 无参构造 vector()

2.5.3 拷贝构造 vector(const vector& v)

2.5.4 赋值重载

2.5.5 用迭代器区间进行构造

2.5.6 析构函数

 2.6 完整代码


一、vector的介绍和基本使用的方法

1.1 介绍


vector 是 C++ 标准库中的一个容器,提供了动态数组的功能。它可以根据需要动态增长或减少其大小,并且支持随机访问元素(类似我们初学的数组,但却有很多的不同),以下是其的几个特性:

  1. 动态大小: 允许在运行时动态增加或减少其大小,而无需在编译时指定数组大小。

  2. 随机访问: 可以使用索引随机访问元素。这意味着可以通过索引直接访问任何位置的元素,而不必像链表那样从头开始遍历。

  3. 连续存储: 元素在内存中是连续存储的,这样就可以利用 CPU 缓存的局部性原理,提高访问效率

  4. 自动管理内存: 自动管理动态数组的内存分配和释放,使得程序员无需手动处理内存管理问题。

  5. 模板类: vector 是一个模板类,可以存储任意类型的元素。这意味着可以创建包含整数、浮点数、自定义对象等任意类型的vector

1.2 迭代器

       简单介绍完了vector这个新朋友后,我们就不得不来好好聊聊六大组件之一的迭代器(接下来很多场景都会使用它),那么迭代器是什么呢?
       迭代器提供了一种统一的访问数据结构(如容器、数组、集合等)元素的方式,使得我们能够以统一的接口遍历和操作数据结构中的元素,而不必关心底层数据结构的实现细节。在 C++ 中,迭代器是一种类似于指针的对象,它允许我们遍历容器中的元素并访问它们。迭代器通常具有以下特性:

  1. 遍历元素: 通过迭代器,我们可以逐个遍历容器中的元素,访问它们并对其进行操作。

  2. 随机访问: 某些迭代器(如 std::vector 的迭代器)支持随机访问,允许我们以常数时间访问容器中的任意元素。

  3. 泛型性: 迭代器是泛型的,可以用于各种不同类型的数据结构,包括数组、链表、集合等。

  4. 迭代器范围: 迭代器通常表示一个范围,包括起始位置和终止位置。这样,我们可以通过两个迭代器来表示一个范围,例如在排序算法中指定待排序数组的范围。

1.3 vector的一些基本使用

1.3.1 构造函数

  1. 默认构造
    vector<int> s;//类似于这样的定义会调用默认构造,但是这是个空对象,没有内容
  2. 有参构造(介绍几个常用的)

    vector<int> arr(10,1);//构造并在对象中初始化10个1
    vector<int> arr(10,1);
    vector<int> brr(arr);//拷贝构造
    vector<int> arr(10,1);
    vector<int> brr(arr.begin(),arr.end());//用迭代器区间进行初始化

1.3.2 迭代器

  1. begin()  用对象显示调用这个函数会返回第一个数据位置的iterator/const iterator。
  2. end()  用对象显示调用这个函数会返回最后一位数据位置的iterator/const iterator。
  3. rbegin() 反向迭代器,显示调用这个函数会返回最后一位数据位置的iterator/const iterator。
  4. rbegin() 反向迭代器,显示调用这个函数会返回第一个数据位置的iterator/const iterator。

1.3.3 有关容量的接口

  1. size 获取数据个数
  2. capacity 获取容量大小
  3. empty 判断是否为空
  4. resize 更改当前对象的size
  5. reserve 更改当前对象容量

1.3.4 增删查改

  1. push_back 尾插
  2. pop_back 尾删
  3. insert 在pos位置之前插入数据
  4. erase 删除pos位置的数据
  5. swap 交换两个vector的数据域空间
  6. operator[] 函数重载,使得我们可以像访问数组一样的访问vector。

二、模拟实现vector

终于来到我们的重头戏了,话不多说,直接开始。(注意:vector是需要使用模板的哦)。

2.1 成员变量

       主要的i成员就三个,_start指向数据块的开始,_finish指向有效数据的尾,_endOfStorage指向存储容量的尾。c++11支持声名时带缺省相当于会进行默认初始化。
 

private:

        iterator _start = nullptr; // 指向数据块的开始

        iterator _finish = nullptr; // 指向有效数据的尾

        iterator _endOfStorage = nullptr; // 指向存储容量的尾

 2.2 迭代器的实现

因为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;
            }

记住在vector中是有两个迭代器的,一个普通的,一个const的。

2.3 容量接口的实现

2.3.1 size函数实现

个函数比较容易只需要用_finish-_start就可以了

            size_t size() const
            {
                return _finish - _start;
            }

2.3.2 capacity函数实现

            size_t capacity() const
            {
                return _endOfStorage - _start;
            }

2.3.3 reserve函数实现       

       该函数有一个参数 n,根据要求当n>capacity时该函数才会发挥作用,所以我们要做一个判断。根据描述,我需要开辟一个新空间,那很明显就会涉及原空间数据拷贝的问题,这里我们不能使用memcpy进行拷贝,当我们模板参数T为stirng等类型的话,这将会是一个浅拷贝,具体参考下图。

       那么我们该如何去处理这个问题呢,其实我们只需遍历一边直接用赋值语句就可以,内置类型不必说,自定义类型会去调用其拷贝构造,帮助我们完成深拷贝。但是扩容拷贝之后又会产生新问题,因为扩容之后_start指向的是新空间的地址,而_finish和_endOfStorage都未更新,所以我们要先对这些数据进行更新,先看下面的更新代码。

_start = tmp;//tmp为新空间的地址
_finish = _start + size();
_endOfStorage = _start + size();

       当你自己试过你就会发现_finish出问题了,他会是个野指针,这是为什么呢,明明逻辑是对的呀。其实问题就出在size()上,size = _finish - _start 。用数学代数的方法,不难发现_finishi确实没变,那怎么办呢,总不能修改size吧,其实只要把未变化的size先记录下来就好了。
 

            void reserve(size_t n)
            {
                if (n > capacity())
                {
                    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;
                    _finish = _start + sz;
                    _endOfStorage = _start + n;
                }
            }

2.3.4 resize函数的实现

       根据描述,当传入参数 n 小于capacity时,就直接缩小size的值就好,当n大于capacity时,就需要扩容了,但这里我们可以复用我们reserve函数进行扩容,然后使用传入参数对扩容的size进行初始化,是不是很方便。

            void resize(size_t n, const T& value = T())
            {
                if (n < size())
                {
                    _finish = _start + n;
                }
                else
                {
                    reserve(n);
                    while (_finish < _start + n)
                    {
                        *_finish = value;
                        ++_finish;
                    }
                }
            }

2.4 增删查改接口的实现

2.4.1 operator[] 函数重载的实现

先来个简单的开开胃。(注意别忘了实现const迭代器的接口哦)

            T& operator[](size_t pos)
            {
                assert(pos < size());
                return _start[pos];
            }

            const T& operator[](size_t pos)const
            {
                assert(pos < size());
                return _start[pos];
            }

2.4.2 insert函数的实现      

       插入数据首先要考虑的问题就是要不要扩容,所以这里我们需要做一个判断若_finish==_endOfStorage则调用reserve进行扩容,但是扩容之后会产生问题,pos指向的是旧空间插入位置的地址,但是新空间的插入地址我们不知道啊,所以这里就需要我们提前记录pos相对于_start位置的偏移量。

            iterator insert(iterator pos, const T& x)
            {
                assert(pos >= _start && pos <= _finish);
                if (_finish == _endOfStorage)
                {
                    int len = pos - _start;
                    reserve(capacity() == 0 ? 4 : capacity() * 2);
                    pos = _start + len;
                }
                iterator end = _finish - 1;
                while (end >= pos)
                {
                    *(end+1) = *end;
                    end--;
                }
                *pos = x;
                _finish++;
                return pos;
            }

这里为什么insert有返回值我们放在erase说明。
 

2.4.3 erase函数的实现

       根据描述erase函数是要删除pos位置的值,删除倒还好说,挪动覆盖数据就可以了,但是这里会涉及一个叫做迭代器失效的问题,先简单介绍一下什么是迭代器失效。
       迭代器失效是指在进行某些操作后,原本有效的迭代器不再指向预期的元素位置,或者完全失去了指向元素的能力。这里失效很明显,你删除了数据,那原本的迭代器就失效了,
具体可以跑一下下面删除对象中所有偶数的代码:

//删除对象中的所有偶数
std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	auto it = v.begin();
	while (it != v.end())
	{
	vs2019进行强制检查,erase以后认为it失效了,不能访问,访问就报错
	if (*it % 2 == 0)
	{
	v.erase(it);
	}

	++it;
	}

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

       迭代器失效是无法避免的,但是我们可以去避免让其对我们代码产生影响,所以erase有一个返回值,其会返回删除位置下一位的迭代器,insert则是返回新插入位置的迭代器。函数还需要进行一下断言,pos要大于_start且要小于_finish。

            iterator erase(iterator pos)
            {
                assert(pos >= _start && pos < _finish);
                iterator tmp = pos + 1;
                while (tmp < _finish)
                {
                    *(tmp - 1) = *tmp;
                    tmp++;
                }
                _finish--;
                return pos;
            }

2.4.4 swap函数的实现

这个比较容易,我们可以复用库中的swap函数。

           void swap(vector<T>& v)
            {
                std::swap(_start, v._start);
                std::swap(_finish, v._finish);
                std::swap(_endOfStorage, v._endOfStorage);
            }

2.4.5 pop_back函数实现

直接复用erase就ok。

            void pop_back()
            {
                erase(end()-1);
            }

2.4.6 push_back函数的实现

这个也是直接复用就行了(是不是很爽)。

            void push_back(const T& x)
            {
                insert(end(), x);
            }

2.5 构造函数和析构函数的实现 

2.5.1 有参构造 vector(int n, const T& value = T())

            vector(int n, const T& value = T())
            {
                reserve(n);
                while (_finish < _start + n)
                {
                    *_finish = value;
                    ++_finish;
                }
            }

这里需要调用reserve对整体进行一个初始化。

2.5.2 无参构造 vector()

 vector()
{}

2.5.3 拷贝构造 vector(const vector<T>& v)

            vector(const vector<T>& v)
            {
                reserve(v.capacity());
                for (auto& e : v)
                {
                    push_back(e);
                }
            }

2.5.4 赋值重载

            vector<T>& operator= (vector<T> v)
            {
                swap(v);
                return *this;
            }

2.5.5 用迭代器区间进行构造

            template<class InputIterator>

            vector(InputIterator first, InputIterator last)
            {
                while (first != last)
                {
                    push_back(*first);
                    first++;
                }
            }

template<class InputIterator> 表示这个函数模板接受一个类型为 InputIterator 的参数。这个模板可以用来生成各种接受不同类型迭代器的函数。

2.5.6 析构函数

            ~vector()
            {
                delete[] _start;
                _start = _endOfStorage = _finish = nullptr;
            }

 2.6 完整代码

#pragma once
#include  <assert.h>
#include  <string.h>
namespace bit

{

    template<class T>

    class vector

    {

    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

            vector()
            {}

            vector(int n, const T& value = T())
            {
                reserve(n);
                while (_finish < _start + n)
                {
                    *_finish = value;
                    ++_finish;
                }
            }

            template<class InputIterator>

            vector(InputIterator first, InputIterator last)
            {
                while (first != last)
                {
                    push_back(*first);
                    first++;
                }
            }

            vector(const vector<T>& v)
            {
                reserve(v.capacity());
                for (auto& e : v)
                {
                    push_back(e);
                }
            }

            vector<T>& operator= (vector<T> v)
            {
                swap(v);
                return *this;
            }

            ~vector()
            {
                delete[] _start;
                _start = _endOfStorage = _finish = nullptr;
            }

            // capacity

            size_t size() const
            {
                return _finish - _start;
            }

            size_t capacity() const
            {
                return _endOfStorage - _start;
            }

            void reserve(size_t n)
            {
                if (n > capacity())
                {
                    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;
                    _finish = _start + sz;
                    _endOfStorage = _start + n;
                }
            }

            void resize(size_t n, const T& value = T())
            {
                if (n < size())
                {
                    _finish = _start + n;
                }
                else
                {
                    reserve(n);
                    while (_finish < _start + n)
                    {
                        *_finish = value;
                        ++_finish;
                    }
                }
            }



            ///access///

            T& operator[](size_t pos)
            {
                assert(pos < size());
                return _start[pos];
            }

            const T& operator[](size_t pos)const
            {
                assert(pos < size());
                return _start[pos];
            }



            ///modify/

            void push_back(const T& x)
            {
                insert(end(), x);
            }

            void pop_back()
            {
                erase(end()-1);
            }

            void swap(vector<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);
                if (_finish == _endOfStorage)
                {
                    int len = pos - _start;
                    reserve(capacity() == 0 ? 4 : capacity() * 2);
                    pos = _start + len;
                }
                iterator end = _finish - 1;
                while (end >= pos)
                {
                    *(end+1) = *end;
                    end--;
                }
                *pos = x;
                _finish++;
                return pos;
            }

            iterator erase(iterator pos)
            {
                assert(pos >= _start && pos < _finish);
                iterator tmp = pos + 1;
                while (tmp < _finish)
                {
                    *(tmp - 1) = *tmp;
                    tmp++;
                }
                _finish--;
                return pos;
            }

    private:

        iterator _start = nullptr; // 指向数据块的开始

        iterator _finish = nullptr; // 指向有效数据的尾

        iterator _endOfStorage = nullptr; // 指向存储容量的尾

    };
    
}

相关推荐

  1. C++ STLvector模拟实现

    2024-04-25 20:24:03       59 阅读
  2. C++ | vector模拟实现

    2024-04-25 20:24:03       27 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-25 20:24:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-25 20:24:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-25 20:24:03       82 阅读
  4. Python语言-面向对象

    2024-04-25 20:24:03       91 阅读

热门阅读

  1. Pango

    2024-04-25 20:24:03       37 阅读
  2. C# Bitmap实现角度旋转

    2024-04-25 20:24:03       31 阅读
  3. sa-token整合oauth2

    2024-04-25 20:24:03       32 阅读
  4. 理财投资-认识期货

    2024-04-25 20:24:03       33 阅读
  5. 【代码随想录】day45

    2024-04-25 20:24:03       27 阅读
  6. alert() 函数功能

    2024-04-25 20:24:03       29 阅读
  7. EureKa详解

    2024-04-25 20:24:03       31 阅读
  8. 6-137 高精度小数

    2024-04-25 20:24:03       37 阅读