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

news2024/11/24 19:54:57

文章目录

  • 前言
  • 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://www.coloradmin.cn/o/508289.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux centos安装Redis数据库并远程连接

目录 前言 1. Linux(centos8)安装redis数据库 2. 配置redis数据库 3. 内网穿透 3.1 安装cpolar内网穿透 3.2 创建隧道映射本地端口 4. 配置固定TCP端口地址 4.1 保留一个固定tcp地址 4.2 配置固定TCP地址 4.3 使用固定的tcp地址连接 前言 Redis作为一款高速缓存的ke…

周杰伦官宣数智人“周同学”,数智人与数字人,不止一字之差

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 5月8日&#xff0c;周杰伦亮相中国移动元宇宙总部&#xff0c;与中国移动达成元宇宙领域系列合作&#xff0c;共同推出了数智人“周同学”。 周杰伦从 2003 年开始代言“动感地带”&#xff0c;可以说是中国移动 20 年的忠实…

6. 神经网络

6.1 非线性假设 假设有一个监督学习分类问题&#xff0c;训练集如图 如果利用logistic回归来解决这个问题&#xff0c;我们可以构造一个包含很多非线性项的logistic回归函数。 但在这个训练集只给出了两个特征&#xff0c;如果一旦特征变多了&#xff0c;多项式就会变得很多。…

1951-2023最新中国基础地理信息,包括水系、行政区、DEM高程、气象站经纬位置、土地利用,这些数据获取方法介绍

水系&#xff1a; 流域内所有河流、湖泊等各种水体组成的水网系统&#xff0c;称作水系。其中&#xff0c;水流最终流入海洋的称作外流水系&#xff0c;如太平洋水系、北冰洋水系&#xff1b;水流最终流入内陆湖泊或消失于荒漠之中的&#xff0c;称作内流水系。 [1] 流域面积的…

File 类和 InputStream, OutputStream 的用法

目录 1.文件系统操作 File 类 2.文件内容操作 InputStream 1.创建InputStream类对象 2.读文件 OutputStream 1.创建OutputStream类对象 2.写文件 上篇博客我们详情介绍了文件系统的相关知识。在文件系统操作中&#xff0c;由于文件是在硬盘上的&#xff0c;直接写代…

分布式系统入门概述

前言 随着互联网、物联网、人工智能等技术的广泛应用&#xff0c;计算机系统正在逐渐从单机转向网络化和分布式的趋势。那么&#xff0c;什么是分布式系统呢&#xff1f; 分布式概要 简而言之&#xff0c;分布式系统是由多个节点组成的&#xff0c;这些节点运行在不同的计算机上…

企业IDC服务器迁移上云视频教程来了(Windows和Linux)

上云是趋势&#xff0c;越来越多企业的IDC服务器选择迁移上云&#xff0c;迁移上云的方式有很多&#xff0c;阿里云提供服务器迁移中心SMC来帮助用户迁移上云。使用SMC服务器迁移中心&#xff0c;将您的源服务器方便快捷地迁移至阿里云&#xff0c;支持的迁移源类型包括IDC服务…

2 ElasticaSearch安装

2 ElasticaSearch安装 2.1 安装 安装配置&#xff1a; 1、新版本要求至少jdk1.8以上。 2、支持tar、zip、rpm等多种安装方式。 在windows下开发建议使用ZIP安装方式。 3、支持docker方式安装 详细参见&#xff1a;https://www.elastic.co/guide/en/elasticsearch/refere…

Unity大面积草地渲染——3、使用GPUInstancing渲染大面积的草

大家好&#xff0c;我是阿赵。 这里开始讲大面积草地渲染的第三个部分&#xff0c;使用GPU Instancing来渲染大面积的草。 一、在不使用GPU Instancing时的渲染情况 为了能看性能明显一点&#xff0c;我写了个工具&#xff0c;在10乘10的范围内生成了一万棵草。 由于我的电…

【Python】本地版 Whisper 自动转录器(附源码网址)

目 录 一、实时自动语音转录器简介 二、开源Whisper实时转录器 三、pyinstaller 打包发布exe应用程序四、修改版源代码 一、实时自动语音转录器简介 实时自动语音转录器是一种能够自动将语音信号转换为文字的应用程序。…

【机器学习】机器学习相关概念简述

一、什么是机器学习 机器学习指的是&#xff0c;在没有明确设置的情况下&#xff0c;使得计算机拥有自我学习能力的领域。 二、监督学习和无监督学习 2.1 监督学习 监督学习是指&#xff0c;我们给予算法一个数据集&#xff0c;其中的数据包含了若干个标签。一个例子就是给…

pdf怎么转换成jpg图片

pdf怎么转换成jpg图片&#xff1f;PDF格式可以在电脑和手机上使用&#xff0c;而且其内容不会被篡改。同时&#xff0c;PDF的通用兼容性较强&#xff0c;而且PDF文件操作简单&#xff0c;易于创作。PDF文件格式应用较为广泛。在我们创建PDF文件时&#xff0c;无论在何处查看数据…

OpenGL超级宝典第七章学习笔记:顶点处理与绘图命令

前言 本篇在讲什么 OpenGL蓝宝书第七章学习笔记 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻理论&…

Playground AI:免费绘画图像创作工具

【产品介绍】 Playground AI是一个免费的在线 AI绘画 图像创作工具。你可以用它来创作艺术作品、社交媒体帖子、演示文稿、海报、视频、logo 等等。 Playground AI 的核心技术是基于深度学习的图像生成模型&#xff0c;它可以根据你的输入文字或图片&#xff0c;自动合成出高质…

数组的应用

数组的应用 一、数组的定义二、切片替换删除数值元素 二、数组追加元素三、数组与函数相结合 一、数组的定义 相当于一串数据的集合&#xff0c;以空格相间隔的字符串列表&#xff0c;两边用括号括起来 echo ${shuzu[]}中的代表着显示所有的下标内容&#xff0c;当然&#…

【C++初阶】类和对象(三)

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C初阶】…

无线蓝牙耳机佩戴舒适的有哪几款?佩戴舒适的蓝牙耳机推荐

自从真无线蓝牙耳机产品推出以来&#xff0c;它已经逐渐成为了当代年轻人外出必带的随身数码产品。虽然市面上不缺好产品&#xff0c;但大家对于蓝牙耳机的佩戴舒适度害死更加重要的&#xff0c;下面就来分享几款佩戴舒适的蓝牙耳机吧。 一、南卡小音舱Lite2蓝牙耳机 参考价格…

Android studio单独导入官方例程camera-calibration

1.官方例程camera-calibration 2.将官方例程camera-calibration copy到AndroidStudioProjects项目目录下 3修改AndroidManifest.xml <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android“http://schemas.android.com/apk/res/andr…

2007年计算机真题

2007年计算机真题 数学基础部分 一、用逻辑符号表达下列语句&#xff08;每小题 2 分&#xff0c;共 4 分&#xff09; 1&#xff0e;分别用两种量词形式写出&#xff1a;在北京居住的人未必都是北京人。 答: 全域: 所有人 P ( x ) \mathrm{P}(\mathrm{x}) P(x) 表示 x \…

「企业应用架构」应用架构概述

在信息系统中&#xff0c;应用架构或应用架构是构成企业架构&#xff08;EA&#xff09;支柱的几个架构域之一 应用架构描述了业务中使用的应用程序的行为&#xff0c;重点是它们如何相互之间以及如何与用户交互。它关注的是应用程序消费和生成的数据&#xff0c;而不是它们的内…