【STL】模拟实现vector(详解)

news/2024/7/5 5:02:01

文章目录

  • 前言
  • vector的模拟实现
    • 一,搭建框架
    • 二,实现构造函数
    • 三,构造的其他方式
      • 传统写法
        • 1.拷贝构造
        • 2. 重载赋值操作符
        • 3. 使用迭代器构造
        • 4. 初始化为N个val的vector
      • 现代写法
        • 1. 拷贝构造
        • 2. 赋值重载
    • 四,实现vector相关函数
      • 1. reserve函数
      • 2,resize函数
      • 3,push_back函数
      • 4,pop_back函数
      • 5,insert函数
      • 6,erase函数
    • 源码
  • 后记

前言

本文将讲述怎么模拟实现vector类,有些同学可能会问了,我要实现这个有什么用?会用不就可以了吗?

你没有错,但是我们通过模拟实现vector类可以帮助我们更加深入的了解它具体是怎么一回事?它的内部结构是怎么样的?如果以后我们写程序,碰到某个地方报错,也能很快排查出问题哦~

🕺作者: 迷茫的启明星
专栏:《C++初阶》

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

持续更新中~

vector的模拟实现

一,搭建框架

我们首先把大概框架搭建出来,就像打铁,要先打个大概的模子再细细锤炼,它和string的不同在于,vector 是基于连续内存的动态数组,而 string 可以是基于指针或数组的动态字符序列,所以vector 的成员函数是三个指针:指向数组的指针 start_、指向最后一个元素的下一个位置的指针 finish_,以及指向整个内存空间末尾的指针 end_of_storage_。那么我们现在就来搭建一下:

#pragma once
#include <assert.h>

namespace hxq
{
	template<class T>
    //这里要用模板,因为数组不只有一种类型
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;//返回首元素位置的指针
		}

		iterator end()
		{
			return _finish;//返回最后一个元素下一个位置的指针
		}
		//const类型
		const_iterator begin() const
		{
			return _start;//返回首元素位置的指针
		}

		const_iterator end() const
		{
			return _finish;//返回最后一个元素下一个位置的指针
		}
        
		~vector()//析构函数,释放资源
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

		size_t capacity() const
		{
            //返回vector空间大小
			return _end_of_storage - _start;
		}

        //const类型
		const T& operator[](size_t pos) const
		{
            //用于访问指定位置上的元素
            
			assert(pos < size());

			return _start[pos];
		}

		T& operator[](size_t pos)
		{
            //用于访问指定位置上的元素
            
			assert(pos < size());

			return _start[pos];
		}
		//返回vector的元素数量
		size_t size() const
		{
			return _finish - _start;
		}
        
		//返回第一个元素
		T& front()
		{
			assert(size() > 0);

			return *_start;
		}

        //返回最后一个元素
		T& back()
		{
			assert(size() > 0);

			return *(_finish - 1);
		}

	private:
		iterator _start;//指向数组的指针
		iterator _finish;//指向最后一个元素的下一个位置的指针
		iterator _end_of_storage;//指向整个内存空间末尾的指针
	};
}

二,实现构造函数

vector的构造函数非常简单,因为成员变量都是指针,所以我们只要将它们置空即可。

vector()
    :_start(nullptr)
        , _finish(nullptr)
        , _end_of_storage(nullptr)
    {}

三,构造的其他方式

和我们实现string一样,vector也有传统1写法和现代写法,他们实现的效果是一样的,不过是思维不一样而已,那么我们再来一起尝试写一写两种方式。

传统写法

1.拷贝构造

假设说是v1(v2)

步骤:

  1. 给v1开辟新空间
  2. 将v2的值按位拷贝给v1
  3. 将成员变量更新

所以我们可以这样写:

vector(const vector<T>& v)
{
    _start = new T[v.size()]; // v.capacity()也可以
    //这里还可以复用reserve函数,后面我们再实现它
    
    //思考题:
    //思考一下这里我们可以这样写吗?
    //memcpy(_start, v._start, sizeof(T)*v.size());
    
    for (size_t i = 0; i < v.size(); ++i)
    {
        _start[i] = v._start[i];
    }
    
    
    _finish = _start + v.size();
    _end_of_storage = _start + v.size();
}

思考题答案是不能

在实现vector拷贝构造函数时,使用memcpy是一种浅拷贝,它仅仅是复制了vector对象的数据成员_start所指向的内存区域,而没有复制_start所指向的数组中的每个元素内容。这意味着,如果原始vector发生变化,则新的vector也会受到影响,导致两个vector共享同一个数组。这样就会出现问题,当其中一个vector执行析构函数时,可能会释放已经被释放的内存区域,导致程序崩溃。

因此,为了避免这种问题,我们需要对vector的每个元素进行逐个拷贝,以实现深度拷贝。因此,上述代码采用了for循环来依次拷贝每个元素的方式,确保了新的vector与原始vector具有独立的数组空间和元素值。

拷贝构造还可以这样写,复用之后我们实现的函数实现。

vector(const vector<T>& v)
    :_start(nullptr)
    ,_finish(nullptr)
    ,_end_of_storage(nullptr)
    {
        reserve(v.size());//设置空间
        for (const auto& e : v)
        {
            push_back(e);//尾插
        }
    }

我们先能看懂大概意思就好,后面会一一实现这些函数的。

这里我们先将其初始化,再调用函数给它分配空间,调用函数插入数据(也就是拷贝数据了)。

2. 重载赋值操作符

假设是v1=v2

思路:

  • 直接利用另一个 vector 对象的值。在函数内部使用 swap() 函数交换了两个 vector 对象的 _start_finish_end_of_storage 成员变量,从而将右侧的 vector 对象中的数据成员拷贝到了当前对象中,完成了赋值操作
  • 为什么可以?因为它是传值,所以不影响原对象
vector<T>& operator=(vector<T> v)
{
    swap(v._start, _start);
    swap(v._finish, _finish);
    swap(v._end_of_storage, _end_of_storage);
    return *this;
}

3. 使用迭代器构造

当我们需要构造一个包含一定数量元素的 vector 对象时,可以使用 vector 的构造函数。其中,vector(InputIterator first, InputIterator last) 构造函数接受一对迭代器 [first, last) 作为参数,用于指定需要添加到 vector 中的元素范围。构造函数的实现方式是遍历迭代器范围,每次调用 push_back() 函数将迭代器指向的元素添加到当前 vector 对象的末尾。

需要注意的是,在构造函数中,vector 的 _start、_finish 和 _end_of_storage 成员变量都被设置为 nullptr,表示当前 vector 对象还没有分配任何内存空间。在循环遍历迭代器范围时,每次添加元素时,vector 内部的内存管理函数会自动动态地分配内存空间,并将新的元素存储在这个空间中。当元素个数超过了当前内存空间的大小时,vector 会自动扩充内存空间,以满足新增元素的需求。

	template <class InputIterator>
    vector(InputIterator first, InputIterator last)
    	:_start(nullptr)
        , _finish(nullptr)
        , _end_of_storage(nullptr)
    {
        while(first != last)
        {
            push_back(*first);
            ++first;
        }
    }

4. 初始化为N个val的vector

还有一种初始化方式是初始化为N个val的vector,它需要两个参数,一是数量n,二是val,这里实现时要注意的是,我们要考虑的不只是一种类型,所以这里要用到模板相关知识。

具体实现就简单多了,这只需要先初始化再申请空间,将val的值利用push_back函数插入即可,可能你会问:“为什么老是用这些函数?不自己再实现一遍呢?”,我们要注意的是,这些函数后文我们都会再实现的,但是这里为什么要用函数呢?是为了复用,你想想,要是我们写的代码中有一堆功能相似的代码,是不是屎山代码,极其不美观?我们要尽量避免大量重复代码这种情况。

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

现代写法

1. 拷贝构造

还有一种现代写法
这是一种有着资本主义或者说老板思维,让员工干活
具体怎么回事呢?
v2(v1)
我们可以新建一个vector对象,并且以nullptr来初始化它
的三个成员变量,然后再新建一个空间,这个新的空间以v1的值、以 迭代器的方式初始化,再使用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(const vector<T>& v)
    :_start(nullptr)
    , _finish(nullptr)
    , _end_of_storage(nullptr)
{
    vector<T> tmp(v.begin(), v.end());
    swap(tmp);
}

2. 赋值重载

和传统写法一样,不过这里可以复用前面的swap函数

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

四,实现vector相关函数

1. reserve函数

在 C++ 的 vector 中,reserve(size_type n) 是一个用于预留内存空间的函数。

具体来说,reserve() 函数接受一个参数 n,代表需要预留的内存空间大小。在调用 reserve() 函数后,vector 内部会分配一块大小为 n 的内存空间,但不会改变 vector 的元素个数(即 size() 函数返回值不受影响)。这意味着,我们可以通过 reserve() 函数提前为 vector 分配足够的内存空间,以避免频繁的动态扩容操作,从而提高程序的性能。

需要注意的是,尽管 reserve() 函数为 vector 分配了一定大小的内存空间,但并不意味着这些内存空间都被包含在 vector 的有效元素范围内。只有当这些内存空间真正被添加到 vector 对象中时,它们才被视为 vector 对象的一部分。因此,在调用 reserve() 函数后,如果需要使用新的内存空间,则应该通过 push_back()insert() 等函数添加元素,从而将新的元素存放到 vector 对象中。

需要注意的是:我们应该根据实际情况慎重考虑应该预留多少内存空间,以避免过度浪费内存资源。

实现思路:

  • 首先判断传入的参数是否大于已有空间
  • 需要扩容则开辟一块新的空间
  • 将所有元素拷贝到新空间
  • 再将成员变量更新

这里有个问题,将所有元素拷贝到新空间可以用memcpy函数吗?

回顾一下memcpy函数的使用:

当调用 memcpy() 函数时,它会将从源内存地址开始的 n 个字节的数据拷贝到目标内存地址中,也就是从 src 所指向的内存地址开始的 n 个字节的数据会被拷贝到 dest 所指向的内存地址中。

所以我们能使用它来拷贝数据吗?仔细思考就会发现,它不完全符合条件。

  1. std::vector 对象的内部存储结构是动态分配的,对其使用 memcpy() 拷贝时可能会出现未定义行为。

  2. std::vector 是 C++ 标准库提供的容器之一,它除了存储元素外还有管理容器大小、元素添加和移除等操作。使用 memcpy() 会绕过 std::vector 对象自身的管理和维护,可能会导致程序出现异常或者崩溃。

  3. std::vector 可能包含自定义类型的元素,而这些元素可能包含指针等资源,需要进行深拷贝才能正确复制。如果使用 memcpy() 直接拷贝内存,就无法正确处理这些元素的拷贝。

具体怎么回事?

在 C++ 中,std::vector 可以存储任意类型的元素。当这些元素是自定义类型时,往往会包含指针等资源。例如:

struct point {
    int x;
    int y;
};

struct line {
    point* p1;
    point* p2;
};

在上面的代码中,我们定义了 pointline 两个结构体,其中 line 的成员变量 p1p2 是指向 point 类型对象的指针。如果我们将 line 对象作为元素插入到 std::vector 容器中,那么这个容器就包含了指向堆内存中的 point 对象的指针。

当我们需要对 std::vector 容器进行拷贝操作时,如果直接对容器中的指针进行拷贝,就直接复制了指针的值,而没有复制指针所指向的对象。也就是说,新的 std::vector 容器中的元素指向的是旧的 std::vector 容器中的元素所指向的对象,造成两个 std::vector 容器共享同一块堆内存,无法正确地实现拷贝。

为了解决这个问题,需要对包含指针等资源的自定义类型进行深拷贝。深拷贝是一种将指针所指向的对象也复制一份的拷贝方式,以确保新的对象与旧的对象相互独立,不会共享同一个资源。对于包含指针等资源的自定义类型,可以通过重载拷贝构造函数和赋值运算符,或者使用智能指针等方法进行深拷贝。但是,这些方法不能与 memcpy() 函数兼容,如果直接使用 memcpy() 函数会导致指针指向错误的内存地址,无法正确地完成拷贝操作。

而且,在 C++ 中,std::vector 是一个动态数组容器,它使用动态分配的内存来存储元素。当我们向 std::vector 容器中添加元素时,如果容器已经占满了当前的内存空间,就会自动重新分配更大的内存,并将原有的元素复制到新的内存空间中。这个过程称之为“重新分配”(re-allocation)。

在重新分配内存时,std::vector 会自动调用元素类型的构造函数来构造新的元素对象,并调用元素类型的析构函数来销毁旧的元素对象。这个过程会保证 std::vector 容器中的元素始终是合法的、可访问的对象。

如果对 std::vector 使用 memcpy() 函数进行拷贝操作,由于 memcpy() 只会简单地复制一段内存的内容,不会触发任何构造函数或析构函数的调用,从而导致拷贝出的容器中的元素对象处于不合法的状态。这样的拷贝结果是未定义的,可能会导致程序运行出错、崩溃、甚至安全问题,因此不建议使用 memcpy() 来进行 std::vector 的拷贝操作。

代码:

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;
        _end_of_storage = _start + n;
    }
}

2,resize函数

在 C++ 中,std::vector 容器提供了 resize() 函数来改变容器的大小。该函数接受一个整型参数 newSize,表示容器调整后的大小。如果当前容器大小小于 newSize,则会在容器尾部插入一定数量的元素来扩充容器大小;如果当前容器大小大于 newSize,则会删除容器尾部的一定数量的元素来缩小容器大小。

具体来说,当 newSize 小于当前容器大小时,resize() 会将末尾多余的元素删除,释放内存空间;当 newSize 大于当前容器大小时,resize() 会在末尾插入必要数量的元素,并分配更多的内存空间。

代码:

void resize(size_t n, const T& val = T())
{
    if (n > capacity())
    {
        reserve(n);//如果空间少了就申请空间
    }

    if (n > size())
    {
        // 初始化填值
        while (_finish < _start + n)
        {
            *_finish = val;
            ++_finish;
        }
    }
    else
    {
        _finish = _start + n;
    }
}

3,push_back函数

在C++中,std::vector是一个动态数组容器,其大小可以动态地增加或减少。我们可以使用 push_back()函数向 std::vector 容器的尾部添加元素。

push_back() 函数接受一个参数,表示需要添加到容器尾部的元素值,该函数会将元素添加到容器的末尾,并自动调整容器的大小。如果容器已经占满了目前已分配的内存空间,那么会自动申请更多的内存空间,并将原来的元素对象移动到新的空间中保存。

它可以使用两种方式实现:

  1. 自给自足

    void push_back(const T& x)
    {
        if (_finish == _end_of_storage)
        {
            reserve(capacity() == 0 ? 4 : capacity() * 2);
        }
    
        *_finish = x;
        ++_finish;
    }
    
  2. 复用insert函数

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

4,pop_back函数

在C++中,std::vector 是一个动态数组容器,其大小可以动态地增加或减少。我们可以使用 pop_back() 函数从 std::vector 容器的尾部移除元素。

pop_back() 函数没有参数,它将从容器尾部移除一个元素。如果容器不为空,那么该函数会将容器的大小减少 1,并将最后一个元素对象从容器中销毁,释放内存空间。

思路:

这里我们可以不需要去删除元素,只要控制_finish的位置即可

代码:

void pop_back()
{
    assert(_finish > _start);
    --_finish;
}

5,insert函数

std::vector 是 C++ 标准库中的一个容器,可以快速、高效地对一组元素进行动态数组式的操作。其中,insert 函数是 std::vector 容器提供的一个成员函数,用于在指定位置插入一个或多个元素。

这里我们仅实现插入一个元素的函数

思路:

insert函数是用来在 std::vector 容器的任意位置插入一个新元素的函数。在该函数中,我们需要传入两个参数,分别是迭代器 pos 和元素 x

首先,我们需要通过迭代器找到要插入新元素的位置。对于 std::vector 容器而言,迭代器类型为 T*,即指向存储容器元素的指针。该函数的第一个参数 pos 就是要传入指向容器中某个元素的指针,表示将新元素插入到该元素前面或后面。

接下来,我们需要指定要插入的新元素的类型。由于 std::vector 容器中所有元素类型必须相同,因此我们使用模板参数 T 来表示要插入元素的类型,并将其定义为 const T& 常量引用。

最后,使用内部实现逻辑,我们可以通过比较迭代器 pos_start_finish_end_of_storage 三个指向容器起始、末尾和存储空间末尾位置的指针来确认 pos 的位置是否合法,从而防止越界访问或插入。然后,我们将新元素插入到指定位置,将原先位置及其后面的元素向后移动一位。

这样,我们就可以在 std::vector 容器中的任意位置插入一个新元素,从而添加、修改或更新容器中的元素。

iterator insert(iterator pos, const T& x)
{
    assert(pos >= _start);
    assert(pos <= _finish);

    if (_finish == _end_of_storage)
    {
        size_t 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;
}

6,erase函数

std::vector 是 C++ 标准库中的一个容器,可以快速、高效地对一组元素进行动态数组式的操作。其中,erase 函数是 std::vector 容器提供的另一个成员函数,用于删除指定位置或一段区间内的一个或多个元素。

这里我们仅实现删除一个元素的函数

思路:

在 STL 中,erase 函数返回的是指向被删除元素之后的一个元素的迭代器,而不是指向被删除元素的迭代器。这种设计是考虑到 erase 函数的常见用法,即在循环中删除符合条件的元素。

在这种情况下,如果返回指向被删除元素的迭代器,那么在执行 erase 操作后,迭代器会失效,即无法正常访问和操作,进而导致循环出错甚至崩溃。因此,STL 设计者规定返回指向被删除元素之后的一个元素的迭代器,避免使用已经失效的迭代器。

代码:

iterator erase(iterator pos)
{
    assert(pos >= _start);
    assert(pos < _finish);

    iterator begin = pos + 1;
    while (begin < _finish)
    {
        *(begin - 1) = *begin;
        ++begin;
    }

    --_finish;
    return pos;
}

源码

#pragma once
#include <assert.h>
#include <iostream>
namespace hxq
{
    template<class T>
        class vector
        {
            public:
            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()
                :_start(nullptr)
                    , _finish(nullptr)
                    , _end_of_storage(nullptr)
                {}
            //传统写法
            //拷贝构造
            // v2(v1)
            //        vector(const vector<T>& v)
            //        {
            //        	_start = new T[v.size()]; // v.capacity()也可以
            //        	//memcpy(_start, v._start, sizeof(T)*v.size());
            //        	for (size_t i = 0; i < v.size(); ++i)
            //        	{
            //        		_start[i] = v._start[i];
            //        	}
            //        	_finish = _start + v.size();
            //        	_end_of_storage = _start + v.size();
            //        }

            // v2(v1)
            //        vector(const vector<T>& v)
            //            :_start(nullptr)
            //            , _finish(nullptr)
            //            , _end_of_storage(nullptr)
            //        {
            //            reserve(v.size());
            //            for (const auto& e : v)
            //            {
            //                push_back(e);
            //            }
            //        }
            //初始化为N个val
            vector(size_t n, const T& val = T())
                :_start(nullptr)
                    , _finish(nullptr)
                    , _end_of_storage(nullptr)
                {
                    reserve(n);
                    for (size_t i = 0; i < n; ++i)
                    {
                        push_back(val);
                    }
                }
            //使用迭代器构造
            template <class InputIterator>
                vector(InputIterator first, InputIterator last)
                :_start(nullptr)
                    , _finish(nullptr)
                    , _end_of_storage(nullptr)
                {
                    while(first != last)
                    {
                        push_back(*first);
                        ++first;
                    }
                }
            //现代写法
            void swap(vector<T>& v)
            {
                std::swap(_start, v._start);
                std::swap(_finish, v._finish);
                std::swap(_end_of_storage, v._end_of_storage);
            }

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

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

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

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

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

                return _start[pos];
            }

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

                return _start[pos];
            }

            size_t size() const
            {
                return _finish - _start;
            }
            //相关函数
            void reserve(size_t n)
            {
                if (n > capacity())
                {
                    size_t sz = size();
                    T* tmp = new T[n];
                    if (_start)
                    {
                        //memcpy(tmp, _start, sizeof(T)*sz);
                        for (size_t i = 0; i < sz; ++i)
                        {
                            tmp[i] = _start[i];
                        }
                        delete[] _start;
                    }

                    _start = tmp;
                    _finish = _start + sz;
                    _end_of_storage = _start + n;
                }
            }

            void resize(size_t n, const T& val = T())
            {
                if (n > capacity())
                {
                    reserve(n);
                }

                if (n > size())
                {
                    // 初始化填值
                    while (_finish < _start + n)
                    {
                        *_finish = val;
                        ++_finish;
                    }
                }
                else
                {
                    _finish = _start + n;
                }
            }

            void push_back(const T& x)
            {
                /*	if (_finish == _end_of_storage)
                {
                    reserve(capacity() == 0 ? 4 : capacity() * 2);
                }

                *_finish = x;
                ++_finish;*/
                insert(end(), x);
            }

            void pop_back()
            {
                assert(_finish > _start);
                --_finish;
            }

            iterator insert(iterator pos, const T& x)
            {
                assert(pos >= _start);
                assert(pos <= _finish);

                if (_finish == _end_of_storage)
                {
                    size_t 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;
            }

            // stl 规定erase返回删除位置下一个位置迭代器
            iterator erase(iterator pos)
            {
                assert(pos >= _start);
                assert(pos < _finish);

                iterator begin = pos + 1;
                while (begin < _finish)
                {
                    *(begin - 1) = *begin;
                    ++begin;
                }

                --_finish;
                //if (size() < capacity()/2)
                //{
                //	// 缩容 -- 以时间换空间
                //}

                return pos;
            }

            T& front()
            {
                assert(size() > 0);

                return *_start;
            }

            T& back()
            {
                assert(size() > 0);

                return *(_finish - 1);
            }

            private:
            iterator _start;
            iterator _finish;
            iterator _end_of_storage;
        };
}

后记

这篇我们主要讲了vector是怎么实现的,首先搭建大概的框架,然后实现构造函数,再拓展到构造的其他方式,有拷贝构造、迭代器构造、初始化为N个val的构造等等,它们还有传统和现代两种实现方式,功能都是一样的,只不过思想不一样,现代的实现更加侧重复用老板思维最后我们实现了相关常见的函数,至此vector便已模拟实现。

对学习这里的同学的建议是,考虑清楚每一个函数的实现目的,再考虑实现方式,以及自己动手撸一遍肯定比只看印象深刻。

感谢大家支持!!!

respect!

下篇见!

在这里插入图片描述


http://lihuaxi.xjx100.cn/news/1115762.html

相关文章

UDP 协议详解

UDP (User Datagram Protocol) 是在 OSI 七层模型中的传输层上的一种协议。它和 TCP 类似是用来传输数据的&#xff0c;但是 UDP 更加简单、高效、灵活&#xff0c;适用于对数据传输速度要求较高&#xff0c;但对可靠性要求不高的场景&#xff0c;例如游戏、音频、视频等实时通…

常用的python gpu加速方法

在使用 PyCharm进行机器学习的时候&#xff0c;我们常常需要自己创建一些函数&#xff0c;这个过程中可能会浪费一些时间&#xff0c;在这里&#xff0c;我们为大家整理了一些常用的 Python加速方法&#xff0c;希望能给大家带来帮助。 在 Python中&#xff0c;我们经常需要创建…

工程监测无线中继采集发送仪 指示灯功能说明及接口定义

工程监测NLM5无线中继采集发送仪 指示灯功能说明及接口定义 指示灯功能说明 标识 名称 状态 描述说明 备注说明 CHG 正在充电 常亮 正在充电 DON 充电完成 常亮 已充满 POW 电源指示 常亮 外部电源已连接 仅用于指示是否连接了外部电源 熄灭 无外部电源 SIG 空 RUN 运行状态 闪…

C++多线程编程实践:从基础到实例的全面指南

C 基础知识 九 线程和多线程 一、线程基础知识1. 进程和线程的概念2. 多线程的优点和使用场景3. 线程的生命周期4. 线程的状态5. 线程的同步和互斥5.1 线程同步5.2 线程互斥 6. 代码示例 二、C11线程库1 C11线程库的概念和特性2 线程库中的关键类和函数3 使用线程库创建和控制线…

Linux危险命令

rm -rf 命令 该命令可能导致不可恢复的系统崩坏。 rm -rf / #强制删除根目录下所有东西。rm -rf * #强制删除当前目录的所有文件。rm -rf . #强制删除当前文件夹及其子文件夹。fork 炸弹 :() { :|:& };:不太好理解可以转换成 bomb() {bomb|bomb& }; bomb一旦执行…

如何使用 AppArmor 来保护应用程序和敏感数据

AppArmor 是一种应用级别的访问控制&#xff08;ACL&#xff09;工具&#xff0c;可以限制进程访问文件和目录的权限&#xff0c;从而保护应用程序和敏感数据的安全。下面介绍如何使用 AppArmor 来保护应用程序和敏感数据&#xff1a; 确认 AppArmor 是否安装&#xff1a; 在…

JVM 本地方法

本地方法接口 本地方法: 一个 Native Method 就是一个 Java 调用非 Java 代码的接口在定义一个 native method 时, 并不提供实现体(有些像定义一个 Java interface),因为其实现体是由非 java 语言在外面实现的本地接口的作用是融合不同的编程语言为 java 所用&#xff0c;它的…

跨模态检索论文泛读:VisualSparta-利用加权的词袋进行大规模的文本到图像的检索

ACL2021 | 利用加权的词袋进行大规模的文本到图像的检索 VisualSparta: An Embarrassingly Simple Approach to Large-scale Text-to-Image Search with Weighted Bag-of-words主打速度&#xff01; 简介 目前的跨模态检索方法主要分为查询相关和查询无关两种。查询无关的方法…