STL(Standard Template Library标准模板库):
是c++标准库的重要组成部分.是一个包罗数据结构和算法的软件框架,
STL使得能够直接使用各种容器(vector、list、map等)和使用各种算法.
vector介绍
(1) vector是STL的一个顺序容器,底层使用一个可以动态增长的数组实现的.
cplusplus.com:
(2) 同时它也是一个类模板,因为顺序表里可以存储不同类型的数据.
(3) allocator< T >是一个内存池,里面的数据都是T类型,(vector里alloc的模板参数给了缺省值)默认用库的,也可以自己显式传自己写的内存池.
--内存池介绍
一个自定义类,我们可以在它内部重载专属的operator new函数,
此时new这个类就会调用自己写的operator new函数.
如果多次new这个类,频繁向堆申请空间,效率不高.
池化技术可以解决频繁向堆申请内存的问题,即内存池
c++库里有一个内存池 —— allocator类,也叫空间配置器.
内存池一次向堆申请大量空间,此时申请空间不需要去找堆,而是找内存池申请(比向堆申请要快).
class ListNode
{
public:
ListNode(int val = 0)
:_next(nullptr)
,_val(val)
{}
void* operator new(size_t n)
{
cout << "走重载的operator new" << endl;
void* ret = _alloc.allocate(1);//在内存池里申请一个结点内存
return ret;
}
void operator delete(void* block)
{
cout << "走重载的operator delete" << endl;
//释放内存还给内存池
_alloc.deallocate((ListNode*)block, 1);
}
private:
ListNode* _next;
int _val;
static allocator<ListNode> _alloc;//让该类的所有对象共用一个内存池【声明】
};
//定义
allocator<ListNode> ListNode::_alloc;
void test1()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
delete n1;
delete n2;
delete n3;
}
--构造函数
(1) 全缺省的构造
explicit vector (const allocator_type& alloc = allocator_type());
explicit:不允许该单参数的构造函数进行隐式类型转换
allocator_type:即第二个模板参数Alloc类型,表示传递的内存池类型
allocator_type():调用该内存池类型的默认构造函数,用于初始化alloc
一般用c++库默认的内存池,使用时一般不需要传第二个模板参数Alloc
void test2()
{
//显式实例化,显式传模板参数T,指定要存储的数据类型
vector<int> v1;
vector<double> v2;
//调用成员函数push_back(),尾插数据
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v2.push_back(1.1);
v2.push_back(2.2);
v2.push_back(3.3);
//vector重载了operator[ ],可以像数组一样访问对应位置的值
for (int i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
for (int i = 0; i < v2.size(); ++i)
{
cout << v2[i] << " ";
}
}
(2) 用n个val进行构造
explicit vector(size_type n, const value_type & val = value_type(), const allocator_type & alloc = allocator_type());
size_type:即size_t
value_type:即T,顺序表里存储的数据类型
value_type():调用T类型的默认构造函数,内置类型有默认值
void test3()
{
int a = int();
double b = double();
//正常使用
vector<int> v1(3, 1);//用3个1初始化v1
vector<int> v2(3);//用3个默认的int初始化v2,即0
vector<int*> v3(3);//用3个默认的int*初始化v3,即nullptr
}
(3) 用一段迭代器区间,构造vector
template <class InputIterator>
vector(InputIterator first, InputIterator last,
const allocator_type & alloc = allocator_type());
为什么要写成函数模板?
因为可以用其他容器的迭代器区间来初始化vector.例:
void test4()
{
string s1("abcdef");
//char类型会隐式类型转换成int,迭代器类型string::iterator
vector<int> v1(s1.begin(), s1.end());
//迭代器类型vector<int>::iterator
vector<int> v2(v1.begin(), v1.end());
cout << "遍历v1:" << endl;
for (size_t i = 0; i < v1.size(); ++i)
cout << v1[i] << " ";
cout << endl;
cout << "遍历v2:" << endl;
for (size_t i = 0; i < v2.size(); ++i)
cout << v2[i] << " ";
cout << endl;
}
注意:vector是一个类模板,vector< int >才是实例化出来的类
(4) 拷贝构造
vector (const vector& x);
void test5()
{
vector<int> v1(3, 1);
//效果相同,内部实现深拷贝
vector<int> v2 = v1;
vector<int> v3(v1);
}
--vector的遍历
(1)调用operator[ ]运算符重载,用[ ]访问对应位置的数据
reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
reference:即T&,可以通过[ ]直接修改n下标位置的数据
const_reference:即const T&,const对象会调用第二个函数,不允许修改内容
(2) 迭代器遍历
vector是一个类模板,vector< T >是实例化出来的类,
所以它的迭代器类型为:vector<T>::iterator
void test6()
{
vector<int> v(3, 2);
vector<int>::iterator it = v.begin();
//左闭右开
while (it != v.end())
{
cout << *it << " ";
++it;
}
}
--扩容机制
size_type capacity() const;//返回vector当前容量
不断插入数据,若当前容量改变,打印出来,获取当前平台下vector的扩容机制.
//测试vs2022的vector扩容机制
void test7()
{
vector<int> v;
cout << "初始容量:" << v.capacity() << endl;
size_t curCapacity = v.capacity();
size_t maxNum = 1000;
for (size_t i = 0; i < maxNum; ++i)
{
v.push_back(i);
//插入后如果发生扩容
if (curCapacity != v.capacity())
{
curCapacity = v.capacity();
cout << "新扩容:" << curCapacity << endl;
}
}
}
扩容有消耗,若提前知道要插入的数据量,可以用reserve()提前设置容量,一次性申请空间.
--查找
vector内部没有提供find函数,
因为vector、list、map等容器,它们的find是通用的,都是在一段迭代器区间里找一个数据
而string不同,它可能要找一个字符、一个子串,同时要返回下标.
这个通用的find被放在algorithm文件中.
template <class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val);
在一个迭代器区间[first, last)查找val,并返回第一个出现的val对应的迭代器类型.
若没有找到,返回last.
#include <algorithm>
void test8()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(2);
vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
if (it != v1.end())
{
//找到并修改
*it = 4;
for (auto x : v1)
cout << x << " ";
}
else
cout << "未找到" << endl;
}
--插入
(1) 在迭代器位置position之前,插入val
iterator insert (iterator position, const value_type& val);
(2)在position之前插入n个val
void insert (iterator position, size_type n, const value_type& val);
(3)在position位置之前,插入一段 迭代器区间 的数据
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
例:
void test9()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//在3之前插入5
//先找到3的位置
vector<int>::iterator pos1 = find(v.begin(), v.end(), 3);
if (pos1 != v.end())
{
//找到并插入5
v.insert(pos1, 5);
for (auto x : v)
cout << x << " ";
}
cout << endl << endl;
//1 2 5 3 4
//在5之前插入3个1
vector<int>::iterator pos2 = find(v.begin(), v.end(), 5);
if (pos2 != v.end())
{
v.insert(pos2, 3, 1);
for (auto x : v)
cout << x << " ";
}
cout << endl << endl;
vector<int> v2(3, 5);// 5 5 5
//12111534
//在2之前插入v2.begin()~v2.end()
vector<int>::iterator pos3 = find(v.begin(), v.end(), 2);
if (pos3 != v.end())
{
v.insert(pos3, v2.begin(), v2.end());
for (auto x : v)
cout << x << " ";
}
}
--删除
(1) 删除position位置的数据
iterator erase (iterator position);
(2)删除[first,last)该迭代器区间的所有数据
iterator erase (iterator first, iterator last);
void test10()
{
vector<int> v;
for (size_t i = 0; i < 10; ++i)
v.push_back(i);//0~9
//删除4
vector<int>::iterator pos = find(v.begin(), v.end(), 4);
if (pos != v.end())
{
v.erase(pos);
for (auto x : v)
cout << x << " ";
}
cout << endl << endl;
//删除0~5的所有数据,不包括5
vector<int>::iterator startV = find(v.begin(), v.end(), 0);
vector<int>::iterator endV = find(v.begin(), v.end(), 5);
if (startV != v.end() && endV != v.end())
{
v.erase(startV, endV);
for (auto x : v)
cout << x << " ";
}
}
--vector< char > 和 string 差异
(1) string对象定义时后面默认有'\0'
(2) vector不支持+=,string支持+=一个字符/字符串
(3) vector没有内置find,string内置find用于查找字符或子串
(4) string支持<<和>>重载,可以直接打印字符串,vector不支持等.
vector< char >无法替代string
vector模拟实现
sgi版的stl源代码来源:
也能点下面链接直接下载:
源码的成员变量:
模板不支持分离编译,类模板的函数声明和定义要在同一文件中
因此小函数定义在类里面(默认带了inline),大函数定义在类外面(但保证在同一文件)
namespace lyh
{
template<class T>
class vector
{
public:
//vector底层是个连续的数组,迭代器就是原生指针
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start; //指向第一个数据
iterator _finish; //指向最后一个数据的下一个位置
iterator _end_of_storage;//指向当前开的空间的末尾
};
}
--构造与析构函数
//默认构造
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
//用n个val构造
vector(size_t n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
//开空间+初始化空间
T* tmp = new T[n];
for (size_t i = 0; i < n; ++i)
tmp[i] = val;
//更新成员变量
_start = tmp;
_finish = _start + n;
_end_of_storage = _finish;//初始容量和初始数据个数一致
}
//用一段迭代器区间构造
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
//元素个数
size_t n = last - first;
//开空间+拷贝数据
T* tmp = new T[n];
for (size_t i = 0; i < n; ++i)
{
tmp[i] = first[i];
}
//更新成员变量
_start = tmp;
_finish = tmp + n;
_end_of_storage = tmp + n;
}
~vector()
{
std::cout << "成功调用析构函数" << std::endl;//仅用于验证
delete[] _start;
}
//以下实现的函数仅用来验证构造出来的vector的数据
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _end_of_storage - _start;
}
T& operator[](size_t n)
{
//检查是否越界访问
assert(n < size());
return *(_start + n);
}
test11以及下面的测试函数,都在lyh这个命名空间中,
该文件已经展开了std命名空间,在当前命名空间找不到的符号,
也会到std中找.
namespace lyh
{
void test11()
{
vector<int> v1;
vector<int> v2(3, 1);
string s1("abcdefg");
vector<int> v3(s1.begin(), s1.end());
cout <<"v1的数据个数:"<< v1.size() << " v1的初始容量:" << v1.capacity() << endl;
cout << "v2的数据个数:"<<v2.size() << " v2的初始容量:" << v2.capacity() << endl;
cout << "v3的数据个数:" << v3.size() << " v3的初始容量:" << v3.capacity() << endl;
cout << "v2的数据如下:" << endl;
for (size_t i = 0; i < v2.size(); ++i)
{
cout << v2[i] << " ";
}
cout << endl;
cout << "v3的数据如下:" << endl;
for (size_t i = 0; i < v3.size(); ++i)
{
cout << v3[i] << " ";
}
}
}
注意:
//当定义vector<int> v2(3,1)时;
//由于编译器会调用最匹配的函数,会调用到迭代器区间构造的函数
//因为该函数的两个模板参数都相同,不会去调用vector(size_t n, const T& val)
//所以多提供一个vector(int n, const T& val)
vector(int n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
//开空间+初始化空间
T* tmp = new T[n];
for (size_t i = 0; i < n; ++i)
tmp[i] = val;
//更新成员变量
_start = tmp;
_finish = _start + n;
_end_of_storage = _finish;//初始容量和初始数据个数一致
}
--reserve和resize
reserve仅仅改变容量,不会改变数据量
resize改变数据量,必要时也会扩容
//设置数据个数为n,新增加的数据初始化为val
void resize(size_t n, const T& val)
{
//(1) n > 当前数据量
if (n > size())
{
//扩容+初始化
reserve(n);
for (size_t i = size(); i < n; i++)
_start[i] = val;
_finish = _start + n;
}
//(2) n < 当前数据量
else if (n < size())
{
//删除多出n的数据
_finish = _start + n;
}
//(3) n =当前数据量,无操作
else{}
}
void reserve(size_t n)
{
if (n > capacity())
{
//开空间,拷贝数据
T* newBlock = new T[n];
for (size_t i = 0; i < size(); ++i)
{
newBlock[i] = _start[i];
}
//记录当前size()的值,因为它内部是_finish - _start算出来的,更新成员变量时,不能使用size()
size_t st = size();
//释放旧空间
delete[] _start;
//更新成员变量
_start = newBlock;
_finish = newBlock + st;
_end_of_storage = newBlock + n;
}
}
void test12()
{
vector<int> v;
//提前申请空间,不会有扩容
//v.reserve(1000);
v.resize(1000, 0); //改变数据量,改变容量
cout << "初始容量:" << v.capacity() << endl;
cout << "初始数据量:" << v.size() << endl;
size_t curCapacity = v.capacity();
size_t maxNum = 1000;
for (size_t i = 0; i < maxNum; ++i)
{
v.push_back(i);
//插入后如果发生扩容
if (curCapacity != v.capacity())
{
curCapacity = v.capacity();
cout << "新扩容:" << curCapacity << endl;
}
}
}
--尾插尾删
//const引用可以引用常量数据、引用隐式类型转换中间产生的临时变量
//引用减少拷贝
void push_back(const T& x)
{
//1 检查扩容
if (_finish == _end_of_storage)
reserve(capacity() == 0 ? 5 : 2 * capacity() );
//2 插入数据
*_finish = x;
++_finish;
}
void pop_back()
{
assert(size() > 0);
--_finish;
}
--迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
void test13()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.pop_back();
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//依次取出容器v的每一个迭代器,解引用赋值给x(如果x不是引用)
for (auto& x : v)
{
cout << x << " ";
}
}
--insert和erase的迭代器失效【重点】
insert和erase的实现
( 1 ) 以下的insert实现存在问题:
若发生扩容,_start等成员变量全都指向全新的空间,
但传过去的pos仍然指向旧空间,此时pos失效了(内部扩容发生的失效)
//第一种插入实现:在pos位置之前插入val
void insert(iterator pos, const T& val)
{
//允许头插或尾插
assert(pos >= _start && pos <= _finish);
//判断扩容
if (_finish == _end_of_storage)
reserve(capacity() == 0 ? 5 : capacity() * 2);
//从后往前拷贝
iterator cur = _finish;
iterator pre = _finish - 1;
while (cur != pos)
{
*cur = *pre;
--pre;
--cur;
}
//当cur == pos,说明所有数据挪好了
//放置数据,更新_finish
*pos = val;
++_finish;
}
( 2 ) 版本2:先计算扩容前pos与_start的相对位置,发生扩容后修改pos
void insert(iterator pos, const T& val)
{
//允许头插或尾插
assert(pos >= _start && pos <= _finish);
//先计算当前pos与_start的相对位置
size_t relative_pos = pos - _start;
//保证扩容后,pos指向的相对位置与之前的一致
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 5 : capacity() * 2);
pos = _start + relative_pos;//一定要更新pos
}
//接下来正常拷贝
//从后往前拷贝
iterator cur = _finish;
iterator pre = _finish - 1;
while (cur != pos)
{
*cur = *pre;
--pre;
--cur;
}
//当cur == pos,说明所有数据挪好了
//放置数据,更新_finish
*pos = val;
++_finish;
}
void test14()
{
vector<int> v;
v.insert(v.end(), 1);//尾插
v.insert(v.begin(), 2);//头插
//2 1
for (auto& x : v)
cout << x << " ";
cout << endl << endl;
v.push_back(3);
v.push_back(4);
v.push_back(5);
//2 1 3 4 5
//在3之前插入一个6
//找到3的位置
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
v.insert(pos, 6);
for (auto& x : v)
cout << x << " ";
}
提问1:插入数据后,外面的pos还可以用吗?
在pos位置插入数据后,不要访问pos,因为插入后如果发生扩容,pos会失效成为野指针.
提问2:可以把函数原型改成:void insert(iterator& pos, const T& val)吗?
不能,会有下面的情况
(3) erase实现(没有缩容)
void erase(iterator pos)
{
assert(size() > 0);
assert(pos >= _start);
assert(pos < _finish);
//把后面的数据往前挪动
iterator pre = pos;
iterator cur = pos + 1;
while (cur != _finish)
{
*pre = *cur;
++pre;
++cur;
}
--_finish;
}
erase会导致pos失效吗?
如果内部有缩容机制,pos会失效,否则没有失效
STL是一种规范,规定了vector要实现的功能:插入、删除等。
但没有规定如何去实现。在少数编译器下,会有缩容机制(以空间换时间).
所以调用erase后,也不要去访问外面的pos.
insert和erase的使用
(1) 要求删除所有出现的1
void test18()
{
//删除所有出现的1
vector<int> v(3, 1);
v.push_back(4);
v.push_back(5);
v.push_back(1);
v.push_back(6);
//遍历vector
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it == 1)
{
//由于erase后,迭代器会有失效的风险,
//所以erase会返回 被删除元素 的 下一个元素 的迭代器,可以用it接收
it = v.erase(it);
//此时it已经指向下一个元素,不应该再++
}
else
++it;
}
for (auto x : v)
cout << x << " ";
}
库里的erase的一种声明:
完整的erase模拟实现代码:
stl规定了erase会返回 被删除元素 的 下一个元素 的迭代器
iterator erase(iterator pos)
{
assert(size() > 0);
assert(pos >= _start);
assert(pos < _finish);
//把后面的数据往前挪动
iterator pre = pos;
iterator cur = pos + 1;
while (cur != _finish)
{
*pre = *cur;
++pre;
++cur;
}
--_finish;
return pos;
}
(2) 要求在所有的2前面,插入一个9
void test19()
{
//在所有的2前面插入9
vector<int> v;
v.push_back(2);
v.push_back(2);
v.push_back(4);
v.push_back(2);
v.push_back(7);
// 2 3 4 2 7
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it == 2)
{
//插入数据后同样需要更新it,否则it可能会是野指针
//stl规定返回 插入的新数据位置的迭代器
it = v.insert(it, 9);
++it;
//所以应该跳过刚才已经遍历过的数据
++it;
}
else
++it;
}
for (auto x : v)
cout << x << " ";
}
完整的insert模拟实现代码:
insert会返回新插入元素的迭代器位置
//先计算扩容前pos与_start的相对位置,发生扩容后修改pos,返回新插入数据的迭代器位置
iterator insert(iterator pos, const T& val)
{
//允许头插或尾插
assert(pos >= _start && pos <= _finish);
//先计算当前pos与_start的相对位置
size_t relative_pos = pos - _start;
//保证扩容后,pos指向的相对位置与之前的一致
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 5 : capacity() * 2);
pos = _start + relative_pos;//一定要更新pos
}
//接下来正常拷贝
//从后往前拷贝
iterator cur = _finish;
iterator pre = _finish - 1;
while (cur != pos)
{
*cur = *pre;
--pre;
--cur;
}
//当cur == pos,说明所有数据挪好了
//放置数据,更新_finish
*pos = val;
++_finish;
//返回新插入数据的迭代器位置
return pos;
}
总结
vector中insert和erase的迭代器失效有两种:
第一种是 插入时发生扩容 或 删除时发生缩容 导致外面的pos成了野指针
解决方案:接收insert和erase返回的迭代器更新pos.
第二种是 pos接收insert和erase返回值后,pos已经不是指向原来的值,
insert返回新插入元素的迭代器位置,erase返回 被删除元素 的 下一个元素迭代器位置.
拷贝构造与赋值运算符重载
默认的拷贝构造和赋值运算符重载,会按字节进行浅拷贝.
与string一样:
(1) 两个对象内部指向同一块空间,一个对象修改影响另一个对象
(2) 同一块空间会被delete[]两次,程序崩溃
所以要手动实现深拷贝.
拷贝构造函数手动拷贝写法:
注意内部不能浅拷贝T类型数据,因为vector存储的数据类型可能也需要深拷贝.
//1 手动拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
//开空间,拷贝数据
T* tmp = new T[v.capacity()];
//也可以用v.size()
//memcpy实现的浅拷贝,若出现vector<vector<int>> vv,
//vector内部不能浅拷贝T类型,也必须是深拷贝
//错误:memcpy(tmp, v._start, sizeof(T) * v.size());
for (size_t i = 0; i < v.size(); i++)
{
tmp[i] = v[i]; //相当于内部调用T类型的赋值运算符重载完成深拷贝
//注意(const vector<T>类型)v调用const类型的[]运算符重载,没有会报错
}
//更新成员变量
_start = tmp;
_finish = tmp + v.size();
_end_of_storage = tmp + v.capacity();
}
用已有的构造函数帮助拷贝构造:
用已有的构造函数,构造出和v相同数据的临时对象,然后this的所有成员变量与临时对象的交换.
void swap(vector<T>& tmp)
{
//交换所有成员变量
std::swap(tmp._start, _start);
std::swap(tmp._finish, _finish);
std::swap(tmp._end_of_storage, _end_of_storage);
}
//2 用已有的构造函数帮助拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
//调用之前写的迭代器区间构造函数
vector<T> tmp(v.begin(), v.end());
//交换tmp和this的所有成员变量
swap(tmp);
//直接用库里的swap交换2个类对象,会调用vector<T>的1次拷贝构造,和vector<T>的2次赋值运算符重载
//所以内部写一个swap用于交换两个类的成员变量即可
}
赋值运算符重载,手动拷贝版本:
注意要释放旧空间,以及内部不能浅拷贝T类型数据.
//1 赋值运算符重载,手动拷贝
vector<T>& operator=(const vector<T>& v)
{
//不是自我赋值才处理
if (&v != this)
{
//开空间+拷贝数据
T* tmp = new T[v.capacity()];
//与拷贝构造相似,不能浅拷贝T类型
//memcpy(tmp, v._start, v.size() * sizeof(T));
for (size_t i = 0; i < v.size(); ++i)
{
tmp[i] = v[i];
}
//释放旧空间
delete[] _start;
//更新this的成员变量
_start = tmp;
_finish = tmp + v.size();
_end_of_storage = tmp + v.capacity();
}
return *this;
}
赋值运算符重载,调用拷贝构造函数帮助拷贝:
//2 赋值运算符重载,调用拷贝构造实现
vector<T>& operator=(vector<T> v)
{
//形参拷贝构造实参,此时v正是this需要的
//this的成员变量与v的成员变量交换
//让形参v在函数调用结束后帮忙清理旧空间
swap(v);
return *this;
}
void test20()
{
vector<int> v1(2, 3);
for (auto x : v1)
cout << x << " ";
cout << endl;
//拷贝构造
vector<int> v2(v1);
for (auto x : v2)
cout << x << " ";
cout << endl;
vector<int> v3;
//赋值运算符重载
v3 = v1;
for (auto x : v3)
cout << x << " ";
}