文章目录
vector与数组的不同:
- vector是表示可以改变大小的数组的序列容器。
- vector与数组一样,元素使用连续的存储空间,就可以使用常规指针,指向其元素,使用偏移量来访问存储空间中的元素。
- vector 与数组不同的是,vector的大小可以动态变化,容器会自动扩容存储空间。
vector的特点: - vector使用一个动态分配的连续存储空间来存储元素。在插入新元素时存储空间可能需要重新分配,以便增大大小,这意味着分配一个新存储空间要将所有元素移动到其中。就处理时间而言,这是一项相对昂贵的任务,因此,向量不会在每次向容器添加元素时重新分配。
- vector容器可以分配一些额外的存储空间以适应可能的增长,因此容器的实际容量可能大于严格需要的存储容量(即容器的大小)。。
- vector与array相比,向量消耗更多的内存,以换取管理存储和以高效方式动态增长的能力。
- 与其他动态序列容器(deques、 list和forward_list)相比,vector可以非常高效地访问其元素(就像数组一样)并相对高效地从其末尾添加或删除元素。对于在结尾以外的位置插入或删除元素的操作,其性能较差。
初始化:
int main()
{
vector<int> ar1;
vector<int> ar2 = { 12,23,34 };
vector<int> ar3({ 12,23,34 });//初始化列表
vector<int> ar4(10, 23);//初始化十个空间,将23拷贝进去
vector<int> ar5 = ar2;
vector<int> ar6(std::move(ar3));
}
- 调用构造函数初始化object的属性,创建对象,然后将这些对象,调用拷贝构造函数,拷贝到vector中,然后调用析构函数,析构掉拷贝
过程中产生的4个无名对象。 - 等整个函数生存期到了,在析构vector中的4个对象。
int main()
{
vector<Object> vec = { Object(10),Object(20),Object(30),Object(40)};
cout << vec.size() << endl;
cout << vec.capacity() << endl;
}
访问vector中的数据
1.for循环,下标访问
2.at()访问
3.迭代器访问,相当于面向对象的指针
4.常性迭代器,无法改变值,只可取值
5.基于范围的for循环,使用引用的方式,因为如果是变量,每一步都会拷贝一个对象。
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i].Value() << endl;
//obj.Value()
}
//for (int i = 0; i < vec.capacity(); i++)
//{ //代表的是容量,后面访问的均为空
// cout << vec.at(i).Value() << endl;
// //vec.obj.value()
//}
vector<Object>::iterator it = vec.begin();
for (; it != vec.end(); it++)
{
cout << it->Value() << " " << endl;
// it->Value() += 10;
// cout << (*it).Value() << " " << endl;
}
vector<Object>::const_iterator it2 = vec.begin();
for (; it2 != vec.end(); it2++)
{
//常性迭代器只能调动常方法
// cout << it2->Value() << endl;
cout << (*it2).Value() << endl;
//it->Value() += 100;不能修改,只能读
}
auto(底层用迭代器实现)
自己设计的类型会导致反复析构
内置类型不影响
//推演出x是object类型,因此需要遍历vector容器,需要每次都把当前节点的对象拷贝给x,如果采用引用,就可以直接操纵vector中的对象,完成相应操作。
for (auto x : vec)//x不是迭代器,vector容器,
{
cout << x.Value() << " " << endl;
}
cout << endl;
如下结果,反复拷贝和析构
解决办法,x使用引用
for (auto& x : vec)//x不是迭代器,vector容器,
{
cout << x.Value() << " " << endl;
}
cout << endl;
结果:
如果不希望修改容器中的值:
for (const auto& x : vec)
数组也可以被认为是一种容器
内置类型:不使用引用,直接将1拷贝给x
int arr[] = { 1,2,3,4,5 };
for (auto x : arr)
{ //直接打印
cout << x << endl;//数组也可以被认为是一种容器
}
使用引用:取的是1的地址
int arr[] = { 1,2,3,4,5 };
for (auto &x : arr)
{ //需要下地址,找到其值
cout << x << endl;//数组也可以被认为是一种容器
}
const修饰的是整个迭代器,修饰的是it3本身
所以其指向的值可以变,但是其本身不能变
总结
是否是使用引用和const,取决于使用目的。容器中的数据是否需要修改,是内置类型还是设计类型,等等。
const vector<Object>::iterator it3 = vec.begin();
for (; it3 != vec.end(); it3++) //const修饰的是整个迭代器,修饰的是it3本身 it3++ error
{ //所以其指向的值可以变,但是其本身不能变
cout << it3->Value() << endl;
it3->Value() += 10;
}
reserve()(只进行扩容,对象个数不改变)
void reserve(size_type new_cap);
增加vector的容量到大于或等于new_cap,若new_cap大于当前capacity(),则重新分配空间,将原来数据拷贝构造过去,如果小于capacity(),则不处理。
int main()
{
vector<Object> vec = { Object(10),Object(20),Object(30),Object(40) };
cout << vec.size() << endl;
cout << vec.capacity() << endl;
vec.reserve(10); //如果大于原本的容量。重新申请空间,并将原来容器中的对象,调用拷贝构造,拷贝到新的空间。
cout << vec.size() << endl;
cout << vec.capacity() << endl;
}
迭代器是失效问题:
如果在使用reserve()时, enw_cap大于capacity,需要重新申请空间,那么迭代器的指向原有空间会被析构,造成迭代器失效。
int main()
{
vector<Object> vec = { Object(10),Object(20),Object(30),Object(40) };
cout << vec.size() << endl;
cout << vec.capacity() << endl;
vector<Object>::iterator it = vec.begin();
Object& obj = vec.back();
vec.reserve(10);
cout << vec.size() << endl;
cout << vec.capacity() << endl;
}
如下图:
resize():(对象的个数改变,可以进行容量的收缩)
如果resize()的参数大于原来的capacity,那么
- 会重新申请空间
- 再对申请的额外的空间调用默认构造,创建对象,初始化为0;
- 把源数据拷贝构造过来。
vec.resize(10);
vec.resize(2);//会将10个对象,后面8个对象全部析构掉
assign()
void assign(size_t count, _Ty());
这个函数和上面两个的不同之处是:
- 先构造一个目标对象;
- 析构之前的对象;
- 拷贝构造这个目标对象count次;
- 析构目标对象。
int main(void)
{
vector<Object> vec = { Object(10), Object(20), Object(30),Object(40) };
cout << vec.size() << endl;
cout << vec.capacity() << endl;
vec.assign(10, Object(100));//构建新对象,delete 原来的对象,,拷贝构造到vector容器中,拷贝10次
cout << vec.size() << endl;
cout << vec.capacity() << endl;
return 0;
}
程序运行一段时间,只删除对象,不删除容量,当前对象的个数远远小于容量,如何降容量减下来?
int main(void)
{
vector<Object> vec = { Object(10), Object(20), Object(30),Object(40) };
cout << vec.size() << endl;
cout << vec.capacity() << endl;
{
vector<Object> tmp(vec);
tmp.swap(vec);
}
cout << vec.size() << endl;
cout << vec.capacity() << endl;
return 0;
}
1.调用拷贝构造函数将vec的中的对象拷贝到tmp
2.使用swap,将tmp和vec中的first,last,end进行交换。
3.块作用域结束,释放tmp中的对象,以及空间。
完成容量的缩小。(必须使用块作用域)
vector的数据老是搬家,非常的危险。
push_back()
int main(void)
{
vector<Object> vec = { Object(10), Object(20), Object(30),Object(40) };
cout << vec.size() << endl;
cout << vec.capacity() << endl;
vec.reserve(10);
vector<Object>::iterator it = vec.begin();
cout << it->Value() << endl;
vec.push_back(Object(40));
cout << it->Value() << endl;
return 0;
}
如果没有一开始就给vec增容,就会导致插入数据时,增容,导致迭代器失效。和reserve()的原因一样,it所指的原空间释放。
迭代器失效:
由于插入数据时没有空间,就需要扩容,原空间释放。迭代器失效。
insert()
int main(void)
{
vector<Object> vec = { Object(10), Object(20), Object(30),Object(40) };
cout << vec.size() << endl;
cout << vec.capacity() << endl;
vec.reserve(10);
vector<Object>::iterator it = vec.begin();
cout << it->Value() << endl;
vec.insert(it, Object(90)); //均是调用拷贝构造函数,将Object对象拷贝到vec中,it所指的位置。
vec.insert(it, { Object(70),Object(80) });
//可以初始化列表方式进行初始化。
cout << it->Value() << endl;
return 0;
}
原位构建元素:emplace_back(),
使用定位new,创建对象。
如下:
template<class T,class<...Arg>>
void Make(T* p,Arg...arg)
{
new(p) T(std::forward<Arg...>(arg)...);
}
emplace_back(100)
将100作为实参,在vector容器中的插入点,调用Object的构造函数,直接初始化,创建对象。
而push_back 是隐式的调用拷贝构造,将obj无名对象拷贝到vector的插入点。然后将拷贝构造析构掉。
int main(void)
{
vector<Object> vec = { Object(10), Object(20), Object(30),Object(40) };
vec.reserve(10);
cout << vec.size() << endl;
cout << vec.capacity() << endl;
vec.emplace_back(100);
cout << vec.size() << endl;
cout << vec.capacity() << endl;
return 0;
}
#emplace_back和push_back相比,前者的效率更快。
区别:两者的构建形式有什么区别?
前者使用定位new在vector的插入节点,调用构造函数,创建对象
后者,调用拷贝构造将Object对象,将无名对象拷贝到vector容器中。
解决迭代器失效的方法:
提前增容,就不会因为容量不足导致扩容,让先前的空间被析构。