【C++初阶8-vector实现】没想的那么简单!

news2024/10/9 8:37:02

前言

本期带来vector实现,和以前的顺序表差别不大。

博主水平有限,不足之处望请斧正!


预备知识

实现参考SGIstl30

我们看这种源码,要抓框架看:首先找类,看它的属性,再看方法

template <class T, class Alloc = alloc>
class vector {
public:
  typedef T value_type;
  typedef value_type* iterator;
  typedef const value_type* const_iterator;
  
  //...
  
protected:
  iterator start;
  iterator finish;
  iterator end_of_storage;
  
  //...
}

public:
  iterator begin() { return start; }
  const_iterator begin() const { return start; }
  iterator end() { return finish; }
  const_iterator end() const { return finish; }
	
	//...

}

可以看到,vector是使用三个迭代器 startfinishend_of_storage维护的,这是怎么个思路?

如图所示:

在这里插入图片描述

  • _start 指向起始位置
  • _finish 指向最后一个有效数据的下一个位置
  • _endofstorage 指向容量的下一个位置

有了这些储备,就能开始实现我们自己的vector了。

实现

分离

我们用一个 main.cpp 和一个 vector.h

#include <iostream>
using namespace std;

#include "vector.h"

int main()
{
		bacon::...; //调用测试函数
    return 0;
}

这里需要注意命名空间展开和包含头文件的顺序:为了编译速度,编译器找定义都是向上找。当 "vector.h" 展开,只会向上找——如果先包含 "vector.h" 后展开命名空间,"vector.h" 中使用 cout 之类向上就找不到。

跑起来

namespace bacon
{

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)
    ,_endofstorage(nullptr)
    {}
    
    size_t capacity() { return _endofstorage - _start; }
    
    size_t size() { return _finish - _start; }
    
    void reserve(size_t n)
    {
        if(n > capacity())
        {
            size_t oldsize = size();
            T* tmp = new T[n];
            if(_start)
            {
                memcpy(tmp, _start, sizeof(T) * size());
            }
            delete[] _start;

            _start = tmp;
            _finish = _start + size(); 
            _endofstorage = _start + size();
        }
    }
    
    void push_back(const T& val)
    {
        if(_finish == _endofstorage)
            reserve(capacity() == 0 ? 4 : capacity() * 2);
        
        *_finish = val;
        ++_finish;
    }
    
    T& operator[](size_t index)
    {
        assert(index < size());
        return _start[index];
    }
    
    
private:
    iterator _start;
    iterator _finish;
    iterator _endofstorage;
};

void test1()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
}

}

//main.cpp
#include <iostream>
using namespace std;

#include "vector.h"

int main()
{
    bacon::test1();
    return 0;
}

测一下发现报错了

在这里插入图片描述

注意:reserve这里非常容易出bug

    void reserve(size_t n)
    {
        if(n > capacity())
        {
            T* tmp = new T[n];
            if(_start)
            {
                memcpy(tmp, _start, sizeof(T) * size());
            }
            delete[] _start;
            _start = tmp;
          	//size():return _finish - _start
          	//_finish = _start + _finish - _start; 
            _finish = _start + size(); 
            _endofstorage = _start + size();
        }
    }

size()本来应该用老的_start来和_finish作差,但是_start变了,导致size()返回的不是0,而是_finish - _start,原语句则变成_finish = _start + _finish - _start; 最终_finish还是nullptr

解决方案1:先更新_finish,仍然能用老的_start计算大小。

_finish = tmp + size();
_start = tmp;
_endofstorage = _start + size();

解决方案2:提前保存size,用临时保存的size迭代_finish_endofstorage

size_t oldsize = size();     
_start = tmp;
_finish = _start + oldsize;
_endofstorage = _start + oldsize;

所以reserve应该是这样:

		void reserve(size_t n)
    {
        if(n > capacity())
        {
            size_t oldsize = size();
            T* tmp = new T[n];
            if(_start)
            {
                memcpy(tmp, _start, sizeof(T) * size());
            }
            delete[] _start;

            //err:_start的改变影响size()
            //_finish = _start + size() = _start + _finish - _start = nullptr
            //1.调换顺序
            _finish = tmp + size();
            _start = tmp;
            _endofstorage = _start + size();
            
            //2.提前保存
            _start = tmp;
            _finish = _start + oldsize;
            _endofstorage = _start + n;
        }
    }

跑起来:

//vector.h
void test1()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
}

//main.cpp
#include <iostream>
using namespace std;

#include "vector.h"

int main()
{
    bacon::test1();
    return 0;
}
1 2 3 4 1 2 3 4

补充成员函数

resizeempty
    void resize(size_t n, T val = T())
    {
        if(n > capacity()) reserve(n); //需要扩就扩了
        
        if(n > size())
        {
            while(_finish < _start + n)
            {
                *_finish = val;
                ++_finish;
            }
        }
        else
        {
            _finish = _start + n;
        }
    }
    
    bool empty() { return _start == _finish; }

resize

  1. 需要扩容则扩容

  2. n > size:填数据

  3. n <= size:删除数据

    (n == size)包含在哪种情况都一样

跑一下:

void test2()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
    
    
    v.resize(100);
    cout << v.size() << endl;
    cout << v.capacity() << endl;
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
}

//main.c
#include <iostream>
using namespace std;

#include "vector.h"

int main()
{
    bacon::test2();
    return 0;
}
1 2 3 4 1 2 3 4 
100
8
1 2 3 4 1 2 3 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
pop_back
    void pop_back()
    {
        assert(!empty());
        --_finish;
    }
insert
		void insert(iterator pos, const T& val)
    {
        if(_finish == _endofstorage)
            reserve(capacity() == 0 ? 4 : capacity() * 2);
        
        iterator end = _finish - 1; //最后一个有效数据
        while(end >= pos)
        {
            *(end + 1) = *end;
            --end;
        }
        
        *pos = val;
        ++_finish;
    }

测一下,发现报错。

因为属性换成 _start 这些,挪动数据的时候不会有类型提升影响。那是什么问题?

其实是迭代器是小问题

#在insert内部,迭代器失效

为什么?

在这里插入图片描述

未扩容前:

在这里插入图片描述

pos仍正常,属于_start_finish 的范围内

在这里插入图片描述

扩容后:

在这里插入图片描述

画图分析一下:

在这里插入图片描述

扩容导致_start_finish 变化,所以pos已经不属于 _start_finish 的范围内了。此时,用 endpos 比较显然不是我们的意思了。

所以如果扩容了,需要更新一下 pos(按理也要检查一下pos)。

所以insert内部迭代器失效的原因是:扩容,未更新迭代器。

怎么解决?

更新迭代器

		void insert(iterator pos, const T& val)
    {
        assert(pos >= _start && pos < _finish);
        
        if(_finish == _endofstorage)
        {
            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 = val;
        ++_finish;
    }

在这里插入图片描述
:
在这里插入图片描述

更新成功。

void test4()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
    
    v.insert(v.begin(), 9);
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
}
1 2 3 4 
9 1 2 3 4

insert内部迭代器失效

原因:扩容,迭代器未更新,导致迭代器野指针。

解决:更新迭代器。


#在insert外部,迭代器失效
void test4()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
    
    vector<int>::iterator pos = find(v.begin(), v.end(), 2);
    v.insert(pos, 9);
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    
    *(pos)++; //可以吗?
    
    cout << endl;
}

main insert以后,pos能否继续使用?

不能,有可能是失效的。insert 内的迭代器是传值传参得到的,不影响main内的 pos ,而我们仅在 insert 内 更新了迭代器,若 insert时发生扩容,mainpos 不会被更新。

那有什么方式可以解决?

传值不行,传引用?还是不行。引用形参只能接收左值(能修改的),当我们传了右值,如 v.begin(),调用返回涉及到临时变量,就会出错。

Error:非常量引用的初始值必须为左值

看看文档,发现还有个返回值

		iterator insert (iterator position, const value_type& val);

文档中这样描述返回值:

An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.

一个迭代器,指向被函数调用删除的最后一个元素之后的元素的新位置。如果操作擦除了序列中的最后一个元素,则这是容器结束。

比如,某迭代器原来指向 v[3],删除后指向新的 v[3],如果 v[3] 是最后一个元素,迭代器就是_finish

哦!当我们 insert 后还是需要用 pos ,通过返回值更新一下就好了!

		iterator insert(iterator pos, const T& val)
    {
        assert(pos >= _start && pos < _finish);
        
        if(_finish == _endofstorage)
        {
            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 = val;
        ++_finish;
        
        return pos;
    }

insert外部迭代器失效

原因:insert的迭代器参数是传值传参,内部更新不影响外部。main的迭代器仍然失效。

解决:外部按需通过返回值更新迭代器。

erase

经历前面的迭代器失效后,我们也想:这个会不会也导致迭代器失效呢?

好像不会哦?因为不缩容,空间不会变的。用库里的测一下,看库怎么说:

		void test5()
    {
        std::vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);

        std::vector<int>::iterator pos = find(v.begin(), v.end(), 3);
        v.erase(pos);
        for (size_t i = 0; i < v.size(); ++i)
        {
            cout << v[i] << ' ';
        }
        cout << endl;

        //读
        cout << *pos << endl;
        //写
        ++(*pos);  
     }

vs2022:

在这里插入图片描述

Xcode:

1 2 4 
4
#在erase外部,迭代器失效

为什么?

  1. 意义来说,迭代器如果不更新,是失效的(原来指向的数据都删了,现在指向谁说不准)
  2. 而且我们的代码得在所有平台能跑。

所以我们还是认为 erase 后,迭代器失效。

怎么解决?

同样的,通过返回值来选择性更新(需要再用就更新)。

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

erase外部迭代器失效

原因:意义改变

解决:按需通过返回值更新迭代器

find

可以直接用算法库的find

template<class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val)
{
  while (first!=last) {
    if (*first==val) return first;
    ++first;
  }
  return last;
}

算法库的find是函数模版,只需要我们的vector支持 != * ==++就行。

void test4()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
    
    vector<int>::iterator pos = find(v.begin(), v.end(), 2);
  	if(pos != v.end())
    	v.insert(pos, 9);
    for(size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << ' ';
    }
    cout << endl;
}
1 2 3 4 
1 9 2 3 4
swap
		void swap(vector<T>& v)
    {
        std::swap(this->_start, v._start);
        std::swap(this->_finish, v._finish);
        std::swap(this->_endofstorage, v._endofstorage);
    }
clear
    void clear() { _finish = _start; }
~vector
    ~vector()
    {
        delete[] _start;
        _start = _finish = _endofstorage = nullptr;
    }
vector(const vector<T>& v)

传统写法:开空间,拷贝数据(复用)

    vector(const vector<T>& v)
        :_start(nullptr)
        ,_finish(nullptr)
        ,_endofstorage(nullptr)
    {
        reserve(v.capacity());
        for(auto& e : v) 	//不加引用每次拿v内对象赋值给e,效率低
        {
            push_back(e);
        }
    }

现代写法:抓壮丁(迭代器区间构造)

vector(InputIterator first, InputIterator last)
    template <class InputIterator>
    vector(InputIterator first, InputIterator last)
        :_start(nullptr)
        ,_finish(nullptr)
        ,_endofstorage(nullptr)
    {
        while(first != last)
        {
            push_back(*first);
            ++first;
        }
    }
    vector(const vector<T>& v)
        :_start(nullptr)
        ,_finish(nullptr)
        ,_endofstorage(nullptr)
    {
        vector<T> man(v.begin(), v.end());
        swap(man);
    }
vector<T>& operator= (vector<T>& v)

现代写法:抓壮丁(传值传参调用拷贝构造产生的临时对象)

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

只要拷贝构造写好了,都能用这个现代写法~

但是,上面的写法有一个缺陷:v1 = v1,自己给自己赋值,白拷贝了。但可以容忍,因为这种场景非常少,而且程序能正常运行。

想实现全一点的话还可以实现一个这个。

explicit vector (size_type n, const value_type& val = value_type()

explicit:由于这是个单参构造,涉及赋值运算符的时候,若类型合适,可能将 n 转换为 vector

class t1
{
public:
	t1(int n) :_n(n) {}
private:
	int _n;
};

class t2
{
public:
	explicit t2(int n) :_n(n) {}
private:
	int _n;
};

int main()
{
	int num = 0;

	t1 t1 = num;
	
	t2 t2 = num;

	return 0;
}

如上,单参构造类型合适(int的形参和要初始化的属性合适),int n 转换成了t1 n

如此能防止单参构造的 n 转换为 vector

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

但是,这有个坑。

#n个val构造的匹配问题
void test9()
{
  	//vector<char> v(10, 'A'); //正常运行
    vector<int> v(10, 1);
    
    for(auto e : v)
        cout << e << ' ';
    cout << endl;
}

在这里插入图片描述

vector<int> v(10, 1);

vector(size_t n, const T& val = T())

template <class InputIterator>
    vector(InputIterator first, InputIterator last)

为什么vector<char> v(10, 'A');能匹配到vector(size_t n, const T& val = T())?

10这个字面量的类型是int'A'这个字面量的类型是char,模版参数T上绑定了char类型。虽然intsize_t有一点不匹配,但是可以类型转换,也没有其他更加匹配的函数。

而对于vector<int> v(10, 1);,两个字面量类型都是int,调用vector(size_t n, const T& val = T())需要将int转换为size_t,函数模版显然能产生更好的匹配,int被当做迭代器解引用,当然不行。

那如何解决呢?

想不到办法可以参考源码的实现

  vector() : start(0), finish(0), end_of_storage(0) {}
  vector(size_type n, const T& value) { fill_init ialize(n, value); }
  vector(int n, const T& value) { fill_initialize(n, value); }
  vector(long n, const T& value) { fill_initialize(n, value); }
  explicit vector(size_type n) { fill_initialize(n, T()); }

直接一样来一个。

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

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

原因:迭代器区间模版更匹配
解决:一样来一个,让它匹配

到这就差不多了……吗?

我们开空间的操作还有问题!拷数据简单地用memcpy就能解决吗?不能!当要拷贝的元素涉及深拷贝就完了!

#元素浅拷贝问题
void test11()
{
    vector<int> v(10, 1);
    vector<vector<int>> vv;
    
    vv.push_back(v);
    for(size_t i = 0; i < vv.size(); ++i)
    {
        for(size_t j = 0; j < vv[i].size(); ++j)
        {
            cout << vv[i][j] << ' ';
        }
        cout << endl;
    }
    cout << endl;
}

在这里插入图片描述

void reserve(size_t n)
    {
        if(n > capacity())
        {
            size_t oldsize = size();
            T* tmp = new T[n];
            if(_start)
            {
//                浅拷贝不够用,有些要深拷贝的
                memcpy(tmp, _start, sizeof(T) * size());
            }
            delete[] _start;
            
            _start = tmp;
            _finish = _start + oldsize;
            _endofstorage = _start + n;
        }
    }

对于reserve画图分析一下:

在这里插入图片描述

这是浅拷贝后未释放空间的情况,vv的属性直接拷贝给tmpvvtmp 指向同一块空间。这时再 delete[] _start; ,对每个自定义类型vector<int> v都调用析构,在这之前,每个vector<int> v 又会对自己的空间调用析构。
在这里插入图片描述

扩容后,vv指向新空间,但新空间的前4个元素的空间已经被析构,此时如果vv[0][0],解引用野指针,必然出错。

调试验证:

1.浅拷贝,新空间指向原空间

在这里插入图片描述

2.原空间释放,新空间受影响,[_start, _start+4) 变成野指针
在这里插入图片描述

3.解引用野指针(对野指针的访问,打印出随机值)

在这里插入图片描述

为什么说C++不适合用C语言的内存函数,这里若用realloc,其内部memcpy拷贝数据,完球。(要使用必须确定不会影响)

对于哪哪都是自定义类型的C++,我们尽量选择为其量身定制的new 和 delete,会自动调用构造和析构。

如何解决?自然是每个元素都深拷贝:

void reserve(size_t n)
    {
        if(n > capacity())
        {
            size_t oldsize = size();
            T* tmp = new T[n];
            if(_start)
            {
//                浅拷贝不够用,有些要深拷贝的
//                memcpy(tmp, _start, sizeof(T) * size());
                for(size_t i = 0; i < oldsize; ++i)
                {
                    tmp[i] = _start[i];
                }
            }
            delete[] _start;
            
            _start = tmp;
            _finish = _start + oldsize;
            _endofstorage = _start + n;
        }
    }

在这里插入图片描述

解决是解决了,效率是不是有点低?确实,现阶段没有好的解决办法,后期学了C++11就有了。

此处的问题不仅出现在刚刚的场景,只要是涉及 reserve 拷贝数据的地方,都会出现问题,如 拷贝构造 ,其中调用了 push_back,会调用到 reserve

vector的实现到这就差不多了,附上整体代码(如有错误不足之处,希望指出)

namespace bacon
{

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)
    ,_endofstorage(nullptr)
    {}
    
    //explicit:对于单参的构造,类型合适的时候会触发隐式类型转换
    //这里是怕n隐式类型转换成vector,传size_t就不会转换成vector
    explicit vector(int n, const T& val = T())
    :_start(nullptr)
    ,_finish(nullptr)
    ,_endofstorage(nullptr)
    {
        reserve(n);
        for(int i = 0; i < n; ++i)
        {
            push_back(val);
        }
    }
    
    //按n的类型再给一个(long也能再给一个)方法,解决参数不匹配的问题
    explicit vector(size_t n, const T& val = T())
    :_start(nullptr)
    ,_finish(nullptr)
    ,_endofstorage(nullptr)
    {
        reserve(n);
        for(int i = 0; i < n; ++i)
        {
            push_back(val);
        }
    }
    
    template <class InputIterator>
    vector(InputIterator first, InputIterator last)
    :_start(nullptr)
    ,_finish(nullptr)
    ,_endofstorage(nullptr)
    //这里的实现体现了迭代器的意义,不同的迭代器,同样的控制,一套代码走所有容器
    {
        while(first != last)
        {
            push_back(*first);
            ++first;
        }
    }
    
    //现代写法
    vector(const vector<T>& v)
    :_start(nullptr)
    ,_finish(nullptr)
    ,_endofstorage(nullptr)
    {
        vector<T> man(v.begin(), v.end());
        swap(man);
    }
    
    //复用写法
    //    vector(const vector<T>& v)
    //        :_start(nullptr)
    //        ,_finish(nullptr)
    //        ,_endofstorage(nullptr)
    //    {
    //        reserve(v.capacity());
    //        for(auto& e : v) //不加引用每次拿v内对象赋值给e,效率低
    //        {
    //            push_back(e);
    //        }
    //    }
    

    ~vector()
    {
        delete[] _start;
        _start = _finish = _endofstorage = nullptr;
    }
    
    //v1 = v1
    vector<T>& operator= (vector<T> v)
    {
        swap(v);
        return *this;
    }
    
    bool empty() const { return _start == _finish; }
    
    size_t capacity() const { return _endofstorage - _start; }
    
    size_t size() const { return _finish - _start; }
    
    void reserve(size_t n)
    {
        if(n > capacity())
        {
            size_t oldsize = size();
            T* tmp = new T[n];
            if(_start)
            {
//                浅拷贝不够用,有些要深拷贝的
//                memcpy(tmp, _start, sizeof(T) * size());
                for(size_t i = 0; i < oldsize; ++i)
                {
                    tmp[i] = _start[i];
                }
            }
            delete[] _start;
            
            //err:_start的改变影响size()
            //_finish = _start + size() = _start + _finish - _start = nullptr
            //1.调换顺序
            //            _finish = tmp + size();
            //            _start = tmp;
            //            _endofstorage = _start + size();
            
            //2.提前保存
            _start = tmp;
            _finish = _start + oldsize;
            _endofstorage = _start + n;
        }
    }
    
    void resize(size_t n, T val = T())
    {
        //        1. 需要扩容则扩容
        //        2. n > size:填数据
        //        3. n <= size:删除数据
        //        (n == size)包含在哪种情况都一样
        if(n > capacity()) reserve(n); //需要扩就扩了
        
        if(n > size())
        {
            while(_finish < _start + n)
            {
                *_finish = val;
                ++_finish;
            }
        }
        else
        {
            _finish = _start + n;
        }
    }
    
    void push_back(const T& val)
    {
        if(_finish == _endofstorage)
            reserve(capacity() == 0 ? 4 : capacity() * 2);
        
        *_finish = val;
        ++_finish;
    }
    
    void pop_back()
    {
        assert(!empty());
        --_finish;
    }
    
    iterator insert(iterator pos, const T& val)
    {
        assert(pos >= _start && pos < _finish);
        
        if(_finish == _endofstorage)
        {
            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 = val;
        ++_finish;
        
        return pos;
    }
    
    iterator erase(iterator pos)
    {
        assert(!empty());
        assert(pos >= _start && pos < _finish);
        
        iterator begin = pos + 1;
        while(begin < _finish)
        {
            *(begin - 1) = *begin;
            ++begin;
        }
        --_finish;
        
        return pos;
    }
    
    void swap(vector<T>& v)
    {
        std::swap(this->_start, v._start);
        std::swap(this->_finish, v._finish);
        std::swap(this->_endofstorage, v._endofstorage);
    }
    
    void clear() { _finish = _start; }
    
    
    T& operator[](size_t index)
    {
        assert(index < size());
        return _start[index];
    }
    
    const T& operator[](size_t index) const
    {
        assert(index < size());
        return _start[index];
    }
    
    
private:
    iterator _start;
    iterator _finish;
    iterator _endofstorage;
};


}

重点回顾

insert内部的迭代器失效

  • 原因:扩容,迭代器未更新,野指针
  • 解决:需要扩容则更新迭代器

insert外部的迭代器失效

  • 原因:insert迭代器传值传参,内部更新不影响外部
  • 解决:按需用返回值更新迭代器

erase外部的迭代器失效

  • 原因:迭代器指向的数据已经删除,迭代器意义改变
  • 解决:按需用返回值更新迭代器

n个val构造类型不匹配

  • 原因:有些场景类型不够匹配,会走到迭代器区间构造(模版)
  • 解决:按n可能传的类型,多写几个这样的构造

元素浅拷贝问题

  • 原因:对自定义类型的元素浅拷贝,指向错误,导致新空间被影响
  • 解决:对每个元素的拷贝都调用它们自己的赋值重载或者拷贝构造等。

今天的分享就告一段落了。

这里是培根的blog,期待与你共同进步!

下期见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/135814.html

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

相关文章

全方位解析 C 端和 B 端的产品特性

近年来&#xff0c;互联网进入下半场&#xff0c;C 端流量红利逐渐消退&#xff0c;很多企业转向了 B 端服务&#xff0c;随之而来的是产品设计者的转型&#xff0c;现在越来越多的 C 端产品设计师开始涉足到 B 端产品的设计&#xff0c;这是一个知识迁移的过程&#xff0c;需要…

自动驾驶专题介绍 ———— 摄像头

文章目录介绍工作原理实现功能分类按通信协议区分按不同感光芯片按像元排列方式介绍 摄像头可以采集汽车周边的图像信息&#xff0c;跟人类的眼睛最为接近。摄像头可以拥有较广的视场角、较大的分辨率&#xff0c;还可以提供颜色和纹理等信息。这些信息对于实现自动驾驶功能是存…

Mentor-dft 学习笔记 day48-OCC With Capture Enable Clock Control Operation Modes

OCC With Capture Enable 有一个OCC具有capture_enable输入&#xff0c;可以与自由运行的慢速时钟一起使用。当OCC指定为启用捕获&#xff08;capture_trigger:capture_en&#xff09;时&#xff0c;在输入自由运行的慢时钟上添加时钟门控器&#xff0c;以从自由运行的时钟输…

影响宝宝大脑发育的6个坏习惯,你可能每天都在做

“望子成龙&#xff0c;望女成凤”这几乎是每个父母的愿望。虽然有一个高智商的天才宝宝太难了&#xff0c;但从不妨碍父母希望孩子更健康、更聪明。所以大家都比较关注宝宝的大脑发育&#xff0c;希望宝宝的大脑发育更好&#xff0c;长大后更聪明。但在日常生活中&#xff0c;…

android 12+从后台启动FGS限制

后台启动FGS限制 限制简介 以 Android 12&#xff08;API 级别 31&#xff09;或更高版本为目标平台的应用在后台运行时无法启动前台服务&#xff0c;少数特殊情况除外。 如果应用程序在后台运行时尝试启动前台服务&#xff0c;而前台服务不满足其中一种异常情况&#xff0c;系…

vue前端打包Docker镜像并nginx运行

首先说明咱们的前端项目是基于Vue的&#xff0c;反向代理使用的是nginx 1.打包vue前端项目生成dist文件夹上传至服务器 新建一个文件夹&#xff0c;叫vueDockerTest&#xff0c;下面的文件都需要。 cert是你存放ssl证书的文件夹&#xff0c;nginx.conf 是nginx的配置文件&am…

Kotlin 惰性集合操作-序列 Sequence

集合操作函数 和 序列 在了解 Kotlin 惰性集合之前&#xff0c;先看一下 Koltin 标注库中的一些集合操作函数。 定义一个数据模型 Person 和 Book 类&#xff1a; data class Person(val name: String, val age: Int) data class Book(val title: String, val authors: List…

jmeter 5.5+influxdb 2.0+grafana v9.3.2 - 压测看板setup

Docker set up 安装docker应用 https://docs.docker.com/desktop/install/mac-install/&#xff0c;在官网下载docker安装包&#xff0c;和安装其他的mac应用是一样的操作。 设置国内的镜像仓库&#xff08;拉取镜像会快很多&#xff09; {"registry-mirrors": [&q…

叠氮-聚乙二醇-羧酸;叠氮-单乙二醇-丙酸Azido-PEG1-acid;1393330-34-1小分子PEG衍生物

Azido-PEG1-acid 中文名称&#xff1a;叠氮-聚乙二醇-羧酸&#xff1b;叠氮-单乙二醇-丙酸 英文名称&#xff1a;Azido-PEG1-acid&#xff1b; 分子式&#xff1a;C5H9N3O3 分子量 &#xff1a;159.1 CAS&#xff1a;1393330-34-1 外观&#xff1a;粘稠液体或者固体粉末&#…

SHA和AES加密+GUI Swing写的一个本地运行和保存的密码管理小工具

目录效果项目结构功能1、登录2、加密3、解密4、列表代码1、先准备好两种加密方式的工具类SHAUtilAESUtil2、登录窗口3、主页窗口&#xff08;加密和解密面板&#xff09;4、主页窗口&#xff08;列表面板&#xff09;5、主程序&#xff08;main&#xff09;最后通过SHA和AES加密…

TestStand-序列步骤属性

文章目录GeneralRun OptionLoopingPost ActionSwitchingSynchronizationExpressionPreconditionsRequirementAdditional ResultPropertyCtrl-N创建一个新的Sequence&#xff0c;通过右键创建任意步骤 General Name -步骤的名称。 Type -步骤类型。一般不需要设置。 Adapter-适…

Android Kotlin之协程-异步流Flow的使用

数据流以协程为基础构建&#xff0c;与仅返回单个值的挂起函数相反&#xff0c;数据流可按顺序发出多个值。从概念上来讲&#xff0c;数据流是可通过异步方式进行计算处理的一组数据序列。所发出值的类型必须相同。 数据流包含三个实体&#xff1a; 提供方会生成添加到数据流…

信息安全技术 政务信息共享 数据安全技术要求

声明 本文是学习GB-T 39477-2020 信息安全技术 政务信息共享 数据安全技术要求. 下载地址 http://github5.com/view/790而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 政务信息共享 数据安全 范围 本标准提出了政务信息共享数据安全要求技术框架&…

2023年工作第一天心情感悟

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 今天是2023年1月3日&#xff0c;也是我们上班的第一天。今天这篇随记&#xff0c;也发表下我对2023年的看法&#xff0c;也对过去的2022年做过总结。 &#xff08;2023年元旦&#xff0c;到门头沟…

Spring之ApplicationContext快速入门

目录 一&#xff1a;概述 二&#xff1a;代码演示 三&#xff1a;BeanFactory与ApplicationContext的关系 四&#xff1a;BeanFactory的继承体系 五&#xff1a;ApplicationContext的继承体系 一&#xff1a;概述 ApplicationContext称为Spring容器&#xff0c; 内部封装了…

面试官:能用JavaScript手写一个bind函数吗

经常会看到网上各种手写bind的教程&#xff0c;下面是我在自己实现手写bind的过程中遇到的问题与思考。如果对于如何实现一个手写bind还有疑惑的话&#xff0c;那么可以先看看上面两篇文章。 手写bind vs 原生bind 我们先使用一个典型的手写bind的例子&#xff0c;代码如下&a…

PHP命令执行的函数

在做面试题的时候发现&#xff0c;自己对PHP命令执行的函数的了解并不是很全面&#xff0c;就想这去学习一下。我也在网上找到了许多的资料&#xff0c;在这里我就相当于一个总结吧。 system(); System()函数的主要功能是在系统权限允许的情况是执行系统命令,windows系统和Lin…

【服务器数据恢复】EMC存储Zfs文件系统下raid5数据恢复案例

服务器存储数据恢复环境&#xff1a; 某公司一台EMC存储&#xff0c;12块硬盘组成raid5&#xff0c;2块热备盘&#xff1b; Zfs文件系统。 服务器存储故障&#xff1a; 硬盘故障导致存储崩溃。 服务器存储数据恢复过程&#xff1a; 1、对故障存储所有硬盘进行物理故障检测&…

详细软件著作权的申请

一&#xff0c;申请注册账号并进行实名认证 在中国版权保护中心官网注册账号。 我是自己申请的所以选择的个人&#xff0c;这里根据实际情况进行选择后注册。 注册后进行实名认证&#xff08;3-7个工作日人工会进行审核&#xff0c;所以每个著作权人都要提前注册并进行实名认证…

论文投稿指南——中文核心期刊推荐(地球物理学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…