【C++】 STL-vector模拟实现

news2024/11/19 8:35:30

文章目录

    • vector源码的内容:
      • 成员变量
    • 默认构造函数
      • 构造函数1-无参构造
      • 构造函数2 -使用n个相同的值构造
      • 构造函数3-使用迭代器区间构造
    • 拷贝构造函数
      • **传统写法**
      • 现代写法
    • 赋值重载函数opeartor=
      • 传统写法
      • 现代写法
    • 析构函数
    • 迭代器
      • begin & end
      • 任意类型vector容器迭代器通用遍历方式:
    • 容量相关的函数
      • reserve
      • resize
      • capacity
      • size
      • empty
    • 增删查改的函数
      • push_back
      • pop_back
      • insert
      • erase
      • swap
    • 访问
      • operator[]
  • 接口函数
  • vector.h
    • 测试代码
  • 关于memcpy函数设计深拷贝
      • 结论:

vector源码的内容:

image-20220205203854514

start:指向第一个位置的指针

finish:最后一个位置的下一个位置的指针

End_of_storage:指向最后一个空间的下一个位置

size = finish - start capacity = end_of_stroage - start


image-20220205204011034

我们直接使用new即可


成员变量

_start指向容器的起始位置,_finish指向容器当中有效数据的下一个位置,_endofstorage指向整个容器的结尾位置,

template<class T>//参数模板
class vector
{
public:
    typedef T* iterator;//普通迭代器
    typedef const T* const_iterator;//const迭代器

private:
    iteartor _start;
    iterator _finish;
    iterator _endofstorage;
};

默认构造函数

构造函数1-无参构造

在初始化列表中,将三个指针置空

//构造函数-无参构造
vector()
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
    {}

构造函数2 -使用n个相同的值构造

先使用reserve函数将容器容量先设置为n,然后使用push_back函数尾插n个值为val的数据到容器当中即可,

注意:要先在初始化列表把三个指针置空,否则后序reserve的时候就会有问题!

image-20220210213147362

//构造函数2-使用n个值初始化
vector(size_t n, T val)
    :_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
    //先开辟n个空间
    reserve(n);//内部会帮我们改指针的指向
    // 放入数据
    for (size_t i = 0; i < n; i++)
    {
        push_back(val);
    }
}

注意:

1)该构造函数知道其需要用于存储n个数据的空间,所以最好用reserve函数一次性开辟好空间,避免调用push_back函数时需要增容多次,导致效率降低,
2)该构造函数还需要实现重载函数,否则下面的代码会被认为是迭代器区间的构造

Mango::vector<int> v(2,3); //会被认为是迭代器区间的构造

image-20220210213314745

而如果被认为是迭代器区间的构造, 因为要对迭代器进行解引用,而int类型不能解引用,所以发生报错!

补充两个重载函数

vector(long n, const T& val)
    :_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
    reserve(n); //调用reserve函数将容器容量设置为n
    for (size_t i = 0; i < n; i++) //尾插n个值为val的数据到容器当中
    {
        push_back(val);
    }
}

vector(int n, const T& val)
	:_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
    reserve(n); //调用reserve函数将容器容量设置为n
    for (int i = 0; i < n; i++) //尾插n个值为val的数据到容器当中
    {
        push_back(val);
    }
}

构造函数3-使用迭代器区间构造

存在下面的构造方式

string s1("hello");
vector<char> v(s1.begin(), s1.end());
  • vector支持使用一段迭代器区间进行对象的构造,

  • 该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的

  • 我们这里需要将该构造函数设计为一个函数模板,将该迭代器区间的数据一个个尾插到容器当中即可,

template<class InputIterator>//函数模板
//迭代器区间 左闭右开[first,last)
vector(InputIterator first, InputIterator last)
     //指针需要初始化为空,否则指针是随机值,push_back的时候发生增容reserve可能出错,
	:_start(nullptr) , _finish(nullptr) , _endofstorage(nullptr)
{
    while (first != last)
    {
        //把迭代器区间的值拷贝到此时的调用对象中
        push_back(*first);
        first++;
    }
}

拷贝构造函数

//v2(v1)
vector(const vector<T>& v)
	 :_start(nullptr) , _finish(nullptr), _endofstorage(nullptr)
{
    _start = new T[v, capacity()];
    memcpy(_start, v._start, sizeof(T) * v.size());
    //注意我们要手动的更改_endofstorage和_finish的指向
    _endofstorage = _start + v.capacity();
    _finish = _start + v.size();
}

上述代码是可能导致问题的!

1.memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中,

2.如果拷贝的是内置类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝

所以不能使用memcpy进行拷贝! 如果容器中存的是string类型等其它自定义类型,就会发生错误

image-20220210214909879

解决办法

memcpy的思路不完美,我们应该使用for循环,把原来容器的内容尾插到现在调用赋值重载函数的容器中.

如果是容器中存的是string类型,就会去调用string的赋值重载函数(是深拷贝)

image-20220210215424955


传统写法

拷贝构造的传统写法的思想是我们最容易想到的:先开辟一块与该容器大小相同的空间,然后将该容器当中的数据一个个拷贝过来即可,最后更新_finish和_endofstorage的位置即可,

//传统写法
vector(const vector<T>& v)
	:_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
	_start = new T[v.capacity()]; //开辟一块和容器v大小相同的空间,_start指向新空间的起始位置
	for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝到当前容器
	{
		_start[i] = v[i];
	}
     //处理两个指针的位置
	_finish = _start + v.size(); 
	_endofstorage = _start + v.capacity(); 
}

现代写法

使用范围for (C++11) 对容器进行遍历,在遍历过程中将容器v中存储的数据一个个尾插过来即可,

image-20220210215705864

vector(const vector<T>& v)
	:_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
	reserve(v.capacity()); //调用reserve函数将容器容量设置为与v相同
	for (const auto& e : v) //将容器v当中的数据一个个尾插过来
	{
		push_back(e);
	}
}

注意: 在使用范围for对容器v进行遍历的过程中,变量e就是每一个数据的拷贝,然后将e尾插到构造出来的容器当中,就算容器v当中存储的数据是string类,在e拷贝时也会自动调用string的拷贝构造(深拷贝),所以也能够避免出现与使用memcpy时类似的问题


现代写法2:

复用拷贝构造函数,构造一个临时容器,然后二者进行交换

vector(const vector<T>& v) // 推荐
	:_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
    vector<T> tmp(v.begin(), v.end());
    swap(tmp);//this->swap(tmp);
}

赋值重载函数opeartor=

vector的赋值同样涉及深拷贝,所以同样不能使用memcpy拷贝数据,又因为要支持连续赋值 ,因为返回引用

传统写法

首先判断是否是给自己赋值,若是给自己赋值则无需进行操作,

若不是给自己赋值,则先开辟一块和容器v大小相同的空间,然后将容器v当中的数据一个个拷贝过来,最后更新_finish和_endofstorage的值即可,

//参数需要加const,否则const对象不能调用
vector<T>& operator=(const vector<T>& v)
{
    // 防止自己给自己赋值
    if (this != &v)
    {
        delete[] _start;//释放原来空间的内容
        _start = new T[v.capacity()];//开辟和拷贝对象一样大小的空间
        //此处不能使用memcpy,防止浅拷贝
        for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来
        {
            _start[i] = v[i];
        }
        //更新指针的位置
        _finish = _start + v.size();
        _endofstorage = _start + v.capacity();
    }
    return *this; //支持连续赋值  出了作用域不销毁,可以返回引用
}

现代写法

首先在右值传参时并没有使用引用传参,因为这样可以间接调用vector的拷贝构造函数,然后将这个拷贝构造出来的容器v与左值进行交换,此时就相当于完成了赋值操作,而容器v会在该函数调用结束时自动析构,

//这里参数不能加const修饰,因为要发生交换,此时是传值拷贝,v就是v2传参是拷贝构造出来的
//v1 = v2
vector<T>& operator=(vector<T> v)
{
    //v是局部对象,出了作用域就调用析构函数销毁了,仅是用来交换v的值到v1中
    swap(v); //交换这两个对象
    return *this;
}

这种写法进行的也是深拷贝,只不过是调用的vector的拷贝构造函数进行的深拷贝,深拷贝构造出v,然后v和当前调用该函数的容器交换


析构函数

先判断该容器是否为空,若不为空,则先释放容器存储数据的空间,然后将容器的各个成员变量设置为空指针即可,

//析构函数
~vector()
{
    //判断原来空间是否为空
    if (_start)
    {
        delete[] _start;//释放空间
    }
    _start = _finish = _endofstorage = nullptr;
}

迭代器

vector的迭代器实际上就是容器当中所存储数据类型的指针

typedef T* iterator;//普通迭代器
typedef const T* const_iterator;//const迭代器

begin & end

begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址,

普通对象调用:可读可写

//普通迭代器 -左闭右开
iterator begin()
{
    return _start;//返回容器的起始位置
}
iterator end()
{
    return _finish; //返回容器当中最后一个数据的下一个数据的位置
}

const对象调用:只读

//const迭代器  -左闭右开
const_iterator begin() const
{
    return _start;//返回容器的起始位置
}
const_iterator end() const
{
    return _finish;//返回容器当中最后一个数据的下一个数据的位置
}

任意类型vector容器迭代器通用遍历方式:

template<class T> //等价于:template<typename T>
void PrintVector(const vector<T>& v)
{
    //取类模板的内嵌类型,类模板没有实例化,编译器不会去找
    //前面加typename,告诉编译器这个是一个类型
	typename vector<T>::const_iterator it = v.begin();//前面需要加typename,否则报错
    //简写:由auto编译器自己推导
	//auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
int main()
{
	vector<int> v(2, 3);
	const vector<char> v2(6, 'x');
	PrintVector(v);
	PrintVector(v2);
	return 0;
}

编译器在编译时会自动将范围for替换为迭代器的形式 支持迭代器就支持范围for

//范围for进行遍历,传引用防止深拷贝
for (auto& e : v)
{
	cout << e << " ";
}
cout << endl;

容量相关的函数

reserve

Bug代码:

//扩容
void reserve(size_t n)
{
    // 确保是扩容
    if (n > capacity())
    {
        T* tmp = new T[n];
        //判断原空间是否为空
        if (_start)
        {
            把原空间的内容拷贝到新空间,拷贝的是字节数!
            //要记得是数据个数*每一个数据的大小  sizeof(T) 
            memcpy(tmp, _start, size()*sizeof(T));
            //释放原空间
            delete[] _start;
        }
        _start = tmp;
        _finish = _start + size();
        _endofstorage = _start + n;
    }
}

原因是:扩容之后,_start的指向已经发生改变,不再指向的是原空间,所以 size()函数计算出来的大小已经失效了, 并且不建议采用memcpy进行拷贝

image-20220206112414470


解决办法:提前保存原来旧空间的元素个数 + 使用for循环拷贝元素

//扩容
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;//reserve了n个空间
    }
}

resize

1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值,
 2、当n小于当前的size时,将size缩小到n,

根据resize函数的规则,进入函数我们可以先判断所给n是否小于容器当前的size,若小于,则通过改变_finish的指向,直接将容器的size缩小到n即可,否则先判断该容器是否需要增容,然后再将扩大的数据赋值为val即可,

//T():匿名对象,调用T类型的默认构造函数,
//如果是string类型,val就是空串,如果是int,val就是0
void resize(size_t n, T val = T())
{
    //case1:缩小空间 
    if (n < size())
    {
        _finish = _start + n;
    }
    else
    {
        //case2:增加数据&容量够
        if (n <= capacity())
        {
            while (_finish < _start + n)
            {
                *_finish = val;
                _finish++;
            }
        }
        else
        {
            //case3:增加数据&容量不够->增容
            reserve(n);
            while (_finish != _start + n)
            {
                *_finish = val;
                _finish++;
            }
        }
    }
}

注意: 在C++当中内置类型也可以看作是一个类,它们也有自己的默认构造函数,所以在给resize函数的参数val设置缺省值时,设置为T( )即可,

image-20220210225432424

优化:

如果n> size() 先判断是否需要扩容,如果需要,则先开辟空间,然后再插入值, 不需要则直接插入值

//优化:
void resize(size_t n, T val = T())
{
    //case1:缩小空间 
    if (n < size())
    {
        _finish = _start + n;
    }
    else
    {
        //检查是否需要扩容
        if (n > capacity())
        {
            reserve(n);
        }
        //增加数据
        while (_finish < _start + n)
        {
            *_finish = val;
            _finish++;
        }
    }
}

image-20220210225201241

capacity

//容量
size_t capacity()
{
    return _endofstorage - _start;//指针-指针  返回当前容器最多能存多少个数据
}

size

获取vector中有效数据个数 _finish - _start 两个指针得到的就是有效数据个数

//数据个数
size_t size()
{
    return _finish - _start;//返回有效数据个数
}

empty

判断vector是否为空 如果_start和_finsh的指向相同, 说明容器内没有元素,就是空

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

增删查改的函数

push_back

要尾插数据首先得判断容器是否已满,若已满则需要先进行增容,然后将数据尾插到_finish指向的位置,再将_finish++即可,

//尾插
//传过来的参数不作修改,可以const修饰+传引用
void push_back(const T& x)
{
    //判断空间是否满了
    if (_finish == _endofstorage)
    {
        //扩容
        size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
        reserve(newcapacity);
    }
    //把元素放在_finish位置
    *_finish = x;
    _finish++;
}

pop_back

尾删数据之前也得先判断容器是否为空,若为空则做断言处理,若不为空,则将_finish–-即可,

//尾删
void pop_back()
{
    //保证容器不为空
    assert(!empty());
    _finish--;//_finish指针前移
}

insert

insert函数可以在所给迭代器pos位置插入数据, 在插入数据前先判断是否需要增容,然后将pos位置及其之后的数据统一向后挪动一位,最后将数据插入到pos位置即可,

注意:如果发生了增容,pos是指向原空间的,而_start是指向新空间了, 所以我们先记录_start和pos的相对距离,如果发生了增容的情况,就需要修正pos位置

//这里我们是不需要返回值的!
void insert(iterator pos, const T& x)
{
    assert(pos >= _start);
	assert(pos <= _finish);
    
    //插入之前:检查是否需要扩容
    if (_finish == _endofstorage)
    {
        size_t len = pos - _start;//记录pos与_start之间的相对距离
        size_t newcapacity = capacity == 0 ? 4 : capacity * 2;//2倍扩容
        reserve(newcapacity);
        //更新pos在新空间的位置,解决增容后pos失效的问题
        pos = _start + len;
    }
    
    iterator end = _finish - 1;
    //把pos位置的值往后移动
    while (end >= pos)
    {
        *(end + 1) = *end;
        end--;
    }
    *pos = x;//放到pos指向的位置
    ++_finish;//数据个数增加一个,_finish后移
}

image-20220210225756916

注意: 若需要增容,则需要在增容前记录pos与_start之间的间隔,然后通过该间隔确定在增容后的容器当中pos的指向,否则pos还指向原来被释放的空间


erase

erase函数可以删除所给迭代器pos位置的数据,在删除数据前需要判断容器是否为空,若为空则需做断言处理,删除数据时直接将pos位置之后的数据统一向前挪动一位,将pos位置的数据覆盖即可

iterator erase(iterator pos)
{
    //容器为空则断言
    assert(!empty()); 
    //把pos位置后面的数据往前挪动覆盖pos位置
    iterator it = pos + 1;
    while (it != _finish)
    {
        *(it - 1) = *it;
        it++;
    }
    --_finish;//删除了一个数据,finish--
    return pos;//返回的是原来删除的数据的下一个数据的迭代器
}

注意:erase函数返回的是原来删除数据的下一个数据的迭代器,主要是为了迭代器失效后返回下一个位置进行处理

image-20220211151119910


vector的insert和erase都可能导致迭代器失效,但是我们实现的时候,insert不返回值,erase返回删除位置的下一个迭代器 ,insert迭代器失效了,就不需要再使用啦! erase迭代器失效了,就接收返回值


swap

swap函数用于交换两个容器的数据,我们可以直接调用std库当中的swap函数将两个容器当中的各个成员变量进行交换即可,

//v1.swap(v2)
void swap(vector<T>& v)
{
    ::swap(_start, v._start);
    ::swap(_finish, v._finish);
    ::swap(_endofstorage, v._endofstorage);
}

注意: 在此处调用库当中的swap需要在swap之前加上::(作用域限定符),告诉编译器这里优先在全局范围寻找swap函数,否则编译器会认为你调用的就是你正在实现的swap函数(就近原则),


访问

operator[]

vector的底层是数组,支持我们使用“下标+[ ]”的方式对容器当中的数据进行访问,实现时直接返回对应位置的数据即可,

//[]运算符重载
//可读可写版本-普通对象调用
T& operator[](size_t pos)
{
    //保证位置的合法性
    assert(pos < size());
    return _start[pos]; //返回的对应数据-可读可写
}
//只读版本-const对象调用
const T& operator[](size_t pos) const
{
    //保证位置的合法性
    assert(pos < size());
    return _start[pos];//返回的对应数据-只读 const修饰
}

注意: 重载运算符[ ]时需要重载一个适用于const容器的,因为const容器通过“下标+[ ]”获取到的数据只允许进行读操作,不能对数据进行修改,


接口函数

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace Mango
{
template<class T>
class vector
{
    public:
    typedef T* iterator;
    typedef const T* const_iterator;

    //默认成员函数
    vector();                                           
    vector(size_t n, const T& val);   
    vector(int n, const T& val);   
    vector(long n, const T& val);   
    template<class InputIterator>
    vector(InputIterator first, InputIterator last);   
    
    vector(const vector<T>& v);                         
    vector<T>& operator=(const vector<T>& v);          
    ~vector();                                          

    //迭代器相关函数
    iterator begin();
    iterator end();
    const_iterator begin()const;
    const_iterator end()const;

    //容量和大小相关函数
    size_t size()const;
    size_t capacity()const;
    void reserve(size_t n);
    void resize(size_t n, const T& val = T());
    bool empty()const;

    //修改容器内容相关函数
    void push_back(const T& x);
    void pop_back();
    void insert(iterator pos, const T& x);
    iterator erase(iterator pos);
    void swap(vector<T>& v);

    //访问容器相关函数
    T& operator[](size_t i);
    const T& operator[](size_t i)const;

    private:
    iterator _start;        //指向容器的头
    iterator _finish;       //指向有效数据的尾
    iterator _endofstorage; //指向容器的尾
};
}

为了防止和库中的vector函数重名,模拟实现时,应该放到我们自己的命名空间中


vector.h

#pragma once

namespace Mango
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

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

		// v2(v1)   传统写法
		/*
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();

			memcpy(_start, v._start, v.size()*sizeof(T));
		}
		*/

		// 一个类模板的成员函数,又可以是一个函数模板
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr), _finish(nullptr), _endofstorage(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(_endofstorage, v._endofstorage);
		}

		// v2(v1)  现代写法
		//vector(const vector& v)
		vector(const vector<T>& v) // 推荐
			:_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);//this->swap(tmp);
		}

		// v1 = v3  现代写法
		// vector& operator=(vector v)
		vector<T>& operator=(vector<T> v) // 推荐
		{
			swap(v);
			return *this;
		}

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

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

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

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

		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();//记录原来的_finish和_start的距离
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T)*size());
					for (size_t i = 0; i < sz; ++i)
					{
						// T 是int,一个一个拷贝没问题
						// T 是string, 一个一个拷贝调用是T的深拷贝赋值,也没问题
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

				_start = tmp;
				// _finish = _start + size(); size()的计算存在问题,所以使用下面的方式
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

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

				while (_finish != _start+n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

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

			// 满了就扩容
			if (_finish == _endofstorage)
			{
				// 扩容会导致pos失效,扩容需要更新一下pos
				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;
		}

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

			--_finish;

			return pos;
		}

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

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

			--_finish;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

测试代码

void test_vector1()
{
    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 (size_t i = 0; i < v.size(); ++i)
    {
        cout << v[i] << " ";//1 2 3 4 5 6
    }
    cout << endl;

    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";//1 2 3 4 5 6
        ++it;
    }
    cout << endl;

    for (auto e : v)
    {
        cout << e << " ";//1 2 3 4 5 6
    }
    cout << endl;
}


void test_vector3()
{
    vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    vector<int> v2(v1);
    for (auto e : v2)
        cout << e << " ";//1 2 3 4
    cout<<endl;

    vector<int> v3;
    v3.push_back(10);
    v3.push_back(20);
    v3.push_back(30);

    v1 = v3;
    for (auto e : v1)
        cout << e << " ";//10 20 30
    cout << endl;
}

void test_vector4()
{
    vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
    if (it != v1.end())
    {
        // 如果insert中发生了扩容,那么会导致it指向空间被释放
        // it本质就是一个野指针,这种问题,我们就叫迭代器失效
        v1.insert(it, 20);
    }

    for (auto e : v1)
    {
        cout << e << " ";//1 20 2 3 4
    }
    cout<<endl;
}

void test_vector5()
{
    // 三种场景去测试
    // 1 2 3 4 5 -> 正常
    // 1 2 3 4   -> 崩溃
    // 1 2 4 5   -> 没删除完
    vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(4);
    v1.push_back(5);
	
     for (auto e : v1)
        cout << e << " "; //1 2 4 5
    
    // 要求删除v1所有的偶数
    vector<int>::iterator it = v1.begin();
    while (it != v1.end())
    {
        if (*it % 2 == 0)
            it = v1.erase(it);
        else
            ++it;
    }   
  
    for (auto e : v1)
        cout << e << " "; //1 5
    cout << endl;
}

void test_vector6()
{
    vector<string> v;
    v.push_back("111111111111111111111111");
    v.push_back("111111111111111111111111");
    v.push_back("1111111111");
    v.push_back("1111111111");
    v.push_back("1111111111");

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

关于memcpy函数设计深拷贝

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
  2. 如果拷贝的是自定义类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且
    自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝

假设vector存的是string对象

image-20220220225224429

->

image-20220220225344711

结论:

如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是
浅拷贝,否则可能会引起内存泄漏甚至程序崩溃,


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

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

相关文章

paddleOCRv3之四: rec识别部分用 tensorRT(C++)部署

文章目录1. 简介&#xff1a;速度测试2. paddle 模型转onnx3. onnx转为tensorRT的engine模型4. tensorRT在vs2017中的配置5. 源码1. 简介&#xff1a; tensorRT是nvdia GPU模型部署的一个框架&#xff0c;似乎只是部分开源&#xff0c;github地址.大多数时候用这个框架去部署模…

十九、Docker容器监控之CAdvisor+InfluxDB+Granfana

1、概述 Docker自带查询容器状态的命令&#xff1a;docker stats&#xff0c;可以看到容器的ID\名称、占用CPU、内存等信息 但是我们不能时时刻刻的盯着这个命令&#xff0c;并且这个都是实时数据不能留痕&#xff0c;如果这个时候某一个容器挂了&#xff0c;我们想查看下当时…

webpack性能优化

splitChunks webpack splitChunks minSize: 只有到目标文件超过这个minSize时才会分包。cacheGroups: 可以对某个第三方包进行单独分离出来 例如&#xff1a; splitChunks: {minSize: 300 * 1024&#xff0c;chunks: all,name: aaa,cacheGroups: {jquery: {name: jquery,test…

SCADA平台在风电场测量的应用,实现风电场的高效管理

一、应用背景 随着煤碳、石油等能源的逐渐枯竭&#xff0c;人类越来越重视可再生能源的利用。风能作为一种清洁的可再生能源日益受到世界各国的重视。中国风能储量大&#xff0c;分布面广&#xff0c;仅陆地上的风能储量就约2.53亿千瓦。我国的风电发展起步较晚&#xff0c;但…

大数据教学实训沙盘介绍

沙盘的作用主要有3个&#xff1a; 1、采集真实数据&#xff0c;解决教学中缺少真实数据的困扰&#xff1b; 2、形成从数据采集、预处理、挖掘建模、模型部署的业务闭环&#xff0c;可以把构建模型发布到沙盘系统上&#xff0c;根据模型产生真实的反馈不断的修正模型精度&#x…

DoIP协议从入门到精通系列——车辆声明

上篇文章对DoIP中物理连接做了说明和描述,介绍了以太网应用到车载网络中重要的两个组织: IEEE;OPEN联盟。本文主要对物理连接后,车辆进行自属信息声明过程做一个完整描述。 一、基础信息 DoIP协议标准由一个或多个DoIP实体实施,具体取决于车辆的网络架构。如下图是车辆网…

SuperMap iServer在不同系统中设置开机自启动--Windows篇

目录前言1.删除已有的 SuperMap iServer 系统服务2.注册 SuperMap iServer 系统服务3.设置 SuperMap iServer 系统服务开机自启动实例作者&#xff1a;kxj 前言 在成功部署SuperMap iServer之后&#xff0c;每次重启电脑都需要手动去启动iServer&#xff0c;如何能让iServer在…

HTML5 Web Worker(多线程处理)

文章目录HTML5 Web Worker(多线程处理)概述简单使用处理复杂数据HTML5 Web Worker(多线程处理) 概述 JavaScript的执行环境是单线程的&#xff0c;也就是一次只能执行一个任务。如果遇到多个任务时&#xff0c;只能排队依次执行。 在HTML5中&#xff0c;可以使用Web Worker创…

小程序集成Three.js,使用npm安装gsap动画库

0.视频演示 three.js集成gsap创建物体动画gsap作为简单易用的补间动画库&#xff0c;获得开发者一致好评。 在小程序中&#xff0c;我们集成了Three.js第三方库&#xff0c;可以创建和加载模型及场景&#xff0c;但是做动画还是需要第三方库的支持。 下面详细说明如何在小程序…

Java SPI机制详解

一、什么是SPI SPI全称Service Provider Interface&#xff0c;是Java提供的一种服务发现机制。实现服务接口和服务实现的解耦。 Java SPI 实际上是“基于接口的编程&#xff0b;策略模式&#xff0b;配置文件”组合实现的动态加载机制&#xff0c;实现不修改任何代码的情况下…

不错的一个麦肯锡信任公式

1&#xff09;可信度&#xff1a;这人是不是专家。 你是否让他人可以相信你这个人。这取决于你解决问题的能力、经验、专业知识、资源等等&#xff1b;这个人的专业能力是否真有别人说的那么出色&#xff0c;是否能够胜任这份工作呢&#xff1f;过往的履历中是否做过足以让我值…

函数指针到底需不需要解引用?类成员函数呢?

1、 普通函数指针 C函数指针有两点比较令人疑惑的做法&#xff1a; 函数名作为实参时&#xff0c;到底要不要取地址&#xff1f;通过函数指针调用函数时&#xff0c;到底要不要解引用&#xff1f; int add(int a, int b) {cout << "common function: " <…

ubuntu18安装、测试YOLOV3记录

官方教程&#xff1a; YOLO: Real-Time Object Detection 一、使用预训练模型进行检测 1、安装Darknet: git clone https://github.com/pjreddie/darknet cd darknet make 2、下载预训练权重https://pjreddie.com/media/files/yolov3.weights&#xff08;打开链接或wget&…

VSCode无密码连接远程服务器,并能debug python代码

1.官网下载VScode 官网 2.打开VScode&#xff0c;在扩展中搜索下载远程连接插件Remote-SSH 下载完毕会在侧边栏产生“远程资源管理器”图标①&#xff0c;打开远程资源管理器&#xff0c;点击右上角设置进入配置界面&#xff0c;并按照②添加远程服务器账号&#xff0c;输入…

Charles -证书过期失效处理方法

当出现环境配置正常但却无法抓包的时候&#xff0c;可能是因为证书失效了&#xff0c;这种情况移除旧证书&#xff0c;安装新的证书即可。 一、判断是否证书过期 iOS手机&#xff1a; 进入&#xff1a;设置 > 通用 > VPN与设备管理 > Charles Proxy CA... > 更多…

45. 含并行连结的网络(GoogLeNet)代码实现

1. Inception块 import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2lclass Inception(nn.Module):# c1--c4是每条路径的输出通道数,c2,c3,c4是一个tuple元组def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):su…

C# .Net MVC框架实现最简单的登陆

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言C#.net实现最简单登陆一、C#,.net是什么&#xff0c;相比较于C,java二、C# .net创建一个MVC框架工程1.步骤实现结果前言 C#.net实现最简单登陆 提示&#xff1…

linux-----基本操作指令(2)

将一个文件按照升序排序:注意这里面的S是大写 按照文件大小按照降序排列:ll -hS 按照文件大小按照升序排列:ll -hrS 一:cp(copy)表示复制&#xff0c;类似于windows系统上面的复制文件到指定文件夹的操作时类似的&#xff0c;拿鼠标一拖到指定路径 1)同时也就是说这个文件最终在…

Python实现的通用的二进制数据分析工具,分析任意格式的二进制数据,还能同时查看协议文档

这是一个通用的二进制数据分析工具。 完整程序代码下载地址&#xff1a;Python实现的通用的二进制数据分析工具 它能做什么 分析任意格式的二进制数据&#xff0c;还能同时查看协议文档逐字节、逐位分析手动、自动分析对分析结果建透视图&#xff0c;发现规律&#xff0c;学习…